4ad0f9e493
## Backend — Service Layer & Repository Refactoring ### Neue Services (21 neue Dateien) **Interfaces & Implementierungen:** - `IOpenClawGatewayClient` — Interface für OpenClawGatewayClient (DIP-Fix: DashboardController hing an konkreter Klasse) - `IAgentConfigService` / `AgentConfigService` — Agent-Config-File-I/O aus AgentsController extrahiert - `IProjectService` / `ProjectService` — Projekt-CRUD + Activity-Logging (SRP) - `ITaskService` / `TaskService` — Task-State-Machine, Approve/Reject, Dashboard-Operationen (eliminiert Duplikation zwischen TasksController und DashboardController) - `IDashboardService` / `DashboardService` — Queue-Aggregation, Priority-Normalisierung, Gateway-Delegation - `IOperationsService` / `OperationsService` — Metriken-Berechnung aus OperationsController - `ITeamService` / `TeamService` — IDENTITY.md-Lesen aus TeamController - `IMemoryService` / `MemoryService` — File-I/O aus MemoryController - `IIncidentService` / `IncidentService` — File-Parsing (Regex-Source-Generatoren) aus IncidentsController - `IDocService` / `DocService` — Directory-Scan aus DocsController - `ICalendarService` / `CalendarService` — Gateway-HTTP-Calls + Fallback-Daten aus CalendarController ### Repository-Fixes **IUserRepository / UserRepository:** - `SaveChangesAsync` entfernt (leaky abstraction — Caller sollten nie SaveChanges steuern) - `RevokeTokenAsync(tokenHash)` — atomares Token-Revoke inkl. SaveChanges - `RevokeFamilyAsync(familyId)` — Batch-Revoke einer Token-Familie inkl. SaveChanges - `RemoveExpiredTokensAsync` speichert jetzt selbst (war vorher dependent auf nachfolgenden Save) ### AuthService-Fixes - `GetUserAsync`: unnötiges `Task.Run` entfernt → direkt `_users.GetByIdAsync().AsTask()` - `RevokeAsync`: delegiert jetzt an `IUserRepository.RevokeTokenAsync` - `RefreshAsync`: Token-Reuse-Detection delegiert an `IUserRepository.RevokeFamilyAsync` ### Bug-Fix - `OpenClawGatewayClient.ReadAgentGoalAsync`: pre-existing `CS1656` behoben (`reader` war `using`-Variable und wurde neu zugewiesen — in `reader2` umbenannt) ### Controller (16 Stück — alle slim) Alle Controller reduziert auf: Input validieren → Service aufrufen → HTTP-Result zurückgeben. Kein Business-Logic, kein File-I/O, keine direkte Repository-Nutzung (außer AgentsController für Activity-Log). **Program.cs — neue Registrierungen:** - `AddHttpClient<IOpenClawGatewayClient, OpenClawGatewayClient>` (war vorher konkrete Klasse) - Scoped: IDashboardService, IProjectService, ITaskService, IOperationsService, ITeamService, ICalendarService - Singleton: IAgentConfigService, IMemoryService, IIncidentService, IDocService --- ## Frontend — Dashboard V2 Components **AgentDetailModal.vue, IrisChat.vue, TaskStrip.vue:** - V2 Design-System: Dark Space Theme, Glass-Panels, Gradient-Akzente - Stores (agents, chat, tasks) nutzen Service + Mapper-Pattern - NexusLayout, FlowBoard, Topbar — Layoutfixes für fullHeight-Route-Meta Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
194 lines
8.2 KiB
Markdown
194 lines
8.2 KiB
Markdown
# Deployment
|
|
|
|
> Letzte Aktualisierung: 2026-06-13
|
|
> 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)**:
|
|
```bash
|
|
# 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:
|
|
```yaml
|
|
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.
|
|
|
|
```bash
|
|
# 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
|
|
|
|
- [x] Produktions-`.env` mit starken, getrennten Secrets angelegt (2026-06-08)
|
|
- [x] Datenbankmigration und kompletter Stack per Docker-Compose deployt
|
|
- [x] Nexus auf dem VPS deployt (Docker Compose)
|
|
- [x] Nginx mit Let's Encrypt SSL fuer `nexus.noveria.net` konfiguriert
|
|
- [x] HTTPS, Security-Header (HSTS, X-Content-Type-Options, X-Frame-Options), Cookies validiert
|
|
- [x] Externe Erreichbarkeit bestaetigt (2026-06-09)
|
|
- [x] CI/CD entkoppelt — Deploy darf automatisch (v3) oder manuell (2026-06-13)
|
|
- [x] Automatischer Deploy nach CI-Success auf main mit Loop-Schutz via [skip ci] (2026-06-13)
|
|
- [x] Safe Secret Handling: Tempfile in /tmp statt Workspace-Datei (2026-06-13)
|
|
- [x] Rollback-Workflow implementiert mit Safety-Gate (2026-06-13)
|
|
- [x] Main-Deploys koennen Version-Bump + Git-Tag automatisch setzen; Non-Main-Deploys bleiben read-only (2026-06-13)
|
|
- [x] Reviewer-Handoff bei Deploy/Rollback-Fehlern (2026-06-13)
|
|
- [x] Database-Backup-Workflow mit pg_dumpall + Gitea-Artifact (2026-06-13)
|
|
|
|
## Verifizierung (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
|
|
|
|
## Offene Arbeit
|
|
|
|
- [ ] 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
|