Files
nexus/.gitea/workflows/backup.yaml
T
reviewer 4ad0f9e493
CI - Build & Test / Backend (.NET) (push) Failing after 25s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 17s
CI - Build & Test / Security Check (push) Successful in 2s
refactor: SOLID architecture — backend service layer + frontend V2 components
## 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>
2026-06-14 08:34:58 +02:00

157 lines
7.6 KiB
YAML

name: Database Backup
run-name: 💾 DB Backup triggered by @${{ gitea.actor }}
# ───────────────────────────────────────────────────────
# Owner: DevOps (Architekt)
# Trigger: Manual (workflow_dispatch) + optional schedule.
#
# Strategy:
# 1. Connects to the live PostgreSQL container via docker exec.
# 2. Runs pg_dumpall (full cluster dump, single file).
# 3. Compresses with gzip.
# 4. Uploads as a Gitea Action artifact (or writes to host path).
# 5. Artifacts are retained per Gitea repo settings (default 90 days).
#
# Rotation: Gitea artifact expiration handles old backups automatically.
# For longer retention, configure an external cron job or use the
# host_path output to copy the backup elsewhere.
#
# Restoration: See phases/deployment.md for step-by-step instructions.
# ───────────────────────────────────────────────────────
concurrency:
group: db-backup
cancel-in-progress: false
on:
workflow_dispatch:
inputs:
keep_on_host:
description: 'Also copy backup to host path?'
required: false
default: false
type: boolean
host_backup_path:
description: 'Host path for backup (only if keep_on_host is true)'
required: false
default: '/opt/openclaw/backups'
type: string
# Optional: uncomment to enable nightly automatic backups
# schedule:
# - cron: '0 3 * * *' # Every night at 03:00 UTC
jobs:
backup:
name: Backup PostgreSQL
runs-on: ubuntu-latest
env:
ENV_TMPFILE: /tmp/nexus-backup-env
ENV_POSTGRES_PASSWORD: ${{ secrets.ENV_POSTGRES_PASSWORD }}
DEPLOY_PATH: /opt/openclaw/data/openclaw/workspace/nexus
BACKUP_CONTAINER_NAME: nexus-postgres-1
steps:
# ═══════════════════════════════════════════════════
# Step 1: Generate backup filename
# ═══════════════════════════════════════════════════
- name: Generate backup identifier
id: meta
run: |
TIMESTAMP=$(date -u +'%Y-%m-%dT%H%M%SZ')
echo "timestamp=${TIMESTAMP}" >> "$GITEA_OUTPUT"
echo "filename=nexus-backup-${TIMESTAMP}.sql.gz" >> "$GITEA_OUTPUT"
echo "📅 Backup ID: ${TIMESTAMP}"
# ═══════════════════════════════════════════════════
# Step 2: Dump PostgreSQL via docker exec
# ═══════════════════════════════════════════════════
- name: Dump database
run: |
set -euo pipefail
echo "🗄️ Dumping PostgreSQL cluster..."
docker exec "${BACKUP_CONTAINER_NAME}" \
sh -c "PGPASSWORD='${ENV_POSTGRES_PASSWORD}' pg_dumpall -U nexus -h localhost" \
| gzip > "${{ steps.meta.outputs.filename }}"
SIZE=$(du -h "${{ steps.meta.outputs.filename }}" | cut -f1)
echo "✅ Backup written: ${{ steps.meta.outputs.filename }} (${SIZE})"
# ═══════════════════════════════════════════════════
# Step 3: Upload backup as Gitea artifact
# ═══════════════════════════════════════════════════
- name: Upload backup artifact
uses: actions/upload-artifact@v4
with:
name: nexus-backup-${{ steps.meta.outputs.timestamp }}
path: ${{ steps.meta.outputs.filename }}
retention-days: 90
compression-level: 0 # already gzipped
# ═══════════════════════════════════════════════════
# Step 4: Optional — copy to host filesystem
# ═══════════════════════════════════════════════════
- name: Copy backup to host (optional)
if: inputs.keep_on_host == true
run: |
set -euo pipefail
HOST_PATH="${{ inputs.host_backup_path }}"
# Create host dir if it doesn't exist
docker run --rm \
-v "${HOST_PATH}:/backup-target" \
-v "${{ gitea.workspace }}:/src:ro" \
alpine:latest \
sh -c "
mkdir -p /backup-target && \
cp /src/${{ steps.meta.outputs.filename }} /backup-target/ && \
echo '✅ Backup copied to host: ${HOST_PATH}/${{ steps.meta.outputs.filename }}'
"
# ═══════════════════════════════════════════════════
# Step 5: Verify backup integrity
# ═══════════════════════════════════════════════════
- name: Verify backup integrity
run: |
echo "🔍 Verifying backup integrity..."
if gzip -t "${{ steps.meta.outputs.filename }}"; then
echo "✅ Backup gzip integrity check passed"
else
echo "❌ Backup file is corrupted!"
exit 1
fi
# Quick content check: should start with PostgreSQL dump header
HEADER=$(zcat "${{ steps.meta.outputs.filename }}" | head -1)
if echo "$HEADER" | grep -qE '^(-- PostgreSQL database cluster dump|-- Dumped|--)'; then
echo "✅ Backup content header check passed"
else
echo "⚠️ Unexpected backup header (may still be valid): $HEADER"
fi
# ═══════════════════════════════════════════════════
# Step 6: Backup Summary
# ═══════════════════════════════════════════════════
- name: Backup Summary
if: always()
run: |
STATUS="${{ job.status }}"
echo ""
echo "═══════════════════════════════════════"
echo " 💾 Database Backup Summary"
echo "═══════════════════════════════════════"
echo " File: ${{ steps.meta.outputs.filename }}"
echo " Timestamp: ${{ steps.meta.outputs.timestamp }}"
echo " Triggered: @${{ gitea.actor }}"
echo " On host: ${{ inputs.keep_on_host == 'true' && inputs.host_backup_path || 'No (artifact only)' }}"
echo " Status: ${STATUS}"
echo "═══════════════════════════════════════"
if [ "${STATUS}" = "success" ]; then
echo ""
echo "💡 Restore command (manual, on host):"
echo " zcat ${{ steps.meta.outputs.filename }} | docker exec -i nexus-postgres-1 psql -U nexus -d postgres"
fi