bb963a796f
- seedDefaultUsers: generiert kryptographisch zufällige Passwörter (crypto/rand) statt hartkodiertes "archivmailrockz" — Passwörter werden einmalig im Terminal angezeigt und können danach nicht wiederhergestellt werden - generateJTI: verwendet crypto/rand (16 Byte, hex) statt time.UnixNano XOR deadbeef Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
204 lines
7.6 KiB
Markdown
204 lines
7.6 KiB
Markdown
# PROJ-15: CLI Import & Export
|
||
|
||
## Status: Deployed
|
||
**Created:** 2026-03-13
|
||
**Last Updated:** 2026-03-17
|
||
|
||
## 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 <command>`
|
||
- 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 <command> [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_
|