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/
- Streaming-Indexierung von
.mboxund.mbox.gz(bis 10 GB komprimiert) - Volltextsuche über
subject,from,to,bodymit FTS5 (snippet-Highlighting) - Erweiterte Operatoren:
from:to:cc:bcc:subject:"…"has:attachmentis:unreadlabel:before:YYYY-MM-DDafter: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 viasrcdoc, 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)
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 --adminBeim 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.
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.WorkerSettingspytest führt 34 Tests aus (Parser, Suche, Auth) — siehe tests/.
- Bei
https://<host>/einloggen (Username + Passwort, später ggf. TOTP). - Über
/uploadeine Google-Takeout-mbox hochladen. Während des Uploads streamt die Datei direkt in den persistenten Volumedata/uploads. - Sobald der Upload abgeschlossen ist, übernimmt der arq-Worker die Indexierung. Der Fortschritt (Mails/sec, ETA, %) wird über HTMX-Polling live aktualisiert.
- Suche über die Topbar — z. B.
from:rechnung@ has:attachment after:2023-01-01.
| 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" |
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/Workerindex 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.
data/db/rongbox.sqlite3— Single-File-DB im WAL-Modus. Enthältmessages(mit FTS5-Spiegelmessages_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, wennkeep_raw=false(Default).
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 --adminWenn 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 -dDann 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.
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 --admincd /opt/rongbox
./scripts/backup.sh ./backups
git pull
docker compose up -d --buildDie Schemamigration läuft im Lifespan-Handler beim ersten Start automatisch. Kein zusätzlicher Migrations-Schritt.
# 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 -dscripts/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- HTML-Mails werden bereits beim Indexieren über
bleachsanitisiert (Whitelist-Tags, CSS-Sanitizer, Skripts/Eventhandler/javascript:-URLs raus). Das Rendering passiert in einem<iframe sandbox="allow-same-origin">mit eigenerContent-Security-Policypersrcdoc.allow-scriptsist 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, nurimage/*, kein Redirect-Loop, 10 s Timeout). - Anhänge werden ausschließlich mit
Content-Disposition: attachmentundX-Content-Type-Options: nosniffausgeliefert; Inline-Cid-Bilder sind die einzige Ausnahme (mitDisposition: inlineausschließ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).
- 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+ANALYZEnach 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).
- 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_textab, 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.
MIT.