rebreak-monorepo/docs/internal/MAIL_DAEMON_DEPLOYMENT.md
chahinebrini 5d6c322129 wip: KeyboardAwareSheet migrations + Snake/Tetris UI + iron.png + useMe live-update
Sheets via neuer KeyboardAwareSheet-Composable (in Modal pattern, auto-grow
mit Tastatur, paddingBottom-Lift): EditMail, AddDomain, CreateRoom, ConnectMail.
GameOverScreen behält Spring-Slide-In, nutzt RN Keyboard.addListener für Lift.

- KeyboardAwareSheet.tsx — universal modal with sheet-grow + keyboard-padding
- react-native-keyboard-controller installiert + KeyboardProvider in Root
- Snake: time + ScoreProgressBar + useSnakeSounds (haptic, audio TODO)
- Tetris: title weg, Buttons zentriert, kein Pressable mit style-fn
- DPad-Buttons 60→48, more bg, no scale
- useMe: pub-sub listener pattern für app-weite avatar/nickname-Updates
- dm.tsx: resolveAvatar wrap (iron.png-Warning)
- Mail-error-humanizer + locales

Recovery-Doc-Update in docs/internal/RECOVERY_LOG_2026-05-10.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 23:59:25 +02:00

211 lines
7.5 KiB
Markdown

