Skip to content

operator64/rongbox

Repository files navigation

Rongbox

Read-only Online-Viewer für eine große mbox-Datei aus Google Takeout. Eine Person, ein VPS, ein Single-File-Backup. Indexiert mehrere GB streamend, sucht über FTS5, rendert HTML-Mails in einer sandboxed iframe ohne Tracking.

┌────────┐   upload .mbox.gz   ┌────────┐   arq job   ┌────────┐   FTS5   ┌────────┐
│ Caddy  │───────────────────► │FastAPI │───────────► │ worker │────────► │SQLite  │
│ (HTTPS)│                     │  app   │             │  +Redis│          │ + WAL  │
└────────┘ ◄───────────────────└────────┘ ◄───────────└────────┘          └────────┘
                  HTMX + Alpine                                          attachments/

Was es kann

  • Streaming-Indexierung von .mbox und .mbox.gz (bis 10 GB komprimiert)
  • Volltextsuche über subject, from, to, body mit FTS5 (snippet-Highlighting)
  • Erweiterte Operatoren: from: to: cc: bcc: subject:"…" has:attachment is:unread label: before:YYYY-MM-DD after:YYYY-MM-DD -keyword
  • Konversations-/Thread-Ansicht über Message-ID/References
  • Anhänge: SHA-256-dedupliziert auf der Disk, signierte Download-Links, Vorschau über separate Routen
  • HTML-Mails in <iframe sandbox="allow-same-origin"> mit eigener CSP via srcdoc, Bilder per Proxy (kein direkter Client→Tracker)
  • Login mit argon2id, Session-Cookies (httpOnly, secure, sameSite=strict), optionale TOTP-2FA, Bruteforce-Lockout (5/15 min), CSRF-Schutz
  • Statistik (Mails/Monat, Top-Sender, Top-Domains)
  • Saved Searches, CSV-Export der Treffer
  • Tastatur-Shortcuts: j/k, o/Enter, u, /, g i
  • Dark Mode + system preference, mobile-responsive
  • Audit-Log (für Admin-Nutzer einsehbar)

Setup mit Docker (empfohlen)

git clone <repo> rongbox && cd rongbox
cp .env.example .env

# 64-Byte Hex-Secret in .env eintragen
python -c "import secrets; print('RONGBOX_SECRET_KEY=' + secrets.token_hex(32))" >> .env

# Caddyfile: 'mbox.example.com' durch deinen Hostnamen ersetzen
$EDITOR Caddyfile

docker compose up -d --build
docker compose exec app python -m app.cli create-user yourname --admin

Beim ersten Start kümmert sich der App-Container um die Schemamigrationen, der Worker-Container um Hintergrund-Indexierung, Caddy um automatisches HTTPS via Let's Encrypt.

Lokal entwickeln

python3.12 -m venv .venv && source .venv/Scripts/activate   # Windows: bash
pip install -e '.[dev]'

# .env-Werte exportieren oder vor jedem Befehl setzen
export RONGBOX_SECRET_KEY=$(python -c 'import secrets; print(secrets.token_hex(32))')
export RONGBOX_DB_PATH=./data/db/rongbox.sqlite3
export RONGBOX_ATTACHMENT_DIR=./data/attachments
export RONGBOX_UPLOAD_DIR=./data/uploads
export RONGBOX_REDIS_URL=redis://localhost:6379/0
export RONGBOX_COOKIE_SECURE=false   # nur lokal über http

python -m app.cli init-db
python -m app.cli create-user demo

# Web (Port 8000) und Worker brauchen jeweils einen Prozess
uvicorn app.main:app --reload --port 8000
arq app.worker.WorkerSettings

pytest führt 34 Tests aus (Parser, Suche, Auth) — siehe tests/.

Erste Schritte im UI

  1. Bei https://<host>/ einloggen (Username + Passwort, später ggf. TOTP).
  2. Über /upload eine Google-Takeout-mbox hochladen. Während des Uploads streamt die Datei direkt in den persistenten Volume data/uploads.
  3. Sobald der Upload abgeschlossen ist, übernimmt der arq-Worker die Indexierung. Der Fortschritt (Mails/sec, ETA, %) wird über HTMX-Polling live aktualisiert.
  4. Suche über die Topbar — z. B. from:rechnung@ has:attachment after:2023-01-01.

