chore: simplify nexus cicd pipeline
This commit is contained in:
@@ -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=
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 "========================================"
|
||||
@@ -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
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
# Environment
|
||||
.env
|
||||
!.env.example
|
||||
!.env.template
|
||||
|
||||
# IDE
|
||||
|
||||
@@ -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`)
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
bin/
|
||||
obj/
|
||||
*.user
|
||||
*.suo
|
||||
.vs/
|
||||
.vscode/
|
||||
.git/
|
||||
.gitignore
|
||||
.env
|
||||
*.log
|
||||
@@ -0,0 +1,9 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.pnpm-store/
|
||||
.pnpm-home/
|
||||
.corepack-home/
|
||||
.git/
|
||||
.gitignore
|
||||
.env
|
||||
*.log
|
||||
@@ -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"
|
||||
@@ -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 <<OVERRIDE
|
||||
[Service]
|
||||
Environment="OLLAMA_HOST=${BIND_ADDRESS}"
|
||||
Environment="OLLAMA_KEEP_ALIVE=15m"
|
||||
OVERRIDE
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now ollama
|
||||
systemctl restart ollama
|
||||
|
||||
max_attempts=30
|
||||
attempt=1
|
||||
while [[ "${attempt}" -le "${max_attempts}" ]]; do
|
||||
if curl -fsS "http://${BIND_ADDRESS}/api/tags" >/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}"
|
||||
@@ -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
|
||||
# ==============================================================================
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
+15
-23
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user