# MAIL_DAEMON_DEPLOYMENT — Backyard Handoff
**Erstellt von:** Mo (Mail-Architektur-Agent)
**Datum:** 2026-05-09
**Status:** Bereit für Deployment — wartet auf Backyard-GO vom User
## Kontext
Der `rebreak-imap-idle` Daemon ist ein eigenständiger Node.js-Prozess.
Er hält pro aktivem MailConnection-DB-Eintrag eine persistente IMAP-IDLE-Session
und triggert bei neuer Mail sofort `/api/mail/scan-internal` statt auf den 30min-Cron zu warten.
Der Daemon liegt unter `backend/imap-idle/index.mjs` und hat seine eigene `package.json`.
Er ist KEIN Teil des Nitro-Builds — er wird direkt via `node` gestartet.
## Was Backyard tun muss (in dieser Reihenfolge)
### Schritt 1 — GH-Actions: imap-idle ins Artifact einschließen
In `.github/workflows/deploy-backend.yml` (oder analog) muss das `backend/imap-idle/`-Verzeichnis
ins deploy-Artifact aufgenommen werden.
Das Artifact enthält aktuell wahrscheinlich nur `backend/.output-staging/` und `backend/prisma/`.
`backend/imap-idle/` muss ebenfalls mit kopiert werden.
Konkretes Beispiel (je nach Artifact-Aufbau anpassen):
```yaml
# In der scp/rsync-Step des deploy-workflows:
- name: Copy imap-idle to server
run: |
scp -r backend/imap-idle/ rebreak-server:/srv/rebreak/backend/imap-idle/
```
### Schritt 2 — npm install auf Server
Nach dem Artifact-Copy muss auf dem Server `npm install` in `backend/imap-idle/` laufen.
Das installiert `imapflow` und `pg` lokal für den Daemon.
```bash
cd /srv/rebreak/backend/imap-idle && npm install --production
```
Diesen Schritt als deploy-Step in GH-Actions oder in `deploy-from-artifact.sh` ergänzen.
### Schritt 3 — Zombie-Prozesse aufräumen
Vor dem ersten Start der neuen pm2-Einträge alte Stale-Einträge entfernen
(falls `rebreak-imap-staging` oder `rebreak-idle-staging` aus altem Setup noch existieren):
```bash
pm2 delete rebreak-idle-staging rebreak-imap-staging 2>/dev/null || true
```
### Schritt 4 — ecosystem.config.js erweitern
Die folgenden Einträge in `/srv/rebreak/ecosystem.config.js` ergänzen
(unterhalb des bestehenden `rebreak-staging`-Eintrags einfügen):
```js
// ─── IMAP IDLE Daemon Staging ───────────────────────────────────────────────
{
name: "rebreak-idle-staging",
script: "/srv/rebreak/backend/imap-idle/index.mjs",
interpreter: "/root/.nvm/versions/node/v24.11.1/bin/node",
cwd: "/srv/rebreak/backend/imap-idle",
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: "256M",
env: {
NODE_ENV: "production",
// ACHTUNG: Keine Secrets hier hinterlegen.
// Infisical-Wrapper via start-idle-staging.sh (Schritt 5).
},
},
// ─── IMAP IDLE Daemon Prod (auskommentiert bis Prod-Cutover) ───────────────
// {
// name: "rebreak-idle-prod",
// script: "/srv/rebreak/backend/imap-idle/index.mjs",
// interpreter: "/root/.nvm/versions/node/v24.11.1/bin/node",
// cwd: "/srv/rebreak/backend/imap-idle",
// instances: 1,
// autorestart: true,
// watch: false,
// max_memory_restart: "256M",
// env: { NODE_ENV: "production" },
// },
```
### Schritt 5 — start-idle-staging.sh erstellen
Der Daemon braucht die gleichen Infisical-Secrets wie das Backend.
Eine eigene Start-Shell analog zu `backend/start-staging.sh` erstellen:
Pfad: `/srv/rebreak/backend/imap-idle/start-idle-staging.sh`
```bash
#!/bin/bash
# rebreak-imap-idle Staging — Infisical-Secret-Injection
set -euo pipefail
source /etc/environment
if [[ -z "${INFISICAL_CLIENT_ID:-}" || -z "${INFISICAL_CLIENT_SECRET:-}" ]]; then
echo "[idle] FEHLER: INFISICAL_CLIENT_ID / SECRET nicht gesetzt" >&2
exit 1
fi
INFISICAL_TOKEN=$(infisical login \
--method=universal-auth \
--client-id="${INFISICAL_CLIENT_ID}" \
--client-secret="${INFISICAL_CLIENT_SECRET}" \
--silent --plain 2>/dev/null)
[[ -z "$INFISICAL_TOKEN" ]] && { echo "[idle] Infisical login fehlgeschlagen" >&2; exit 1; }
NODE_BIN="/root/.nvm/versions/node/v24.11.1/bin/node"
DAEMON="/srv/rebreak/backend/imap-idle/index.mjs"
exec infisical run \
--projectId="${INFISICAL_PROJECT_ID:-14b11b35-ef59-4b8a-a16b-398f0cc3ad93}" \
--env=staging \
--token="$INFISICAL_TOKEN" \
-- bash -c '
set -e
export DATABASE_URL="${DATABASE_URL:-${NUXT_DATABASE_URL:-}}"
export ADMIN_SECRET="${ADMIN_SECRET:-${NUXT_ADMIN_SECRET:-}}"
export ENCRYPTION_KEY="${ENCRYPTION_KEY:-${NUXT_ENCRYPTION_KEY:-}}"
export BACKEND_URL="http://127.0.0.1:3016"
exec '"$NODE_BIN"' '"$DAEMON"'
'
```
Dann `chmod +x start-idle-staging.sh` und in ecosystem.config.js den `script`-Key
auf `start-idle-staging.sh` zeigen lassen (mit `interpreter: "bash"`),
analog zum Pattern von `rebreak-staging`.
### Schritt 6 — pm2 starten
```bash
pm2 startOrReload /srv/rebreak/ecosystem.config.js
```
## Verifikations-Schritte nach Deployment
```bash
# 1. pm2-Status prüfen
pm2 list
# Erwartung: rebreak-idle-staging → status=online, restart=0
# 2. Logs der ersten 60 Sekunden ansehen
pm2 logs rebreak-idle-staging --lines 100
# Erwartung: "[idle/<email>] connected (...)" für alle aktiven Mailboxen
# "[idle/db] refreshed — N active connections, N sessions"
# 3. Test: Mail an eine verbundene Mailbox schicken (Betreff: "casino bonus")
# Innerhalb von 5 Sekunden sollte im Log erscheinen:
# "[idle/<email>] exists-event received (new mail)"
# "[idle/<email>] scan-triggered → scanned=X blocked=1"
# 4. Memory-Check nach 10 Minuten
pm2 monit
# Erwartung: < 100MB bei <20 Connections, < 200MB bei 100 Connections
```
## Rollback-Plan
Falls der Daemon crashed oder instabil ist:
```bash
pm2 stop rebreak-idle-staging
# NICHT delete — damit Logs erhalten bleiben
```
Auswirkung: Mail-Scanning fällt auf den bestehenden 30min-Cron zurück.
Kein Komplett-Ausfall der Mail-Schutz-Funktion. Gambling-Mails werden
weiter gelöscht, nur mit bis zu 30min Verzögerung statt Echtzeit.
## Bekannte Provider-Quirks
| Provider | IMAP-Host | Port | TLS | Bekanntes Problem |
|-------------|-----------------------------|------|----------|--------------------------------------------|
| Gmail | imap.gmail.com | 993 | Implicit | App-Password erforderlich (kein OAuth2) |
| iCloud | imap.mail.me.com | 993 | Implicit | App-Specific-Password in Apple-Settings |
| Outlook | outlook.office365.com | 993 | Implicit | IDLE-Drop nach ~20min — disableCompression |
| GMX | imap.gmx.net | 993 | Implicit | Stabil, kein besonderer Quirk |
| Web.de | imap.web.de | 993 | Implicit | Stabil |
| T-Online | secureimap.t-online.de | 993 | Implicit | Stabil |
| Posteo | posteo.de | 993 | Implicit | Stabil |
Outlook-spezifisch: Der Daemon setzt `disableCompression: true` wenn der Host
`office365` enthält — verhindert partial-read-Fehler nach IDLE-Drop.
## Datei-Übersicht
```
backend/imap-idle/
index.mjs — Daemon-Hauptdatei (ESM, standalone)
package.json — Eigene Dependencies (imapflow, pg)
README.md — Kurz-Doku (lokal starten, env-vars, log-format)
```
## Abhaengigkeiten
- `imapflow ^1.2.18` — IMAP-Client-Library (bereits in backend/package.json)
- `pg ^8.16.3` — Direkter Postgres-Zugriff (kein Prisma im Daemon-Kontext)
- Node.js >= 20 (ESM, top-level await via main())
- Infisical-CLI auf dem Server (bereits installiert fuer rebreak-staging)