Guard against missing Mail-Graveyard DB
This commit is contained in:
parent
d4d43fe672
commit
29208b7a41
4 changed files with 127 additions and 3 deletions
|
|
@ -23,9 +23,14 @@ an, nicht hier.
|
||||||
## 3. Starten (Web)
|
## 3. Starten (Web)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./mail-graveyard # lauscht auf 127.0.0.1:8087
|
./mail-graveyard --init-db # nur beim ersten Start: DB aus db_path anlegen
|
||||||
|
./mail-graveyard # danach normal, lauscht auf 127.0.0.1:8087
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Fehlt die DB-Datei spaeter, startet Mail-Graveyard bewusst nicht mehr still mit
|
||||||
|
einer leeren Datenbank. Dann `db_path` pruefen oder nur fuer eine echte
|
||||||
|
Neuinstallation erneut `--init-db` benutzen.
|
||||||
|
|
||||||
Browser auf `http://127.0.0.1:8087`, mit Admin-Login aus `config.json`.
|
Browser auf `http://127.0.0.1:8087`, mit Admin-Login aus `config.json`.
|
||||||
Oberfläche im Outlook-2013-Look: Reiter **Postfächer** (Konten anlegen: Quelle
|
Oberfläche im Outlook-2013-Look: Reiter **Postfächer** (Konten anlegen: Quelle
|
||||||
alt → Ziel neu, Sicherheits-Modus `tls`/`starttls`/`none`, „Test" prüft beide
|
alt → Ziel neu, Sicherheits-Modus `tls`/`starttls`/`none`, „Test" prüft beide
|
||||||
|
|
|
||||||
|
|
@ -64,10 +64,13 @@ type AppUser struct {
|
||||||
// Zweiter Lauf kopiert nur das Delta.
|
// Zweiter Lauf kopiert nur das Delta.
|
||||||
// jobs(account_id, started, finished, -- Lauf-Historie fuer den Fortschritt
|
// jobs(account_id, started, finished, -- Lauf-Historie fuer den Fortschritt
|
||||||
// total, done, errors, state) im Browser.
|
// total, done, errors, state) im Browser.
|
||||||
func ConnectDB() error {
|
func ConnectDB(initDB bool) error {
|
||||||
if Cfg.DBPath == "" {
|
if Cfg.DBPath == "" {
|
||||||
Cfg.DBPath = "mail-graveyard.db"
|
Cfg.DBPath = "mail-graveyard.db"
|
||||||
}
|
}
|
||||||
|
if err := ensureDBFile(Cfg.DBPath, initDB); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
db, err := sql.Open("sqlite", Cfg.DBPath)
|
db, err := sql.Open("sqlite", Cfg.DBPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -167,6 +170,27 @@ func ConnectDB() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ensureDBFile(path string, initDB bool) error {
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err == nil {
|
||||||
|
if info.IsDir() {
|
||||||
|
return fmt.Errorf("DB %q ist ein Verzeichnis", path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !initDB {
|
||||||
|
return fmt.Errorf("DB %q existiert nicht -- mit --init-db neu anlegen oder db_path pruefen", path)
|
||||||
|
}
|
||||||
|
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return f.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func ensureAppUserColumns() error {
|
func ensureAppUserColumns() error {
|
||||||
hasDisplayName := false
|
hasDisplayName := false
|
||||||
rows, err := DB.Query(`PRAGMA table_info(app_users)`)
|
rows, err := DB.Query(`PRAGMA table_info(app_users)`)
|
||||||
|
|
|
||||||
94
db-rename-brief.md
Normal file
94
db-rename-brief.md
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
# Codex-Brief — DB-Namen-Altlast beseitigen (stiller Daten-GAU)
|
||||||
|
|
||||||
|
**Priorität: vor der ersten echten `@dr-gold.de`-Migration.** Das ist kein
|
||||||
|
Kosmetikproblem.
|
||||||
|
|
||||||
|
## Der Befund
|
||||||
|
|
||||||
|
| Stelle | `db_path` |
|
||||||
|
|---|---|
|
||||||
|
| `config.json.example` | `mail-graveyard.db` |
|
||||||
|
| `INSTALL.md` (`cp config.json.example config.json`) | → `mail-graveyard.db` |
|
||||||
|
| Code-Default in `ConnectDB` (02-database.go) | `mail-graveyard.db` |
|
||||||
|
| **LIVE auf GoldPi** | **`/app/data/emailforwarder.db`** ← hier liegen die echten Daten |
|
||||||
|
|
||||||
|
Im `data/`-Verzeichnis liegen aktuell:
|
||||||
|
```
|
||||||
|
emailforwarder.db 380.928 <- die ECHTEN Daten
|
||||||
|
emailforwarder.db-wal 4.124.152 <- ACHTUNG: groesser als die DB selbst!
|
||||||
|
emailforwarder.db-shm 32.768
|
||||||
|
mail-graveyard.db 0 <- Leiche
|
||||||
|
email-forwarder.db 0 <- Leiche
|
||||||
|
```
|
||||||
|
|
||||||
|
**Der Schaden, wenn der Default je greift** (jemand folgt der eigenen
|
||||||
|
INSTALL.md, oder `db_path` fehlt in einer Config): SQLite legt klaglos eine
|
||||||
|
**frische, leere DB** an. Konten weg, `copied`-Historie weg. Der Watcher sieht
|
||||||
|
eine leere `copied`-Tabelle und **kopiert jede Mail erneut in die
|
||||||
|
Ziel-Postfaecher** → **Dubletten quer ueber alle ~20 echten dr-gold-Postfaecher**,
|
||||||
|
lautlos, ohne einen einzigen Fehler im Log.
|
||||||
|
|
||||||
|
## Teil A — Die Daten umziehen (einmalige Ops-Aktion)
|
||||||
|
|
||||||
|
**Ist-Zustand als Baseline (muss danach identisch sein; Stand vor Codex-Umzug):**
|
||||||
|
`accounts=19`, `copied=215`, `app_users=3`, `jobs=294`
|
||||||
|
|
||||||
|
Reihenfolge ist wichtig — **das WAL ist der gefaehrliche Teil**:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
APP=/mnt/DATA/Containers/Test-Container/Mail-Graveyard-Pod
|
||||||
|
D=$APP/data
|
||||||
|
|
||||||
|
# 1. BEIDE Container stoppen -> kein Schreiber mehr auf der DB
|
||||||
|
podman stop mail-graveyard mail-graveyard-watch
|
||||||
|
|
||||||
|
# 2. Backup, bevor irgendwas angefasst wird (inkl. WAL/SHM!)
|
||||||
|
cp -a $D/emailforwarder.db $D/BACKUP-emailforwarder.db
|
||||||
|
cp -a $D/emailforwarder.db-wal $D/BACKUP-emailforwarder.db-wal
|
||||||
|
|
||||||
|
# 3. WAL in die Haupt-DB falten. OHNE DAS gehen 4 MB Daten verloren.
|
||||||
|
sqlite3 $D/emailforwarder.db "PRAGMA wal_checkpoint(TRUNCATE);"
|
||||||
|
|
||||||
|
# 4. Gegenpruefen, dass alles noch da ist
|
||||||
|
sqlite3 $D/emailforwarder.db "SELECT
|
||||||
|
(SELECT COUNT(*) FROM accounts), (SELECT COUNT(*) FROM copied),
|
||||||
|
(SELECT COUNT(*) FROM app_users), (SELECT COUNT(*) FROM jobs);"
|
||||||
|
# -> MUSS 19|215|3|294 liefern. Wenn nicht: STOPP, nichts weiter tun.
|
||||||
|
|
||||||
|
# 5. Erst jetzt umbenennen (ueberschreibt die 0-Byte-Leiche)
|
||||||
|
mv -f $D/emailforwarder.db $D/mail-graveyard.db
|
||||||
|
rm -f $D/emailforwarder.db-wal $D/emailforwarder.db-shm $D/email-forwarder.db
|
||||||
|
|
||||||
|
# 6. Config umstellen
|
||||||
|
# $APP/config.json: "db_path": "/app/data/mail-graveyard.db"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Teil B — Die Ursache beseitigen (Code + Repo)
|
||||||
|
|
||||||
|
1. `deploy/goldpi-test-container/mail-graveyard-pod/config.json` **und**
|
||||||
|
`config.json.example`: `db_path` auf `mail-graveyard.db` — damit Doku, Deploy
|
||||||
|
und Live endlich dasselbe sagen.
|
||||||
|
2. **Kein stilles Anlegen mehr.** Das ist die eigentliche Wurzel: `ConnectDB`
|
||||||
|
erzeugt heute klaglos eine leere DB, wenn die Datei fehlt. Bau eine
|
||||||
|
Sicherung ein:
|
||||||
|
- Wenn die DB-Datei **nicht existiert**, nur dann anlegen, wenn explizit
|
||||||
|
`--init-db` uebergeben wurde. Sonst: **laut scheitern** mit
|
||||||
|
`DB %q existiert nicht — mit --init-db neu anlegen oder db_path pruefen`
|
||||||
|
und Exit ≠ 0.
|
||||||
|
- Damit wird aus einem stillen Daten-GAU ein sofort sichtbarer Startfehler.
|
||||||
|
Dasselbe Prinzip wie beim Watch-Fail-Fast: **permanenter Konfigfehler muss
|
||||||
|
schreien, nicht schweigen.**
|
||||||
|
|
||||||
|
## Abnahme
|
||||||
|
|
||||||
|
1. Container wieder hoch, dann in der DB pruefen:
|
||||||
|
`accounts=19`, `copied=215`, `app_users=3`, `jobs=289` — **identisch**.
|
||||||
|
2. **Der entscheidende Test:** Der erste Watcher-Lauf nach dem Umzug muss
|
||||||
|
`copied=0` melden (`total=215 copied=0 errors=0`).
|
||||||
|
→ Meldet er stattdessen `copied=215`, ist die Dedup-Historie verloren
|
||||||
|
gegangen und er schreibt gerade Dubletten ins Ziel. **Sofort stoppen** und
|
||||||
|
das Backup aus Schritt 2 zurueckspielen.
|
||||||
|
3. Login in der Web-UI funktioniert weiter (die 3 Benutzer sind mitgezogen).
|
||||||
|
4. `go test ./...` gruen.
|
||||||
|
|
||||||
|
Ich (Claude) verifiziere danach Punkt 1–3 unabhaengig auf GoldPi.
|
||||||
3
main.go
3
main.go
|
|
@ -23,12 +23,13 @@ func main() {
|
||||||
watchInterval := flag.Duration("watch-interval", 60*time.Second, "Pause zwischen Watch-Durchlaeufen")
|
watchInterval := flag.Duration("watch-interval", 60*time.Second, "Pause zwischen Watch-Durchlaeufen")
|
||||||
seedAccount := flag.String("seed-account", "", "Konto aus lokaler JSON-Datei in die DB schreiben (nur CLI/Test)")
|
seedAccount := flag.String("seed-account", "", "Konto aus lokaler JSON-Datei in die DB schreiben (nur CLI/Test)")
|
||||||
checkAccount := flag.String("check-account", "", "Konto-Logins pruefen und INBOX zaehlen, ohne zu kopieren")
|
checkAccount := flag.String("check-account", "", "Konto-Logins pruefen und INBOX zaehlen, ohne zu kopieren")
|
||||||
|
initDB := flag.Bool("init-db", false, "SQLite-DB neu anlegen, falls db_path noch nicht existiert")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if err := backend.LoadConfig("config.json"); err != nil {
|
if err := backend.LoadConfig("config.json"); err != nil {
|
||||||
log.Fatalf("config: %v", err)
|
log.Fatalf("config: %v", err)
|
||||||
}
|
}
|
||||||
if err := backend.ConnectDB(); err != nil {
|
if err := backend.ConnectDB(*initDB); err != nil {
|
||||||
log.Fatalf("database: %v", err)
|
log.Fatalf("database: %v", err)
|
||||||
}
|
}
|
||||||
if err := backend.InitAuth(); err != nil {
|
if err := backend.InitAuth(); err != nil {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue