Files
nexus/phases/deployment.md
T
devops f95463ef50
CI - Build & Test / Backend (.NET) (push) Successful in 28s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 18s
CI - Build & Test / Security Check (push) Successful in 2s
fix: permanent owner password persistence with SeedAudit guard
Root cause: Dual-source architecture for owner password (Gitea secret
ENV_OWNER_PASSWORD vs host .env OWNER_PASSWORD) caused drift when
the DB was ever re-seeded or the volume recreated.

Changes:
- Add SeedAudit entity + migration to track one-time seed operations
- EnsureDatabaseAsync checks SeedAudit BEFORE seeding — owner is never
  re-created even if the Users table is wiped
- Deploy and rollback workflows now read OWNER_PASSWORD from the host's
  persistent .env (single source of truth) instead of Gitea secrets
- compose.yaml documented: OWNER_PASSWORD only used during initial seed
- Cleanup: .gitignore extended for core dumps, changelog/deployment.md
  updated with 2026-06-20 session notes

After this fix the DB is the single source of truth for the owner
password after initial seed. The host .env is the single reference
for the initial value.
2026-06-21 10:15:36 +02:00

10 KiB

Deployment

Letzte Aktualisierung: 2026-06-20 Status: CD v3 (Auto + Manual) Live-URL: https://nexus.noveria.net

CD-Philosophie (v3)

  • CI läuft automatisch bei jedem Push → darf nie brechen
  • CD auto + manuell: Automaticher Deploy nach CI-Success auf main (patch default), manueller Deploy mit voller Kontrolle via workflow_dispatch
  • Loop-Schutz: Version-Bump-Commits enthalten [skip ci] — kein Re-Trigger der CI, kein Infinite-Loop
  • Main-Deploys duerfen VERSION bumpen und einen Git-Tag setzen
  • Nicht-Main-Deploys (anderer git_ref) deployen read-only und mutieren Git nicht
  • Rollback als eigener Workflow, manuell triggerbar
  • Database-Backup als eigener Workflow, manuell triggerbar (optionaler Nightly-Schedule)

Workflows

Deploy (.gitea/workflows/deploy.yaml)

Trigger:

  • Automatisch: Nach erfolgreicher CI (workflow_run auf CI - Build & Test) → Default-Parameter: patch bump, all services, main ref
  • Manuell: Via Gitea Actions → workflow_dispatch

Loop-Schutz:

  • Version-Bump-Commits enthalten [skip ci] → Gitea startet keine neue CI
  • Auto-Deploy prüft zusätzlich github.event.workflow_run.head_commit.message auf [skip ci]
  • Beide Mechanismen zusammen verhindern Endlosschleife: CI → Deploy → Bump → CI …

Inputs (nur bei workflow_dispatch):

Input Typ Default Beschreibung
version_bump choice (patch/minor/major) patch Version-Bump-Typ
service string (all) Einzelner Service oder alle
no_cache boolean false Docker-Build-Cache deaktivieren
git_ref string main Branch/Tag/Commit zum Deployen

Ablauf:

  1. Job-Level-Guard: Auto-Deploys fuer [skip ci]-Commits werden gar nicht gestartet
  2. Checkout des gewählten Git-Refs
  3. Wenn git_ref = main: Version-Bump + Git-Tag + Push
  4. Wenn git_ref != main: VERSION nur lesen, kein Push, kein Tag
  5. Safe Secret Handling: .env wird aus Secret-Umgebungsvariablen in /tmp/nexus-deploy-env geschrieben (mode 600), NICHT im Workspace
  6. Code-Sync zum Host-Deploy-Pfad
  7. docker compose build && up -d --wait --force-recreate
  8. .env-Tempfile wird mit shred gelöscht
  9. Health-Check (exponentieller Backoff, 6 Versuche)
  10. Smoke-Test (/dashboard, /health, /api/v1/operations/snapshot erwartet 401)
  11. Bei Fehler: Reviewer-Handoff-Meldung mit Job-URL

Backup (.gitea/workflows/backup.yaml)

Trigger: Manuell via Gitea Actions → workflow_dispatch (optional: Nightly-Schedule via Cron)

Inputs:

Input Typ Default Beschreibung
keep_on_host boolean false Backup auch auf Host-Pfad kopieren
host_backup_path string /opt/openclaw/backups Host-Zielpfad

Ablauf:

  1. Backup-ID generieren (Timestamp-basiert)
  2. docker exec nexus-postgres-1 pg_dumpall -U nexus → gzip
  3. Upload als Gitea-Artifact (90 Tage Retention, bereits komprimiert)
  4. Optional: Kopie auf Host-Pfad via Docker-Volume-Mount
  5. Integritäts-Check: gzip-Test + SQL-Header-Validierung
  6. Backup-Summary mit Restore-Befehl

Restore (manuell auf dem Host):

# Aus Gitea-Artifact herunterladen oder von Host-Pfad:
zcat nexus-backup-YYYY-MM-DDTHHMMSSZ.sql.gz | docker exec -i nexus-postgres-1 psql -U nexus -d postgres
# Danach Stack neu starten:
cd /opt/openclaw/data/openclaw/workspace/nexus
docker compose up -d --wait

Nightly-Schedule aktivieren: In backup.yaml die Zeilen auskommentieren:

schedule:
  - cron: '0 3 * * *'  # Jede Nacht um 03:00 UTC

Rollback (.gitea/workflows/rollback.yaml)

Trigger: Manuell via Gitea Actions → workflow_dispatch

Inputs:

Input Typ Beschreibung
target_tag string Git-Tag zum Zurückrollen (z.B. v0.2.49)
confirm string Muss exakt ROLLBACK sein (Safety-Gate)

Ablauf:

  1. Safety-Gate: Bestätigungstext muss ROLLBACK sein
  2. Checkout des Target-Tags
  3. Tag-Validierung (existiert? welcher Commit?)
  4. Safe Secret Handling (gleiches Tempfile-Pattern)
  5. Code-Sync des alten Stands zum Host
  6. docker compose build --no-cache && up -d --wait --force-recreate
  7. Health-Check + Smoke-Test (/dashboard, /health, /api/v1/operations/snapshot erwartet 401)
  8. Bei Fehler: Reviewer-Handoff mit manueller Rollback-Anleitung

DB-Migration bei Rollback: Die API führt MigrateAsync beim Start aus. Wenn die Migrationen des Rollback-Tags ein Prefix der aktuellen DB sind (Normalfall), läuft EF Core sie als No-Op. Wenn ein Rollback-Tag vor einer destruktiven Migration liegt, ist manuelles DB-Intervention nötig — ein Edge Case, der DevOps signalisiert wird.

Secrets und Konfiguration

Secrets in Gitea

Folgende Secrets sind in Gitea (Repo → Settings → Actions → Secrets) konfiguriert:

Secret Verwendung
ENV_POSTGRES_PASSWORD PostgreSQL-Passwort
ENV_JWT_KEY JWT-Signing-Key (min. 32 Bytes)
ENV_OWNER_PASSWORD Owner-Account-Passwort
ENV_OPENCLAW_TOKEN OpenClaw Gateway Token

Safe Secret Handling (v3)

Vorher (unsicher): Secrets wurden via ${{ secrets.X }} direkt in eine Datei im Workspace interpoliert, die dann zum Host synct wurde. Das .env lag potenziell lesbar im Workspace und auf dem Host-Dateisystem.

Jetzt (sicher):

  1. Secrets werden als Step-Environment aus Gitea Secrets bezogen und erst dann in /tmp/nexus-deploy-env (mode 600) geschrieben
  2. Die Temp-Datei wird via docker run -v als read-only ins Compose-Environment gemountet
  3. Nach Deploy/Rollback wird die Datei mit shred -u gelöscht
  4. Das .env erscheint nie im Workspace oder auf dem Host-Deploy-Pfad

Build-Anleitung (lokal oder in CI)

Die folgenden Befehle sind auf dem Build-System auszuführen. Vor dem Build müssen die Secrets in .env gesetzt sein.

# 1. Backend veröffentlichen
cd backend
dotnet publish -c Release -o dist

# 2. Frontend bauen (pnpm preferred)
cd ../frontend
pnpm install
pnpm build
# └─ Output: frontend/dist/ (statisch auslieferbar)

# 3. Docker-Stack starten (wenn compose verwendet wird)
cd ..
docker compose up -d --build

Die Container holen sich ihre Umgebungsvariablen aus der .env im Projektstamm.
Stelle sicher, dass .env existiert und alle ***-Platzhalter ersetzt sind.

Deployment-Plan

  1. .env-Datei auf dem VPS anlegen (alle Secrets generieren/setzen)
  2. Backup vor produktiven Infrastrukturarbeiten
  3. Docker-Stack auf dem VPS deployen
  4. Datenbankmigration läuft automatisch beim Start (via MigrateAsync)
  5. Nginx Proxy Manager und nexus.noveria.net verbinden
  6. HTTPS, Header, Cookies und externe Erreichbarkeit validieren

Abgeschlossene Deployment-Arbeit

  • Produktions-.env mit starken, getrennten Secrets angelegt (2026-06-08)
  • Datenbankmigration und kompletter Stack per Docker-Compose deployt
  • Nexus auf dem VPS deployt (Docker Compose)
  • Nginx mit Let's Encrypt SSL fuer nexus.noveria.net konfiguriert
  • HTTPS, Security-Header (HSTS, X-Content-Type-Options, X-Frame-Options), Cookies validiert
  • Externe Erreichbarkeit bestaetigt (2026-06-09)
  • CI/CD entkoppelt — Deploy darf automatisch (v3) oder manuell (2026-06-13)
  • Automatischer Deploy nach CI-Success auf main mit Loop-Schutz via [skip ci] (2026-06-13)
  • Safe Secret Handling: Tempfile in /tmp statt Workspace-Datei (2026-06-13)
  • Rollback-Workflow implementiert mit Safety-Gate (2026-06-13)
  • Main-Deploys koennen Version-Bump + Git-Tag automatisch setzen; Non-Main-Deploys bleiben read-only (2026-06-13)
  • Reviewer-Handoff bei Deploy/Rollback-Fehlern (2026-06-13)
  • Database-Backup-Workflow mit pg_dumpall + Gitea-Artifact (2026-06-13)
  • Live-Recheck nach Deploy-Stoerung: /health, SPA-Root und GET /api/dashboard/tasks wieder 200; Bao-Folgetask zur Agent-Progress-Visibility erstellt (2026-06-20)
  • Agent-Progress-Stand (2d21885) manuell als sauberer Commit-Snapshot live ausgerollt, nachdem der normale Gitea-Deploy-Trigger blockierte (2026-06-20)

Verifizierung

2026-06-20

  • https://nexus.noveria.net/ → 200 OK, SPA geladen (<title>Nexus | Noveria Operations</title>)
  • /health → 200 Healthy, PostgreSQL + Runtime healthy
  • /api/dashboard/tasks → 200 OK mit X-Nexus-Api-Key
  • Follow-up-Task Restore agent progress visibility in Nexus fuer assignedTo=bao erfolgreich angelegt

2026-06-09

  • https://nexus.noveria.net → 200 OK, SPA geladen
  • /health → Healthy
  • /dashboard, /login → SPA-Routing korrekt
  • /api/v1/operations/snapshot → 401 Unauthorized (Auth-Schutz aktiv)
  • Let's Encrypt TLS-Zertifikat aktiv
  • Nginx-Proxy → 127.0.0.1:18880

Incident-Hinweis (2026-06-14)

  • Verifizierter Ausfallpfad: api konnte wegen DB-Passwort-Mismatch nicht healthy werden; dadurch blieb web per depends_on: service_healthy im Status Created.
  • Nach einem isolierten API-Fix startet web nicht automatisch nach. Sicherer Minimalpfad:
    1. docker compose ps
    2. curl http://127.0.0.1:18880/health
    3. Falls health=200, aber /dashboard noch nicht 200 und web auf Created steht: docker compose up -d web
    4. Danach extern /dashboard, /health und /api/v1/operations/snapshot erneut prüfen
  • Der manuelle Helper ops/deploy.sh verifiziert deshalb jetzt nicht mehr nur /health, sondern auch /dashboard und den Auth-Schutz der Operations-API.

Offene Arbeit

  • [!] Gitea-Deploy-Trigger reparieren: POST /actions/workflows/deploy.yaml/dispatches liefert aktuell HTTP 500; fuer Commit 2d21885 war deshalb kein erfolgreicher normaler Deploy-Run belegbar
  • Docker-Socket-Risiko im CD-Workflow final adressieren (kommt spaeter)
  • Docker-Logs und Container-Health-Monitoring einrichten
  • Restore-Drill fuer Backup/Recovery einmal realistisch durchspielen und dokumentieren
  • Direkt-Pushes auf main waehrend eines Main-Deploys organisatorisch vermeiden oder spaeter technisch haerter absichern

Deploy-Trigger-Actor (2026-06-14)

  • Deploy-Trigger werden durch DevOps (nicht Iris) ausgelöst
  • Git-Remote origin verwendet DevOps-Token → Gitea zeigt devops als Actor
  • Workflow-Dispatch API-Calls mit DevOps-Token authentifizieren