Bedienung der Suche

Operator Beispiel
Volltext rechnung 2023
Phrase "quarterly report"
Negation report -spam
Absender from:bob@example.com
Empfänger to:alice cc:carol bcc:dan
Betreff subject:"steuer 2024"
Datum after:2023-01-01 before:2024-07-01
Anhang has:attachment
Ungelesen is:unread
Gmail-Label label:Inbox label:"Wichtig, sehr"

CLI

python -m app.cli init-db                       # Schema sicherstellen
python -m app.cli create-user <name>            # initialer User (--admin für Audit-Zugriff)
python -m app.cli set-password <name>
python -m app.cli reset-2fa <name>
python -m app.cli list-users
python -m app.cli delete-user <name>
python -m app.cli optimize                      # PRAGMA optimize + ANALYZE + FTS rebuild
python -m app.cli index <pfad>.mbox(.gz)        # synchron indexieren, ohne Redis/Worker

index eignet sich besonders zum lokalen Testen oder wenn du eine sehr große Datei einmalig auf einem starken Host vorindexieren willst, bevor die fertige SQLite-DB auf den eigentlichen Server kopiert wird. --delete-raw löscht die Quelldatei nach erfolgreichem Index, --no-optimize überspringt das abschließende PRAGMA optimize.

Der Worker-Container hat dasselbe Image, docker compose exec app … reicht.

Datenarchitektur

  • data/db/rongbox.sqlite3 — Single-File-DB im WAL-Modus. Enthält messages (mit FTS5-Spiegel messages_fts), attachments, users, uploads, audit_log, saved_searches, login_attempts.
  • data/attachments/<sha256-prefix>/<sha256> — Anhänge auf der Disk, dedupliziert über SHA-256. Datenbank speichert nur den Pfad.
  • data/uploads/ — Lebenslauf der hochgeladenen Originaldatei. Wird nach erfolgreichem Index gelöscht, wenn keep_raw=false (Default).

Production-Deploy (Schritt-für-Schritt)

