# PROJ-15: CLI Import & Export ## Status: In Review **Created:** 2026-03-13 **Last Updated:** 2026-03-13 ## Dependencies - Requires: PROJ-5 (Speicherung & Indexierung) – Import nutzt Storage Coordinator - Requires: PROJ-1 (Authentifizierung) – CLI läuft als Systembenutzer `archivmail`, kein Web-Login ## Hinweis Die CLI läuft direkt auf dem Server als Systembenutzer `archivmail` – kein Web-Login, kein API-Key. Zugriff über den gleichen Storage Coordinator wie der Daemon. Gedacht für automatisierte Skripte, Cron-Jobs und administrative Bulk-Operationen. ## User Stories - Als Systemadministrator möchte ich EML/MBOX-Dateien per CLI importieren, damit ich Bulk-Importe skriptbasiert automatisieren kann. - Als Systemadministrator möchte ich E-Mails per CLI exportieren (EML/MBOX), damit ich Sicherungen oder Migrationen durchführen kann. - Als Systemadministrator möchte ich Import/Export mit Pfadangabe starten, damit ich Quell- und Zielverzeichnisse flexibel festlegen kann. - Als System möchte ich Import-Fortschritt und Ergebnis auf stdout ausgeben, damit Skripte den Status auswerten können. ## Acceptance Criteria ### Import - [x] `archivmail import --file /pfad/zu/datei.eml` – einzelne EML importieren - [x] `archivmail import --file /pfad/zu/archiv.mbox` – MBOX importieren - [x] `archivmail import --dir /pfad/zum/verzeichnis/` – alle EML-Dateien in einem Verzeichnis importieren (rekursiv optional: `--recursive`) - [x] Fortschrittsausgabe auf stdout (eine Zeile pro 100 Mails) - [x] Exit-Code 0 bei Erfolg, 1 bei Fehler - [x] Duplikate werden übersprungen (SHA256-Dedup im Store), kein Fehler - [x] `--dry-run` Flag: zeigt was importiert würde ohne tatsächlich zu speichern ### Export - [x] `archivmail export --out /pfad/ziel/` – alle Mails als EML-Dateien exportieren - [x] `archivmail export --out /pfad/archiv.mbox` – alle Mails als MBOX exportieren - [x] `archivmail export --from alice@firma.de --out /pfad/` – Filter nach Absender - [x] `archivmail export --date-from 2024-01-01 --date-to 2024-12-31 --out /pfad/` – Filter nach Datum - [x] `archivmail export --query "Rechnung" --out /pfad/` – Filter per Volltext-Suche (Xapian) - [x] Exportierte Mails als Klartext EML auf Disk - [x] `--format eml` (Standard) oder `--format mbox` ### Allgemein - [x] CLI läuft als Systembenutzer `archivmail` – Config aus `/etc/archivmail/config.yml` - [x] Fehler werden auf stderr ausgegeben - [x] `archivmail help` zeigt Übersicht aller Befehle - [x] `archivmail version` zeigt Version ## Edge Cases - Verzeichnis beim Import enthält keine EML-Dateien → Hinweis + Exit-Code 0 - Zieldatei beim Export bereits vorhanden → Fehler mit `--force` Flag zum Überschreiben - Kein Lese-/Schreibrecht auf Pfad → klare Fehlermeldung auf stderr - Import unterbrochen (Ctrl+C) → partiell importierte Mails werden gespeichert, kein Rollback (Archiv ist append-only) - Export bei leerem Archiv → leeres Verzeichnis / leere MBOX, Exit-Code 0 ## Technical Requirements - CLI ist Teil desselben Go-Binaries (`archivmail`) – Subcommands via `archivmail ` - Zugriff auf Storage Coordinator direkt (kein HTTP-Umweg über den laufenden Daemon) - Key-Datei muss lesbar sein (`/etc/archivmail/keyfile`, `chmod 400`, Owner `archivmail`) - Kann parallel zum laufenden Daemon betrieben werden (Xapian WritableDatabase: Lock beachten) - Strukturierte Ausgabe optional: `--json` Flag für maschinenlesbare Ausgabe --- ## Tech Design (Solution Architect) ### CLI-Struktur ``` archivmail [flags] Commands: import E-Mails importieren (EML, MBOX, Verzeichnis) export E-Mails exportieren (EML, MBOX) version Version anzeigen help Hilfe anzeigen archivmail import --file /pfad/datei.eml oder .mbox --dir /pfad/verzeichnis/ --recursive Unterverzeichnisse einschließen (mit --dir) --dry-run Simulation ohne Speichern --json Maschinenlesbare Ausgabe (JSON) archivmail export --out /pfad/ziel/ oder /pfad/archiv.mbox (Pflicht) --format eml (Standard) | mbox --from Absender-Filter --to Empfänger-Filter --date-from Datum von (ISO 8601: 2024-01-01) --date-to Datum bis (ISO 8601: 2024-12-31) --query Volltext-Suche (Xapian QueryParser) --force Zieldatei überschreiben --json Maschinenlesbare Ausgabe (JSON) ``` ### Komponentenstruktur ``` archivmail (Go-Binary) │ ├── main.go ← Subcommand-Router (import / export / ...) │ ├── cmd/import.go │ ├── Flag-Parsing │ ├── Dateityp-Erkennung (.eml / .mbox / Verzeichnis) │ ├── EML-Parser │ ├── MBOX-Parser (zeilenweise) │ └── → Storage Coordinator (PROJ-5, direkt, kein HTTP) │ └── cmd/export.go ├── Flag-Parsing ├── Filter-Builder (from, to, date, query) ├── → Xapian ReadonlyDatabase (Suche/Filter) ├── → PostgreSQL Metadaten-Lookup ├── → .m-Datei lesen + AES-256-GCM entschlüsseln └── Schreiben als EML-Dateien oder MBOX ``` ### Import-Fluss ``` $ archivmail import --dir /backup/mails/ --recursive Key laden aus /etc/archivmail/keyfile Verzeichnis scannen → 3.842 .eml-Dateien gefunden [████████░░] 2.150 / 3.842 (übersprungen: 12 Duplikate) Fertig: Importiert: 2.130 Übersprungen: 12 (Duplikate) Fehler: 0 ``` ### Export-Fluss ``` $ archivmail export --from alice@firma.de \ --date-from 2024-01-01 \ --out /backup/export/ Key laden aus /etc/archivmail/keyfile Xapian: 847 Mails gefunden (Filter: from=alice, date>=2024-01-01) Exportiere nach /backup/export/ [████████████] 847 / 847 Fertig: Exportiert: 847 EML-Dateien Ziel: /backup/export/ ``` ### JSON-Ausgabe (--json Flag) ```json { "status": "done", "imported": 2130, "skipped": 12, "errors": 0, "duration_sec": 42 } ``` ### Xapian-Lock beim parallelen Betrieb ``` Daemon läuft (WritableDatabase hält Lock für Index-Worker) │ CLI export → ReadonlyDatabase → kein Lock-Konflikt ✓ CLI import → Storage Coordinator → WritableDatabase │ └── Lock bereits gehalten? → Warten (max. 30 Sek.) → dann Fehlermeldung: "Index locked by running daemon. Stop daemon or retry." ``` ### Technische Entscheidungen | Entscheidung | Begründung | |---|---| | **Gleiche Binary, Subcommands** | Kein separates CLI-Tool – `archivmail import` und `archivmail serve` teilen Code und Storage Coordinator | | **Direkter Speicherzugriff, kein HTTP** | CLI läuft als `archivmail`-User mit Dateisystem-Zugriff – kein laufender Daemon nötig für Import/Export | | **`--dry-run`** | Sicher testen ohne Daten zu verändern – wichtig für große Bulk-Imports | | **`--json` Flag** | Maschinenlesbar für Cron-Jobs, Monitoring-Skripte, Ansible-Playbooks | | **Exit-Codes** | 0 = Erfolg, 1 = Fehler – Standard für Shell-Skripting | | **Xapian ReadonlyDatabase für Export** | Export kann parallel zum Daemon laufen ohne Lock-Konflikte | ### Abhängigkeiten | Paket | Zweck | |---|---| | `github.com/spf13/cobra` | Subcommand-CLI-Framework | | Xapian CGo-Bindings | Volltext-Filter beim Export (bereits PROJ-5) | ## Implementation Notes - Subcommands in `cmd/archivmail/main.go` via `os.Args[1]` Router (kein cobra nötig) - `cmd_import.go`: EML + MBOX Import, `--file`, `--dir`, `--recursive`, `--dry-run`, `--json` - `cmd_export.go`: EML + MBOX Export, alle Filter, `--force`, `--json` - MBOX Parser in `pkg/mailparser/mbox.go` (`SplitMbox`) - MBOX Export mit korrektem `>From ` Escaping - Deployed auf `root@192.168.1.131`, Daemon läuft ## QA Test Results _To be added by /qa_ ## Deployment _To be added by /deploy_