# FinanceManager Deployment-Runbook

## Zweck und verbindlicher Produktionspfad

Dieses Runbook dokumentiert den kanonischen lokalen Releasepfad. Produktive Daten, Exporte, Backups, Kontodetails und Secrets bleiben außerhalb des Repositories.

- Repository: `Gamexgit/FinanceManager`
- Deployment-Worktree: `/home/agent/.hermes/worktrees/FinanceManager-main-deploy`
- Backend-Service: `finance-manager-backend.service`
- Frontend-Service: `finance-manager-frontend.service`
- Backend-Health lokal: `http://127.0.0.1:18061/api/health`
- Frontend lokal: `http://127.0.0.1:15177/`
- Runtime-Grenze: siehe [`../decisions/ADR-0002-runtime-data-outside-repo.md`](../decisions/ADR-0002-runtime-data-outside-repo.md)

Die systemd-Units und deren Environment sind die Betriebswahrheit. Die Ports 8000 und 5173 gehören in dieser Umgebung nicht zum FinanceManager-Releasepfad und dürfen durch einen FinanceManager-Deploy nicht beendet oder übernommen werden.

## Release-Voraussetzungen

1. PR vollständig grün; keine offenen P0/P1/P2.
2. Exakten Merge-SHA dokumentieren; nicht nur Branchname oder PR-Head verwenden.
3. Keine uncommitteten Änderungen im Deployment-Worktree.
4. Produktive Mutationen und fachliche Confirms sind kein Bestandteil des Deployments.
5. Bei einer Migration gelten zusätzlich Backup und isolierter Restore-Test. Ohne Migration und ohne produktiven Write wird kein weiterer Backup-/Restore-Lauf erzeugt.

## Deployment

### Online-Backup unmittelbar vor Schema-Migration

Das Backup wird nach grünem Merge-Gate und unmittelbar vor dem Deployment aus der laufenden Schema-48-Datenbank über die SQLite-Backup-API erzeugt. Die Quelle wird ausschließlich read-only geöffnet; Backup und Nachweis bleiben außerhalb Git.

```bash
umask 077
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
WORKTREE="$HOME/.hermes/worktrees/FinanceManager-main-deploy"
SOURCE="$HOME/jarvis_runtime/finance-system/data/finance.sqlite3"
BACKUP="$HOME/jarvis_runtime/finance-system/backups/pre-sprint17d-schema48-$STAMP.sqlite3"
python3 - "$SOURCE" "$BACKUP" <<'PY'
import sqlite3, sys
source, backup = sys.argv[1:]
src = sqlite3.connect(f"file:{source}?mode=ro", uri=True)
dst = sqlite3.connect(backup)
src.backup(dst)
dst.close(); src.close()
PY
PYTHONPATH="$WORKTREE/src" "$HOME/jarvis_runtime/finance-system/venv/bin/python" - "$BACKUP" <<'PY'
import sys
from jarvis_finance.storage.database import connect
conn = connect(sys.argv[1])
assert conn.execute("PRAGMA foreign_keys").fetchone()[0] == 1
assert conn.execute("SELECT MAX(version) FROM schema_migrations").fetchone()[0] == 48
assert conn.execute("PRAGMA integrity_check").fetchone()[0] == "ok"
assert list(conn.execute("PRAGMA foreign_key_check")) == []
conn.close()
PY
stat -c 'BACKUP_BYTES=%s' "$BACKUP"
sha256sum "$BACKUP"
```

Der absolute Backuppfad wird nicht in Git oder Chat veröffentlicht. Größe, SHA-256, Schema und Integritätsresultat gehören in den PR-/Release-Audit. Bei Migrations- oder Startfehlern: Services stoppen, keine Tabellen manuell reparieren, Backup auf einen separaten Restore-Pfad kopieren und erst den dokumentierten Restore prüfen.

```bash
DEPLOY_SHA='<exakter-merge-sha>'
WORKTREE='/home/agent/.hermes/worktrees/FinanceManager-main-deploy'

git -C "$WORKTREE" fetch origin main
test "$(git -C "$WORKTREE" rev-parse origin/main)" = "$DEPLOY_SHA"
test -z "$(git -C "$WORKTREE" status --porcelain)"
git -C "$WORKTREE" checkout --detach "$DEPLOY_SHA"

git -C "$WORKTREE" rev-parse HEAD
(cd "$WORKTREE/frontend" && npm run build)

SOURCE="$HOME/jarvis_runtime/finance-system/data/finance.sqlite3"
RUNTIME="$HOME/jarvis_runtime/finance-system"
ENVFILE="$HOME/.hermes/secrets/finance_manager_runtime.env"
systemctl --user stop finance-manager-backend.service finance-manager-frontend.service
set -a; . "$ENVFILE"; set +a
env JARVIS_FINANCE_ENV=production \
    JARVIS_FINANCE_DB_PATH="$SOURCE" \
    JARVIS_FINANCE_RUNTIME_DIR="$RUNTIME" \
    JARVIS_FINANCE_WRITE_MODE=disabled \
    PYTHONPATH="$WORKTREE/src" \
    "$RUNTIME/venv/bin/python" -m jarvis_finance.cli.main migrate

PYTHONPATH="$WORKTREE/src" "$RUNTIME/venv/bin/python" - "$SOURCE" <<'PY'
import sys
from jarvis_finance.storage.database import connect
conn = connect(sys.argv[1])
assert conn.execute("PRAGMA foreign_keys").fetchone()[0] == 1
assert conn.execute("SELECT MAX(version) FROM schema_migrations").fetchone()[0] == 49
assert conn.execute("PRAGMA integrity_check").fetchone()[0] == "ok"
assert list(conn.execute("PRAGMA foreign_key_check")) == []
conn.close()
PY

systemctl --user restart finance-manager-backend.service
curl --fail --silent --show-error http://127.0.0.1:18061/api/health

systemctl --user restart finance-manager-frontend.service
curl --fail --silent --show-error http://127.0.0.1:15177/ >/dev/null
```

Der Frontend-Build muss im Unterverzeichnis `frontend/` erfolgen. Die Schema-Migration läuft vor dem Service-Start ausschließlich über den gezeigten CLI-/Connection-Pfad mit aktiven Foreign Keys. Ein Restore-Test mit rohem `sqlite3.connect()` und standardmäßig deaktivierten Foreign Keys ist kein Release-Gate. Repository-Restartskripte sind kein Ersatz für die beiden systemd-Services, solange sie nicht nachweislich dieselben Units, Ports und Environment-Dateien verwenden.

## Read-only Verifikation

- `git -C "$WORKTREE" rev-parse HEAD` entspricht exakt dem Merge-SHA.
- Beide Services sind `active`.
- `/api/health` antwortet erfolgreich und meldet einen sicheren/read-only Betriebsmodus für die UAT.
- OpenAPI enthält die im Release erwarteten Routen.
- Browser-UAT verwendet ausschließlich die freigegebenen FinanceManager-Seiten und löst keine Confirms aus.
- Browser-Console ist fehlerfrei; keine fremden Dienste oder Ports wurden beeinflusst.

## Rollback

1. Vorherigen bekannten grünen Merge-SHA wählen.
2. Deployment-Worktree detached auf diesen SHA stellen.
3. Frontend im Unterverzeichnis `frontend/` neu bauen.
4. Backend- und Frontend-Service neu starten.
5. SHA, Health, OpenAPI und read-only Browser-UAT erneut prüfen.

Ein Code-Rollback darf keine produktiven Finanzdaten löschen oder umschreiben. Datenmigrationen benötigen einen separat dokumentierten, vorwärtskompatiblen Rückweg.