Empfohlene Variante: ein Linux-VPS, Docker mit dem mitgelieferten Caddy als Reverse-Proxy (automatisches Let's Encrypt). Alles, was du nicht in der .env setzt, hat einen vernünftigen Default.

# 1. Klonen
git clone https://github.com/operator64/rongbox.git /opt/rongbox
cd /opt/rongbox

# 2. .env erzeugen
cp .env.example .env
echo "RONGBOX_SECRET_KEY=$(./scripts/generate_secret.sh)" >> .env
$EDITOR .env                       # Cookie-Secure auf true lassen (HTTPS)

# 3. Caddyfile auf eigenen Hostnamen anpassen
$EDITOR Caddyfile                  # mbox.example.com → mbox.dein.host

# 4. Pre-flight-Checks
./scripts/preflight.sh

# 5. Erststart
docker compose up -d --build
docker compose exec app python -m app.cli create-user $USER --admin

Variante: eigener Reverse-Proxy

Wenn auf dem Host bereits nginx oder Traefik läuft, Caddy weglassen und den App-Container nur an 127.0.0.1:8000 binden:

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Dann den eigenen Proxy auf 127.0.0.1:8000 zeigen. Vorlage liegt in deploy/nginx.conf. Wichtig dabei: client_max_body_size und proxy_read_timeout müssen großzügig sein, sonst killt der Proxy lange Uploads.

Variante: ohne Docker (systemd)

Auf einem Host mit Python 3.12+ und Redis verfügbar:

sudo useradd --system --home /opt/rongbox --create-home --shell /usr/sbin/nologin rongbox
sudo -u rongbox git clone https://github.com/operator64/rongbox.git /opt/rongbox
cd /opt/rongbox
sudo -u rongbox python3 -m venv .venv
sudo -u rongbox .venv/bin/pip install .

sudo install -d -o rongbox -g rongbox /var/lib/rongbox/{db,attachments,uploads}
sudo cp .env.example /etc/rongbox.env
sudo chmod 600 /etc/rongbox.env
sudo $EDITOR /etc/rongbox.env       # Pfade auf /var/lib/rongbox/* zeigen lassen

sudo cp deploy/systemd/rongbox-*.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now redis rongbox-app rongbox-worker
sudo -u rongbox /opt/rongbox/.venv/bin/python -m app.cli create-user $USER --admin

Updates

cd /opt/rongbox
./scripts/backup.sh ./backups
git pull
docker compose up -d --build

Die Schemamigration läuft im Lifespan-Handler beim ersten Start automatisch. Kein zusätzlicher Migrations-Schritt.

Backup & Restore

# Konsistentes Online-Backup (egal ob App läuft, dank WAL):
./scripts/backup.sh ./backups

# Restore eines Snapshots in eine leere Installation:
docker compose stop app worker
./scripts/restore.sh ./backups/rongbox-2026-04-26T07-00-00Z.tar.gz
docker compose up -d

scripts/backup.sh macht intern sqlite3 .backup + tar über das gesamte Anhangs-Verzeichnis. Eine Cron-Zeile reicht zum täglichen Snapshot:

0 3 * * * cd /opt/rongbox && ./scripts/backup.sh /var/backups/rongbox >>/var/log/rongbox-backup.log 2>&1

Sicherheit

  • HTML-Mails werden bereits beim Indexieren über bleach sanitisiert (Whitelist-Tags, CSS-Sanitizer, Skripts/Eventhandler/javascript:-URLs raus). Das Rendering passiert in einem <iframe sandbox="allow-same-origin"> mit eigener Content-Security-Policy per srcdoc. allow-scripts ist bewusst nicht gesetzt.
  • Externe Bilder sind standardmäßig blockiert. Pro-Mail-Toggle „Bilder laden" leitet alle externen Ressourcen über den lokalen /imgproxy, der serverseitig fetcht (max. 5 MB, nur image/*, kein Redirect-Loop, 10 s Timeout).
  • Anhänge werden ausschließlich mit Content-Disposition: attachment und X-Content-Type-Options: nosniff ausgeliefert; Inline-Cid-Bilder sind die einzige Ausnahme (mit Disposition: inline ausschließlich für die iframe hinter Signed Token).
  • CSP der App verbietet Drittparteien (default-src 'self', frame-ancestors 'none', form-action 'self').
  • Login ist mit konstantzeitigem Hashvergleich, Password-Hashing über argon2id, Session-Cookies httpOnly+secure+sameSite=strict gesichert. Bruteforce: 5 Fehlversuche pro (username, IP) → 15 min Lockout.
  • CSRF auf allen state-changing Endpoints (Doppel-Cookie-Pattern, HTMX setzt den Header automatisch via app.js).

Performance

  • Indexierung: ein 7 GB gzipped mbox indexiert auf einem 4-Core/8 GB-VPS in unter 6 Stunden. Im lokalen Smoke-Test schafft die Pipeline ~2 000 Mails/s (kleine Mails ohne Anhänge, NVMe-SSD).
  • Suche: FTS5 über die typische Mailbox antwortet in deutlich unter 300 ms bei mehreren hunderttausend Mails (PRAGMA optimize + ANALYZE nach Index).
  • Speicher: Streaming-Parser hält maximal eine Mail im RAM; selbst 100 MB-Anhänge schlagen nicht durch (siehe tests/test_parser.py:: test_large_attachment_does_not_explode_memory).

Bekannte Limitierungen

  • Mehrfach-Escape (>>From ) im Body wird nicht restauriert; einfach-Escape (>From ) ja. Dies entspricht der mboxrd-Spezifikation.
  • mbox ohne jeden From -Separator wird vollständig übersprungen (kein Fallback auf einzelne Mail).
  • Inkrementelle Re-Indexierung (mehrere Takeout-Dateien zusammenführen) ist über separate Uploads möglich; eine Deduplikation gleicher Message-ID über Uploads hinweg ist nicht aktiv — doppelte Message-IDs landen als separate Zeilen, was bewusst gewählt ist (Takeout liefert manchmal versehentlich Dubletten, deren Inhalt sich aber unterscheiden kann).
  • Suche im Body deckt aktuell nur body_text ab, nicht den HTML-Quelltext. Für die meisten Mails ist das identisch (Plaintext-Alternative oder HTML→Text-Fallback während des Index).
  • Keine Volltext-Suche in PDF-Anhängen.

Lizenz

MIT.

About

Read-only online viewer for Google Takeout mbox archives — FastAPI + SQLite FTS5

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors