reorganize service and add timer, require auth on viewer
This commit is contained in:
parent
1fad7e1b23
commit
fb1180ae60
23
README.md
23
README.md
|
@ -12,16 +12,33 @@ Does not support different accounts.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
|
### Archiver
|
||||||
|
|
||||||
1. Create a venv
|
1. Create a venv
|
||||||
2. `pip install -r requirements.txt`
|
2. `pip install -r requirements.txt`
|
||||||
3. `cp config.yml.sample config.yml`
|
3. `cp config.yml.sample config.yml`
|
||||||
4. Edit `config.yml` and configure your login info.
|
4. Edit `config.yml` and configure your login info.
|
||||||
5. `python3 run.py`
|
5. `python3 archiver.py`
|
||||||
|
|
||||||
A sample systemd service file is included.
|
### Viewer
|
||||||
|
|
||||||
|
1. `sudo mkdir /etc/secrets`
|
||||||
|
2. `sudo chown -R root:root /etc/secrets`
|
||||||
|
3. Generate your password:
|
||||||
|
```shell
|
||||||
|
echo -n "<your password>" | base64
|
||||||
|
```
|
||||||
|
4. Edit `/etc/secrets/imapviewer` with this content:
|
||||||
|
```shell
|
||||||
|
SQLITE3_DB=<path to your sqlite3 database>
|
||||||
|
HTTP_AUTH_PASS=<your encoded password>
|
||||||
|
```
|
||||||
|
5. `sudo chmod 600 /etc/secrets/imapviewer`
|
||||||
|
6. Reload and start the service.
|
||||||
|
|
||||||
|
Sample systemd services file is included.
|
||||||
|
|
||||||
## To Do
|
## To Do
|
||||||
|
|
||||||
- [ ] Fix subject decoding. Some character sets aren't detected correctly.
|
- [ ] Fix subject decoding. Some character sets aren't detected correctly.
|
||||||
- [ ] Sync Gmail categories as folders.
|
|
||||||
- [ ] In the viewer, format the raw email to something viewable.
|
- [ ] In the viewer, format the raw email to something viewable.
|
||||||
|
|
|
@ -3,4 +3,5 @@ chardet==5.2.0
|
||||||
humanize==4.9.0
|
humanize==4.9.0
|
||||||
mmh3==4.1.0
|
mmh3==4.1.0
|
||||||
flask==3.0.2
|
flask==3.0.2
|
||||||
python-magic==0.4.27
|
python-magic==0.4.27
|
||||||
|
flask_httpauth==4.8.0
|
25
server.py
25
server.py
|
@ -1,3 +1,4 @@
|
||||||
|
import base64
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
@ -6,11 +7,31 @@ from pathlib import Path
|
||||||
|
|
||||||
import magic
|
import magic
|
||||||
from flask import Flask, render_template, send_from_directory
|
from flask import Flask, render_template, send_from_directory
|
||||||
|
from flask_httpauth import HTTPBasicAuth
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
if not os.environ.get('SQLITE3_DB'):
|
if not os.environ.get('SQLITE3_DB'):
|
||||||
raise Exception('SQLITE3_DB not set.')
|
raise Exception('SQLITE3_DB not set.')
|
||||||
|
if not os.environ.get('HTTP_AUTH_PASS'):
|
||||||
|
raise Exception('HTTP_AUTH_PASS not set.')
|
||||||
|
|
||||||
|
try:
|
||||||
|
user_password = base64.b64decode(os.environ.get('HTTP_AUTH_PASS')).decode().replace('\n', '')
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
raise Exception('Cannot decode password. Is it base64 encoded?')
|
||||||
|
|
||||||
|
auth = HTTPBasicAuth()
|
||||||
|
users = {
|
||||||
|
'admin': user_password,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@auth.get_password
|
||||||
|
def get_pw(username):
|
||||||
|
if username in users:
|
||||||
|
return users.get(username)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_db_connection():
|
def get_db_connection():
|
||||||
|
@ -24,6 +45,7 @@ def dict_from_row(row):
|
||||||
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
|
@auth.login_required
|
||||||
def index():
|
def index():
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
folders = conn.execute('SELECT name, table_name FROM folders_mapping').fetchall()
|
folders = conn.execute('SELECT name, table_name FROM folders_mapping').fetchall()
|
||||||
|
@ -36,6 +58,7 @@ def index():
|
||||||
|
|
||||||
|
|
||||||
@app.route('/folder/<table_name>')
|
@app.route('/folder/<table_name>')
|
||||||
|
@auth.login_required
|
||||||
def folder(table_name):
|
def folder(table_name):
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
emails = conn.execute(f'SELECT * FROM {table_name} ORDER BY timestamp DESC').fetchall()
|
emails = conn.execute(f'SELECT * FROM {table_name} ORDER BY timestamp DESC').fetchall()
|
||||||
|
@ -47,6 +70,7 @@ def folder(table_name):
|
||||||
|
|
||||||
|
|
||||||
@app.route('/email/<table_name>/<id>')
|
@app.route('/email/<table_name>/<id>')
|
||||||
|
@auth.login_required
|
||||||
def email(table_name, id):
|
def email(table_name, id):
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
email = conn.execute(f'SELECT * FROM {table_name} WHERE id = ?', (id,)).fetchone()
|
email = conn.execute(f'SELECT * FROM {table_name} WHERE id = ?', (id,)).fetchone()
|
||||||
|
@ -58,6 +82,7 @@ def email(table_name, id):
|
||||||
|
|
||||||
|
|
||||||
@app.route('/attachments/<path:filename>')
|
@app.route('/attachments/<path:filename>')
|
||||||
|
@auth.login_required
|
||||||
def download_file(filename):
|
def download_file(filename):
|
||||||
mimetype = magic.from_file(str(Path('attachments', filename)), mime=True)
|
mimetype = magic.from_file(str(Path('attachments', filename)), mime=True)
|
||||||
return send_from_directory('attachments', filename, mimetype=mimetype)
|
return send_from_directory('attachments', filename, mimetype=mimetype)
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
[Unit]
|
||||||
|
Description=IMAP archiver service timer
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=15min
|
||||||
|
OnUnitActiveSec=12h
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
|
@ -0,0 +1,15 @@
|
||||||
|
[Unit]
|
||||||
|
Description=IMAP archiver viewer
|
||||||
|
Wants=network-online.target
|
||||||
|
After=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=emailsync
|
||||||
|
Group=emailsync
|
||||||
|
EnvironmentFile=/etc/secrets/imapviewer
|
||||||
|
ExecStart=/srv/email/imap-archiver/venv/bin/python3 /srv/email/imap-archiver/server.py
|
||||||
|
SyslogIdentifier=imap-viewer
|
||||||
|
Restart=on-failure
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
Loading…
Reference in New Issue