Files
archivmail/features/PROJ-15-cli-import-export.md
T
sysops bb963a796f security: Zufallspasswörter beim Erststart, kryptographisch sichere JTI-Generierung
- 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>
2026-03-17 01:19:24 +01:00

204 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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_