From 1214cf9a4dcac46607d018bd0817db2ee46630f7 Mon Sep 17 00:00:00 2001 From: DevOps Date: Tue, 23 Jun 2026 18:39:38 +0200 Subject: [PATCH] chore: simplify nexus cicd pipeline --- .env.example | 10 -- .gitea/workflows/backup.yaml | 2 +- .gitea/workflows/ci.yaml | 2 +- .gitea/workflows/deploy-now.yaml | 186 ------------------------------- .gitea/workflows/deploy.yaml | 7 +- .gitignore | 1 - README.md | 22 ++-- backend/.dockerignore | 10 ++ frontend/.dockerignore | 9 ++ ops/deploy.sh | 70 ------------ ops/install-ollama-host.sh | 55 --------- ops/nginx-landing.conf | 55 --------- ops/nginx-nexus.conf | 81 -------------- ops/setup-https.sh | 107 ------------------ phases/deployment.md | 38 +++---- 15 files changed, 49 insertions(+), 606 deletions(-) delete mode 100644 .env.example delete mode 100644 .gitea/workflows/deploy-now.yaml create mode 100644 backend/.dockerignore create mode 100644 frontend/.dockerignore delete mode 100755 ops/deploy.sh delete mode 100755 ops/install-ollama-host.sh delete mode 100644 ops/nginx-landing.conf delete mode 100644 ops/nginx-nexus.conf delete mode 100755 ops/setup-https.sh diff --git a/.env.example b/.env.example deleted file mode 100644 index a6c731f..0000000 --- a/.env.example +++ /dev/null @@ -1,10 +0,0 @@ -POSTGRES_DB=nexus -POSTGRES_USER=nexus -POSTGRES_PASSWORD=replace-with-a-strong-database-password -JWT_KEY=replace-with-at-least-32-random-bytes -BOOTSTRAP_OWNER_EMAIL=owner@example.com -OPENCLAW_BASE_URL=http://host.docker.internal:18789 -OPENCLAW_GATEWAY_TOKEN= -OPENCLAW_GATEWAY_PASSWORD= -OLLAMA_BASE_URL=http://host.docker.internal:11434 -NVIDIA_API_KEY= diff --git a/.gitea/workflows/backup.yaml b/.gitea/workflows/backup.yaml index cad37d1..0d87b40 100644 --- a/.gitea/workflows/backup.yaml +++ b/.gitea/workflows/backup.yaml @@ -72,7 +72,7 @@ jobs: echo "🗄️ Dumping PostgreSQL cluster..." docker exec "${BACKUP_CONTAINER_NAME}" \ - sh -c "PGPASSWORD='${ENV_POSTGRES_PASSWORD}' pg_dumpall -U nexus -h localhost" \ + sh -c "PGPASSWORD='${ENV_POSTGRES_PASSWORD}' pg_dumpall -U nexus" \ | gzip > "${{ steps.meta.outputs.filename }}" SIZE=$(du -h "${{ steps.meta.outputs.filename }}" | cut -f1) diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml index cbad84b..442c3d1 100644 --- a/.gitea/workflows/ci.yaml +++ b/.gitea/workflows/ci.yaml @@ -51,7 +51,7 @@ jobs: - name: Setup pnpm run: | corepack enable - corepack prepare pnpm@latest --activate + corepack prepare pnpm@10.12.1 --activate - name: Install dependencies run: pnpm install --frozen-lockfile diff --git a/.gitea/workflows/deploy-now.yaml b/.gitea/workflows/deploy-now.yaml deleted file mode 100644 index 21b5df8..0000000 --- a/.gitea/workflows/deploy-now.yaml +++ /dev/null @@ -1,186 +0,0 @@ -name: Deploy Now -run-name: 🚀 Deploy Now by @${{ gitea.actor }} - -on: - workflow_dispatch: - -jobs: - deploy: - name: Deploy Nexus - runs-on: ubuntu-latest - env: - DEPLOY_PATH: /home/projekte_bao/openclaw/data/openclaw/workspace/nexus - ENV_TMPFILE: /tmp/nexus-deploy-env - ENV_POSTGRES_PASSWORD: ${{ secrets.ENV_POSTGRES_PASSWORD }} - ENV_JWT_KEY: ${{ secrets.ENV_JWT_KEY }} - ENV_OPENCLAW_TOKEN: ${{ secrets.ENV_OPENCLAW_TOKEN }} - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: main - fetch-depth: 0 - fetch-tags: true - - - name: Resolve Version - id: version - run: | - set -euo pipefail - if [ ! -f VERSION ]; then - echo "ERROR: VERSION file not found" - exit 1 - fi - VERSION=$(cat VERSION | tr -d '[:space:]') - if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then - echo "ERROR: Invalid semver in VERSION: $VERSION" - exit 1 - fi - GIT_REF=$(git rev-parse --short HEAD) - echo "Deploy version: v${VERSION} git:${GIT_REF}" - echo "version=${VERSION}" >> "$GITEA_OUTPUT" - - - name: Prepare .env - run: | - set -euo pipefail - printf 'POSTGRES_DB=nexus\n' > "${ENV_TMPFILE}" - printf 'POSTGRES_USER=nexus\n' >> "${ENV_TMPFILE}" - printf 'POSTGRES_PASSWORD=%s\n' "${ENV_POSTGRES_PASSWORD}" >> "${ENV_TMPFILE}" - printf 'JWT_KEY=%s\n' "${ENV_JWT_KEY}" >> "${ENV_TMPFILE}" - printf 'JWT_ISSUER=nexus\n' >> "${ENV_TMPFILE}" - printf 'JWT_AUDIENCE=nexus-web\n' >> "${ENV_TMPFILE}" - printf 'BOOTSTRAP_OWNER_EMAIL=vmbao62@hotmail.de\n' >> "${ENV_TMPFILE}" - printf 'OPENCLAW_BASE_URL=http://host.docker.internal:18789\n' >> "${ENV_TMPFILE}" - printf 'OPENCLAW_GATEWAY_TOKEN=%s\n' "${ENV_OPENCLAW_TOKEN}" >> "${ENV_TMPFILE}" - printf 'OPENCLAW_GATEWAY_PASSWORD=\n' >> "${ENV_TMPFILE}" - chmod 600 "${ENV_TMPFILE}" - echo "OK .env written to ${ENV_TMPFILE}" - - - name: Sync code to host - run: | - set -euo pipefail - docker run --rm \ - -v "${{ gitea.workspace }}:/src:ro" \ - -v "${DEPLOY_PATH}:/dest" \ - alpine:latest \ - sh -c "cd /src && find . -mindepth 1 -maxdepth 1 ! -name .git -exec cp -r {} /dest/ \; && DEST_OWNER=\$(stat -c '%u:%g' /dest) && chown -R \"\$DEST_OWNER\" /dest" - echo "OK synced to ${DEPLOY_PATH}" - - - name: Build and Deploy - run: | - set -euo pipefail - SCRIPT=/tmp/nexus-deploy-script.sh - printf '#!/bin/sh\n' > "$SCRIPT" - printf 'set -e\n' >> "$SCRIPT" - printf 'trap "rm -f /tmp/nexus-deploy-env" EXIT\n' >> "$SCRIPT" - printf 'cat > /tmp/nexus-deploy-env\n' >> "$SCRIPT" - printf '\n' >> "$SCRIPT" - printf '# ── Graceful shutdown (preserves DB volume integrity) ──\n' >> "$SCRIPT" - printf 'docker compose --env-file /tmp/nexus-deploy-env stop postgres 2>/dev/null || true\n' >> "$SCRIPT" - printf 'docker compose --env-file /tmp/nexus-deploy-env down --remove-orphans 2>/dev/null || true\n' >> "$SCRIPT" - printf 'echo "Postgres volume preserved (nexus-postgres) — no WAL reset"\n' >> "$SCRIPT" - printf '\n' >> "$SCRIPT" - printf 'echo "Deploying all services"\n' >> "$SCRIPT" - printf 'docker compose --env-file /tmp/nexus-deploy-env build --no-cache\n' >> "$SCRIPT" - printf 'docker compose --env-file /tmp/nexus-deploy-env up -d --force-recreate\n' >> "$SCRIPT" - printf '\n' >> "$SCRIPT" - printf 'echo "Waiting for services to become healthy (up to 180s)..."\n' >> "$SCRIPT" - printf 'for i in $(seq 1 36); do\n' >> "$SCRIPT" - printf ' STATUS=$(docker compose --env-file /tmp/nexus-deploy-env ps -a 2>/dev/null | tail -n +2)\n' >> "$SCRIPT" - printf ' if echo "$STATUS" | grep -q unhealthy; then\n' >> "$SCRIPT" - printf ' echo " [$i/36] Unhealthy containers - failing fast"\n' >> "$SCRIPT" - printf ' docker compose --env-file /tmp/nexus-deploy-env ps -a\n' >> "$SCRIPT" - printf ' docker compose --env-file /tmp/nexus-deploy-env logs --tail=30\n' >> "$SCRIPT" - printf ' exit 1\n' >> "$SCRIPT" - printf ' elif echo "$STATUS" | grep -q starting; then\n' >> "$SCRIPT" - printf ' echo " [$i/36] Still starting..."\n' >> "$SCRIPT" - printf ' sleep 5\n' >> "$SCRIPT" - printf ' else\n' >> "$SCRIPT" - printf ' echo "All containers healthy"\n' >> "$SCRIPT" - printf ' docker compose --env-file /tmp/nexus-deploy-env ps -a\n' >> "$SCRIPT" - printf ' exit 0\n' >> "$SCRIPT" - printf ' fi\n' >> "$SCRIPT" - printf 'done\n' >> "$SCRIPT" - printf 'echo "Timeout waiting for services"\n' >> "$SCRIPT" - printf 'docker compose --env-file /tmp/nexus-deploy-env ps -a\n' >> "$SCRIPT" - printf 'docker compose --env-file /tmp/nexus-deploy-env logs --tail=20\n' >> "$SCRIPT" - printf 'exit 1\n' >> "$SCRIPT" - chmod +x "$SCRIPT" - - docker run --rm \ - -v "${DEPLOY_PATH}:/workspace/nexus" \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v "${SCRIPT}:/deploy.sh:ro" \ - -w /workspace/nexus \ - -i \ - docker:cli \ - sh /deploy.sh < "${ENV_TMPFILE}" - - rm -f "$SCRIPT" - echo "OK deployed" - - - name: Clean up temp .env - if: always() - run: | - if [ -f "${ENV_TMPFILE}" ]; then - shred -u "${ENV_TMPFILE}" 2>/dev/null || rm -f "${ENV_TMPFILE}" - echo "OK cleaned" - fi - - - name: Health Check - run: | - echo "Health check..." - RETRY=0; MAX=6; WAIT=1 - while [ $RETRY -lt $MAX ]; do - RETRY=$((RETRY + 1)) - if curl -sf --max-time 10 https://nexus.noveria.net/health; then - echo "" - echo "OK Health check passed (attempt $RETRY/$MAX)" - exit 0 - fi - echo "Attempt $RETRY/$MAX failed, waiting ${WAIT}s..." - sleep $WAIT - NEXT=$((WAIT + RETRY)) - [ $NEXT -le 15 ] && WAIT=$NEXT || WAIT=15 - done - echo "ERROR Health check failed after $MAX attempts" - exit 1 - - - name: Smoke Test - run: | - PASS=0; FAIL=0; BASE="https://nexus.noveria.net" - check() { - local path="$1" label="$2" expected="${3:-200}" - local code - code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "${BASE}${path}") - printf " %-25s HTTP %s" "${label}:" "${code}" - if [ "$code" = "$expected" ]; then - echo " OK" - PASS=$((PASS + 1)) - else - echo " FAIL (expected $expected)" - FAIL=$((FAIL + 1)) - fi - } - check "/dashboard" "Dashboard" 200 - check "/health" "Health API" 200 - check "/api/v1/operations/snapshot" "Operations API (auth)" 401 - echo "Results: $PASS passed, $FAIL failed" - if [ "$FAIL" -gt 0 ]; then - echo "ERROR Smoke test failed" - exit 1 - fi - echo "OK Smoke test passed" - - - name: Summary - if: always() - run: | - echo "========================================" - echo " Deploy Summary" - echo "========================================" - echo " Version: v${{ steps.version.outputs.version }}" - echo " Git ref: main" - echo " Service: all" - echo " Trigger: Manual" - echo " Status: ${{ job.status }}" - echo "========================================" diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index a9ffc79..9b77509 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -7,7 +7,7 @@ run-name: 🚀 Deploy v2 by @${{ gitea.actor }} # # Triggers: # 1. AUTOMATIC after successful CI on main (workflow_run) -# → Uses safe defaults: patch bump, all services, main ref. +# → Deploys main with the VERSION already present in the repo. # → Commits marked with [skip ci] are filtered at job level # (prevents version-bump loops). # 2. MANUAL via workflow_dispatch with full parameter control. @@ -17,9 +17,8 @@ run-name: 🚀 Deploy v2 by @${{ gitea.actor }} # # Version Management: # The VERSION file in the repo root is the single source of truth. -# Version bumps happen in the Dev workflow BEFORE merge to main. -# The deploy workflow only reads, validates, and logs the version. -# The [skip ci] filter remains as a safety layer for auto-triggers. +# Deploy only reads, validates, and logs the version. +# Version changes happen before merge to main, not during deploy. # ─────────────────────────────────────────────────────── concurrency: group: deploy-production diff --git a/.gitignore b/.gitignore index 8b6f6fa..8aa1781 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,6 @@ # Environment .env -!.env.example !.env.template # IDE diff --git a/README.md b/README.md index 266fc15..4a00fc5 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ adapter-backed agent runtime, not a dependency of the frontend or domain model. > [`docs/architecture-board-first-orchestration.md`](docs/architecture-board-first-orchestration.md) > CI runs automatically on every push. CD can run **automatically after successful CI** -> on main (patch-bump default) or can be triggered **manually** (workflow_dispatch) with -> full parameter control. Main deploys bump/tag a release; arbitrary `git_ref` deploys -> stay read-only. Rollback and database backup are separate manual workflows. +> on main or can be triggered **manually** (workflow_dispatch). Deploy reads +> `VERSION` but does not mutate Git or create tags. Rollback and database backup +> are separate manual workflows. > See [phases/deployment.md](phases/deployment.md) for full CD documentation. ## Current foundation @@ -26,7 +26,7 @@ adapter-backed agent runtime, not a dependency of the frontend or domain model. ## Local/container start ```bash -cp .env.example .env +cp .env.template .env # Replace every placeholder, especially POSTGRES_PASSWORD, JWT_KEY and BOOTSTRAP_OWNER_EMAIL. docker compose up --build -d curl http://127.0.0.1:18880/health @@ -358,17 +358,15 @@ Deployment can happen automatically or manually: #### Auto-Deploy (after successful CI on main) - Triggered by `workflow_run` after `CI - Build & Test` succeeds on `main` -- Uses safe defaults: `patch` bump, all services, main ref -- Skips automatically if the triggering commit contains `[skip ci]` (version-bump commits) -- The version-bump commit itself uses `[skip ci]` → no infinite CI→Deploy→Bump→CI loops +- Deploys the current `main` version after CI succeeds. +- Skips automatically if the triggering commit contains `[skip ci]` +- The deploy workflow reads `VERSION`; it does not mutate Git, bump versions, or create tags #### Manual Deploy (`workflow_dispatch`) -1. DevOps triggers `Deploy to Production` in Gitea Actions (or Iris auto-approves) -2. Chooses version bump type: patch (default) / minor / major -3. Optionally scopes to a single service or specific git ref -4. Workflow bumps VERSION, creates git tag, builds and deploys -5. Health check + smoke test verify the deployment +1. DevOps triggers `Deploy Nexus v2` in Gitea Actions +2. Workflow validates `VERSION`, builds and deploys `main` +3. Health check + smoke test verify the deployment #### Rollback (`workflow_dispatch`) diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..0453039 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,10 @@ +bin/ +obj/ +*.user +*.suo +.vs/ +.vscode/ +.git/ +.gitignore +.env +*.log diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..f4a3f4a --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,9 @@ +node_modules/ +dist/ +.pnpm-store/ +.pnpm-home/ +.corepack-home/ +.git/ +.gitignore +.env +*.log diff --git a/ops/deploy.sh b/ops/deploy.sh deleted file mode 100755 index 668c0cb..0000000 --- a/ops/deploy.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash -# Nexus Deployment Script -# Auf dem VPS-HOST ausführen, nicht im Container! -set -e - -SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" -NEXUS_DIR="$(dirname "$SCRIPT_DIR")" - -echo "=== Nexus Deployment ===" -echo "Verzeichnis: $NEXUS_DIR" - -cd "$NEXUS_DIR" - -echo "" -echo "[1/4] Prüfe Konfiguration..." -docker compose config --quiet && echo " ✅ Konfiguration gültig" - -echo "" -echo "[2/4] Starte Stack (mit Healthchecks)..." -docker compose up -d --wait - -echo "" -echo "[3/4] Status nach Deployment..." -docker compose ps - -echo "" -echo "[4/4] Verifikation..." -check_code() { - local path="$1" - curl -s -o /dev/null -w "%{http_code}" "http://localhost:18880${path}" -} - -HEALTH_CODE=$(check_code /health) -DASHBOARD_CODE=$(check_code /dashboard) -OPS_CODE=$(check_code /api/v1/operations/snapshot) - -if [ "$HEALTH_CODE" = "200" ] && [ "$DASHBOARD_CODE" != "200" ]; then - WEB_CID="$(docker compose ps -q web || true)" - if [ -n "$WEB_CID" ]; then - WEB_STATE="$(docker inspect -f '{{.State.Status}}' "$WEB_CID" 2>/dev/null || true)" - if [ "$WEB_STATE" = "created" ]; then - echo " ℹ️ API healthy, aber web noch im Status 'created' — starte web nach" - docker compose up -d web - sleep 2 - DASHBOARD_CODE=$(check_code /dashboard) - OPS_CODE=$(check_code /api/v1/operations/snapshot) - fi - fi -fi - -echo " /health -> ${HEALTH_CODE}" -echo " /dashboard -> ${DASHBOARD_CODE}" -echo " /api/v1/operations/snapshot -> ${OPS_CODE}" - -if [ "$HEALTH_CODE" != "200" ] || [ "$DASHBOARD_CODE" != "200" ] || [ "$OPS_CODE" != "401" ]; then - echo " ❌ Verifikation fehlgeschlagen" - exit 1 -fi - -echo " ✅ Health-Check bestanden" -echo " ✅ Dashboard erreichbar" -echo " ✅ Operations API fordert Auth an" - -echo "" -echo "=== Deployment abgeschlossen ===" -echo "Dashboard: https://nexus.noveria.net/dashboard" -echo "Health-API: https://nexus.noveria.net/health" -echo "" -echo "Login-Informationen: docker compose logs api | grep 'Initial owner'" -echo "Status: docker compose ps" diff --git a/ops/install-ollama-host.sh b/ops/install-ollama-host.sh deleted file mode 100755 index 7b2fb08..0000000 --- a/ops/install-ollama-host.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -MODEL="${OLLAMA_MODEL:-qwen3:4b}" -BIND_ADDRESS="${OLLAMA_BIND_ADDRESS:-172.18.0.1:11434}" -BACKUP_DIR="/root/security-backups/ollama-$(date -u +%Y%m%dT%H%M%SZ)" - -if [[ "${EUID}" -ne 0 ]]; then - echo "Run this script as root on the Ubuntu host." >&2 - exit 1 -fi - -mkdir -p "${BACKUP_DIR}" -if systemctl cat ollama.service >/dev/null 2>&1; then - systemctl cat ollama.service > "${BACKUP_DIR}/ollama.service.before.txt" -fi -if [[ -d /etc/systemd/system/ollama.service.d ]]; then - cp -a /etc/systemd/system/ollama.service.d "${BACKUP_DIR}/" -fi - -if ! command -v ollama >/dev/null 2>&1; then - curl -fsSL https://ollama.com/install.sh -o /tmp/ollama-install.sh - sh /tmp/ollama-install.sh -fi - -install -d -m 755 /etc/systemd/system/ollama.service.d -cat > /etc/systemd/system/ollama.service.d/10-openclaw.conf </dev/null; then - break - fi - if [[ "${attempt}" -eq "${max_attempts}" ]]; then - systemctl status ollama --no-pager - exit 1 - fi - attempt=$((attempt + 1)) - sleep 2 -done - -OLLAMA_HOST="http://${BIND_ADDRESS}" ollama pull "${MODEL}" -OLLAMA_HOST="http://${BIND_ADDRESS}" ollama show "${MODEL}" >/dev/null -curl -fsS "http://${BIND_ADDRESS}/api/tags" -echo -echo "Ollama ${MODEL} is ready on ${BIND_ADDRESS}. Backup: ${BACKUP_DIR}" diff --git a/ops/nginx-landing.conf b/ops/nginx-landing.conf deleted file mode 100644 index 70bf3f1..0000000 --- a/ops/nginx-landing.conf +++ /dev/null @@ -1,55 +0,0 @@ -# ============================================================================== -# Noveria.net Landingpage — Nginx Server Block -# ============================================================================== -# Diese Config gehört in den Host-Nginx unter /etc/nginx/sites-available/ -# und muss via Symlink nach /etc/nginx/sites-enabled/ aktiviert werden. -# -# WICHTIG: Falls "noveria.net" oder "www.noveria.net" bereits in einem anderen -# Serverblock (z.B. dem nexus.noveria.net-Block) als server_name auftaucht, -# muss es dort entfernt werden, sonst schlägt nginx -t fehl. -# ============================================================================== - -server { - listen 443 ssl http2; - server_name noveria.net www.noveria.net; - - # SSL (gleiche Zertifikate wie nexus) - ssl_certificate /etc/letsencrypt/live/noveria.net/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/noveria.net/privkey.pem; - include /etc/nginx/snippets/ssl-params.conf; - - # Security Header - add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-Content-Type-Options "nosniff" always; - add_header Referrer-Policy "no-referrer-when-downgrade" always; - - location / { - proxy_pass http://127.0.0.1:18881; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} - -# HTTP → HTTPS redirect -server { - listen 80; - server_name noveria.net www.noveria.net; - return 301 https://$host$request_uri; -} - -# ============================================================================== -# Diagnose-Kommandos (auf dem Host auszuführen, nicht im Container!) -# ============================================================================== -# 1. Prüfen ob noveria.net bereits in bestehender Config referenziert wird -# grep -rn "noveria.net" /etc/nginx/sites-available/ -# grep -rn "www.noveria.net" /etc/nginx/sites-available/ -# -# 2. Config testen nach Änderung -# nginx -t -# -# 3. Nginx neuladen -# systemctl reload nginx -# ============================================================================== diff --git a/ops/nginx-nexus.conf b/ops/nginx-nexus.conf deleted file mode 100644 index 9839f80..0000000 --- a/ops/nginx-nexus.conf +++ /dev/null @@ -1,81 +0,0 @@ -# /etc/nginx/sites-available/nexus.noveria.net -# Symlink: ln -s /etc/nginx/sites-available/nexus.noveria.net /etc/nginx/sites-enabled/ - -server { - listen 80; - server_name nexus.noveria.net; - - # Let's Encrypt challenge - location /.well-known/acme-challenge/ { - root /var/www/html; - } - - location / { - return 301 https://$host$request_uri; - } -} - -server { - listen 443 ssl http2; - server_name nexus.noveria.net; - - # SSL wird per certbot automatisch befüllt - ssl_certificate /etc/letsencrypt/live/nexus.noveria.net/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/nexus.noveria.net/privkey.pem; - - # Security-Header - add_header Strict-Transport-Security "max-age=63072000" always; - add_header X-Content-Type-Options nosniff; - add_header X-Frame-Options DENY; - - client_max_body_size 16m; - - # Bridge-Endpunkte: Gateway-zu-Backend-Agent-Pfad - # X-Agent-Id wird durchgereicht für Agent-Identität - location /api/bridge/ { - proxy_pass http://127.0.0.1:18880; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Agent-Id $http_x_agent_id; - proxy_buffering off; - proxy_read_timeout 120s; - } - - # Dashboard SSE stream: single dedicated non-buffered block. - location = /api/dashboard/live { - proxy_pass http://127.0.0.1:18880; - proxy_http_version 1.1; - proxy_set_header Connection ""; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_buffering off; - proxy_cache off; - proxy_read_timeout 1h; - proxy_send_timeout 1h; - add_header Cache-Control "no-cache, no-store, must-revalidate" always; - add_header X-Accel-Buffering no always; - } - - location / { - proxy_pass http://127.0.0.1:18880; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - - # API-Direktzugriff falls nötig - location /api/ { - proxy_pass http://127.0.0.1:18880; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} diff --git a/ops/setup-https.sh b/ops/setup-https.sh deleted file mode 100755 index 20c57fd..0000000 --- a/ops/setup-https.sh +++ /dev/null @@ -1,107 +0,0 @@ -#!/bin/bash -# HTTPS-Setup für nexus.noveria.net -# Auf dem VPS-HOST ausführen! - -set -e - -echo "=== HTTPS Setup für nexus.noveria.net ===" - -SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" - -# 1. Zuerst nur HTTP-Config ausrollen (keine SSL-Referenz!) -echo "[1/5] Installiere HTTP-only Nginx-Config..." -sudo tee /etc/nginx/sites-available/nexus.noveria.net > /dev/null << 'NGINXEOF' -server { - listen 80; - server_name nexus.noveria.net; - - location /.well-known/acme-challenge/ { - root /var/www/html; - } - - location / { - proxy_pass http://127.0.0.1:18880; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} -NGINXEOF - -sudo ln -sf /etc/nginx/sites-available/nexus.noveria.net /etc/nginx/sites-enabled/ -sudo nginx -t && sudo systemctl reload nginx -echo " ✅ HTTP-Config aktiv" - -# 2. Firewall -echo "[2/5] Firewall..." -if command -v ufw &>/dev/null; then - sudo ufw allow 80/tcp 2>/dev/null || true - sudo ufw allow 443/tcp 2>/dev/null || true - echo " ✅ ufw: 80+443 offen" -else - echo " ⏭ ufw nicht installiert" -fi - -# 3. HTTP-Test -echo "[3/5] Teste HTTP..." -sleep 1 -STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://nexus.noveria.net) -echo " HTTP-Status: $STATUS" - -# 4. Zertifikat holen -echo "[4/5] Fordere Let's-Encrypt-Zertifikat an..." -sudo certbot certonly --webroot -w /var/www/html -d nexus.noveria.net --non-interactive --agree-tos --email vmbao62@hotmail.de 2>&1 || { - echo " ⚠️ certbot fehlgeschlagen – manuell nachholen:" - echo " sudo certbot --nginx -d nexus.noveria.net" - exit 1 -} -echo " ✅ Zertifikat erhalten" - -# 5. HTTPS-Config ausrollen -echo "[5/5] Aktiviere HTTPS-Config..." -sudo tee /etc/nginx/sites-available/nexus.noveria.net > /dev/null << 'NGINXSSL' -server { - listen 80; - server_name nexus.noveria.net; - location /.well-known/acme-challenge/ { - root /var/www/html; - } - location / { - return 301 https://$host$request_uri; - } -} - -server { - listen 443 ssl http2; - server_name nexus.noveria.net; - - ssl_certificate /etc/letsencrypt/live/nexus.noveria.net/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/nexus.noveria.net/privkey.pem; - - add_header Strict-Transport-Security "max-age=63072000" always; - add_header X-Content-Type-Options nosniff; - add_header X-Frame-Options DENY; - - client_max_body_size 16m; - - location / { - proxy_pass http://127.0.0.1:18880; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} -NGINXSSL - -sudo nginx -t && sudo systemctl reload nginx -echo " ✅ HTTPS aktiv" - -# Test -echo "" -sleep 2 -curl -s -o /dev/null -w "HTTPS-Status: %{http_code}\n" https://nexus.noveria.net -echo "" -echo "=== Fertig ===" -echo "Nexus: https://nexus.noveria.net" diff --git a/phases/deployment.md b/phases/deployment.md index 1530cfd..006f95b 100644 --- a/phases/deployment.md +++ b/phases/deployment.md @@ -7,10 +7,9 @@ ## 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 +- **CD auto + manuell**: Automatischer Deploy nach CI-Success auf main; manueller Deploy via `workflow_dispatch` +- **Loop-Schutz**: Commits mit `[skip ci]` werden von Auto-Deploys ignoriert +- Deploy liest und validiert `VERSION`, mutiert aber weder Git noch Tags - **Rollback** als eigener Workflow, manuell triggerbar - **Database-Backup** als eigener Workflow, manuell triggerbar (optionaler Nightly-Schedule) @@ -20,7 +19,7 @@ **Trigger**: - **Automatisch**: Nach erfolgreicher CI (`workflow_run` auf `CI - Build & Test`) - → Default-Parameter: patch bump, all services, main ref + → Deployt `main` mit dem im Repo gesetzten `VERSION`-Wert - **Manuell**: Via Gitea Actions → `workflow_dispatch` **Loop-Schutz**: @@ -28,26 +27,19 @@ - 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 | +**Inputs**: keine. Der manuelle Deploy nutzt denselben Main-Deploy-Pfad wie der Auto-Deploy. **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 +2. Checkout von `main` +3. `VERSION` lesen und SemVer validieren +4. **Safe Secret Handling**: `.env` wird aus Secret-Umgebungsvariablen in `/tmp/nexus-deploy-env` geschrieben (mode 600), **NICHT** im Workspace +5. Code-Sync zum Host-Deploy-Pfad +6. `docker compose build && up -d --force-recreate` +7. `.env`-Tempfile wird mit `shred` gelöscht +8. Health-Check (Backoff, 6 Versuche) +9. Smoke-Test (`/dashboard`, `/health`, `/api/v1/operations/snapshot` erwartet `401`) +10. Bei Fehler: Reviewer-Handoff-Meldung mit Job-URL ### Backup (`.gitea/workflows/backup.yaml`) @@ -222,7 +214,7 @@ Stelle sicher, dass `.env` existiert und alle `***`-Platzhalter ersetzt sind. 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`](/home/node/.openclaw/workspace/nexus/ops/deploy.sh) verifiziert deshalb jetzt nicht mehr nur `/health`, sondern auch `/dashboard` und den Auth-Schutz der Operations-API. +- Der CD-Pfad verifiziert deshalb nicht mehr nur `/health`, sondern auch `/dashboard` und den Auth-Schutz der Operations-API. ## Offene Arbeit