fix: repair gitea deploy pipeline
This commit is contained in:
Executable
+129
@@ -0,0 +1,129 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
DEPLOY_PATH="${DEPLOY_PATH:-/home/projekte_bao/openclaw/data/openclaw/workspace/nexus}"
|
||||||
|
ENV_TMPFILE="${ENV_TMPFILE:-/tmp/nexus-deploy-env}"
|
||||||
|
COMPOSE_SCRIPT="${COMPOSE_SCRIPT:-/tmp/nexus-compose-deploy.sh}"
|
||||||
|
BASE_URL="${BASE_URL:-https://nexus.noveria.net}"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if [ -f "$ENV_TMPFILE" ]; then
|
||||||
|
shred -u "$ENV_TMPFILE" 2>/dev/null || rm -f "$ENV_TMPFILE"
|
||||||
|
fi
|
||||||
|
rm -f "$COMPOSE_SCRIPT"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
|
require_env() {
|
||||||
|
name="$1"
|
||||||
|
eval "value=\${$name:-}"
|
||||||
|
if [ -z "$value" ]; then
|
||||||
|
echo "Missing required environment variable: $name" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
require_env ENV_POSTGRES_PASSWORD
|
||||||
|
require_env ENV_JWT_KEY
|
||||||
|
|
||||||
|
if [ ! -f VERSION ]; then
|
||||||
|
echo "VERSION file not found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION="$(tr -d '[:space:]' < VERSION)"
|
||||||
|
if ! echo "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||||
|
echo "Invalid VERSION value: $VERSION" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
GIT_REF="$(git rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
||||||
|
echo "Deploying Nexus v$VERSION from $GIT_REF"
|
||||||
|
|
||||||
|
umask 077
|
||||||
|
cat > "$ENV_TMPFILE" <<EOF_ENV
|
||||||
|
POSTGRES_DB=nexus
|
||||||
|
POSTGRES_USER=nexus
|
||||||
|
POSTGRES_PASSWORD=${ENV_POSTGRES_PASSWORD}
|
||||||
|
JWT_KEY=${ENV_JWT_KEY}
|
||||||
|
JWT_ISSUER=nexus
|
||||||
|
JWT_AUDIENCE=nexus-web
|
||||||
|
BOOTSTRAP_OWNER_EMAIL=vmbao62@hotmail.de
|
||||||
|
OPENCLAW_BASE_URL=http://host.docker.internal:18789
|
||||||
|
OPENCLAW_GATEWAY_TOKEN=${ENV_OPENCLAW_TOKEN:-}
|
||||||
|
OPENCLAW_GATEWAY_PASSWORD=
|
||||||
|
EOF_ENV
|
||||||
|
|
||||||
|
echo "Syncing source to deploy path: $DEPLOY_PATH"
|
||||||
|
docker run --rm \
|
||||||
|
-v "$PWD:/src:ro" \
|
||||||
|
-v "$DEPLOY_PATH:/dest" \
|
||||||
|
alpine:3.20 \
|
||||||
|
sh -c '
|
||||||
|
set -eu
|
||||||
|
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 "Building and starting Docker compose stack"
|
||||||
|
cat > "$COMPOSE_SCRIPT" <<'EOF_DEPLOY'
|
||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
cat > /tmp/nexus-deploy-env
|
||||||
|
docker compose --env-file /tmp/nexus-deploy-env build
|
||||||
|
docker compose --env-file /tmp/nexus-deploy-env up -d --force-recreate --remove-orphans --wait
|
||||||
|
docker compose --env-file /tmp/nexus-deploy-env ps
|
||||||
|
rm -f /tmp/nexus-deploy-env
|
||||||
|
EOF_DEPLOY
|
||||||
|
|
||||||
|
docker run --rm \
|
||||||
|
-v "$DEPLOY_PATH:/workspace/nexus" \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-v "$COMPOSE_SCRIPT:/deploy.sh:ro" \
|
||||||
|
-w /workspace/nexus \
|
||||||
|
-i \
|
||||||
|
docker:cli \
|
||||||
|
sh /deploy.sh < "$ENV_TMPFILE"
|
||||||
|
|
||||||
|
echo "Checking live health"
|
||||||
|
retry=0
|
||||||
|
while [ "$retry" -lt 6 ]; do
|
||||||
|
retry=$((retry + 1))
|
||||||
|
if curl -fsS --max-time 10 "$BASE_URL/health" >/dev/null; then
|
||||||
|
echo "Health check passed"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [ "$retry" -eq 6 ]; then
|
||||||
|
echo "Health check failed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep "$retry"
|
||||||
|
done
|
||||||
|
|
||||||
|
pass=0
|
||||||
|
fail=0
|
||||||
|
check() {
|
||||||
|
path="$1"
|
||||||
|
expected="$2"
|
||||||
|
label="$3"
|
||||||
|
code="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 "$BASE_URL$path")"
|
||||||
|
printf '%-28s HTTP %s\n' "$label" "$code"
|
||||||
|
if [ "$code" = "$expected" ]; then
|
||||||
|
pass=$((pass + 1))
|
||||||
|
else
|
||||||
|
fail=$((fail + 1))
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check "/dashboard" "200" "Dashboard"
|
||||||
|
check "/health" "200" "Health"
|
||||||
|
check "/api/v1/operations/snapshot" "401" "Operations auth"
|
||||||
|
|
||||||
|
if [ "$fail" -ne 0 ]; then
|
||||||
|
echo "Smoke test failed: $fail failed, $pass passed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Nexus v$VERSION deployed and verified"
|
||||||
@@ -43,7 +43,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
backup:
|
backup:
|
||||||
name: Backup PostgreSQL
|
name: Backup PostgreSQL
|
||||||
runs-on: ubuntu-latest
|
runs-on: linux
|
||||||
env:
|
env:
|
||||||
ENV_TMPFILE: /tmp/nexus-backup-env
|
ENV_TMPFILE: /tmp/nexus-backup-env
|
||||||
ENV_POSTGRES_PASSWORD: ${{ secrets.ENV_POSTGRES_PASSWORD }}
|
ENV_POSTGRES_PASSWORD: ${{ secrets.ENV_POSTGRES_PASSWORD }}
|
||||||
|
|||||||
@@ -97,3 +97,25 @@ jobs:
|
|||||||
else
|
else
|
||||||
echo "✅ No obvious secrets found"
|
echo "✅ No obvious secrets found"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
name: Deploy Nexus
|
||||||
|
runs-on: linux
|
||||||
|
needs: [backend, frontend, security]
|
||||||
|
concurrency:
|
||||||
|
group: deploy-production
|
||||||
|
cancel-in-progress: false
|
||||||
|
if: |
|
||||||
|
github.event_name == 'push' &&
|
||||||
|
github.ref == 'refs/heads/main'
|
||||||
|
env:
|
||||||
|
DEPLOY_PATH: /home/projekte_bao/openclaw/data/openclaw/workspace/nexus
|
||||||
|
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
|
||||||
|
|
||||||
|
- name: Deploy after green CI
|
||||||
|
run: sh .gitea/scripts/deploy-nexus.sh
|
||||||
|
|||||||
@@ -1,364 +1,28 @@
|
|||||||
name: Deploy Nexus v2
|
name: Deploy Nexus Manual
|
||||||
run-name: 🚀 Deploy v2 by @${{ gitea.actor }}
|
run-name: Deploy Nexus manually by @${{ gitea.actor }}
|
||||||
|
|
||||||
# ───────────────────────────────────────────────────────
|
|
||||||
# Owner: DevOps (Architekt)
|
|
||||||
# CD v3 — 2026-06-13
|
|
||||||
#
|
|
||||||
# Triggers:
|
|
||||||
# 1. AUTOMATIC after successful CI on main (workflow_run)
|
|
||||||
# → 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.
|
|
||||||
#
|
|
||||||
# Concurrency: one deploy at a time.
|
|
||||||
# Queued deploys wait — no race conditions with parallel builds.
|
|
||||||
#
|
|
||||||
# Version Management:
|
|
||||||
# The VERSION file in the repo root is the single source of truth.
|
|
||||||
# Deploy only reads, validates, and logs the version.
|
|
||||||
# Version changes happen before merge to main, not during deploy.
|
|
||||||
# ───────────────────────────────────────────────────────
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: deploy-production
|
group: deploy-production
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# ── Auto-Trigger: after successful CI on main ──
|
|
||||||
workflow_run:
|
|
||||||
workflows: ["CI - Build & Test"]
|
|
||||||
types: [completed]
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
# ── Manual Trigger (full control) ──
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
name: Deploy Nexus
|
name: Deploy Nexus
|
||||||
runs-on: ubuntu-latest
|
runs-on: linux
|
||||||
if: |
|
|
||||||
(github.event_name == 'workflow_dispatch') ||
|
|
||||||
(github.event_name == 'workflow_run' &&
|
|
||||||
github.event.workflow_run.conclusion == 'success' &&
|
|
||||||
!contains(github.event.workflow_run.head_commit.message, '[skip ci]'))
|
|
||||||
|
|
||||||
# ── Env for the deploy target path ──
|
|
||||||
env:
|
env:
|
||||||
DEPLOY_PATH: /home/projekte_bao/openclaw/data/openclaw/workspace/nexus
|
DEPLOY_PATH: /home/projekte_bao/openclaw/data/openclaw/workspace/nexus
|
||||||
ENV_TMPFILE: /tmp/nexus-deploy-env
|
|
||||||
ENV_POSTGRES_PASSWORD: ${{ secrets.ENV_POSTGRES_PASSWORD }}
|
ENV_POSTGRES_PASSWORD: ${{ secrets.ENV_POSTGRES_PASSWORD }}
|
||||||
ENV_JWT_KEY: ${{ secrets.ENV_JWT_KEY }}
|
ENV_JWT_KEY: ${{ secrets.ENV_JWT_KEY }}
|
||||||
ENV_OPENCLAW_TOKEN: ${{ secrets.ENV_OPENCLAW_TOKEN }}
|
ENV_OPENCLAW_TOKEN: ${{ secrets.ENV_OPENCLAW_TOKEN }}
|
||||||
# Owner password is not injected at deploy time.
|
|
||||||
# After first seed, the database is the only password source.
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# ═══════════════════════════════════════════════════
|
- name: Checkout main
|
||||||
# Step 1: Checkout
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: main
|
ref: main
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
fetch-tags: true
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════
|
- name: Deploy main
|
||||||
# Step 2: Set up Git identity
|
run: sh .gitea/scripts/deploy-nexus.sh
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
- name: Configure Git
|
|
||||||
run: |
|
|
||||||
git config user.email "devops@noveria.net"
|
|
||||||
git config user.name "DevOps"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
# Step 3: Resolve deploy version
|
|
||||||
#
|
|
||||||
# Reads VERSION from repo root — the single source of truth.
|
|
||||||
# Validates semver format, logs version + git metadata.
|
|
||||||
# No git mutation: version bumps happen in the Dev workflow.
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
- name: Resolve Version
|
|
||||||
id: version
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# 1. Check VERSION exists
|
|
||||||
if [ ! -f VERSION ]; then
|
|
||||||
echo "❌ VERSION file not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 2. Read and validate semver format
|
|
||||||
VERSION=$(cat VERSION | tr -d '[:space:]')
|
|
||||||
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
|
|
||||||
echo "❌ Invalid semver in VERSION: '$VERSION'"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 3. Log version, git ref, and describe
|
|
||||||
GIT_REF=$(git rev-parse --short HEAD)
|
|
||||||
GIT_DESCRIBE=$(git describe --always --dirty)
|
|
||||||
|
|
||||||
echo "📦 Deploy version: v${VERSION}"
|
|
||||||
echo "🔖 Git ref: ${GIT_REF}"
|
|
||||||
echo "🏷️ Git describe: ${GIT_DESCRIBE}"
|
|
||||||
|
|
||||||
# 4. Set outputs for downstream steps
|
|
||||||
echo "version=${VERSION}" >> "$GITEA_OUTPUT"
|
|
||||||
echo "mutated_main=false" >> "$GITEA_OUTPUT"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
# Step 4: Build .env from secrets (SAFE)
|
|
||||||
#
|
|
||||||
# Secrets are written to /tmp/nexus-deploy-env — NEVER
|
|
||||||
# to a file inside the workspace that gets rsync'd to
|
|
||||||
# the host. The temp file is deleted immediately after
|
|
||||||
# compose operations complete.
|
|
||||||
#
|
|
||||||
# Owner password is deliberately omitted so production deploys
|
|
||||||
# cannot overwrite the persisted DB password.
|
|
||||||
# Other secrets (POSTGRES_PASSWORD, JWT_KEY, OPENCLAW_TOKEN)
|
|
||||||
# come from Gitea secrets.
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
- name: Prepare .env (secrets → temp file)
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
cat > "${ENV_TMPFILE}" <<EOF
|
|
||||||
# Nexus Production Environment — auto-generated by CD pipeline
|
|
||||||
# Managed via Gitea Secrets → do NOT edit manually.
|
|
||||||
# This file lives in /tmp and is removed after deploy completes.
|
|
||||||
POSTGRES_DB=nexus
|
|
||||||
POSTGRES_USER=nexus
|
|
||||||
POSTGRES_PASSWORD=${ENV_POSTGRES_PASSWORD}
|
|
||||||
JWT_KEY=${ENV_JWT_KEY}
|
|
||||||
JWT_ISSUER=nexus
|
|
||||||
JWT_AUDIENCE=nexus-web
|
|
||||||
BOOTSTRAP_OWNER_EMAIL=vmbao62@hotmail.de
|
|
||||||
OPENCLAW_BASE_URL=http://host.docker.internal:18789
|
|
||||||
OPENCLAW_GATEWAY_TOKEN=${ENV_OPENCLAW_TOKEN}
|
|
||||||
OPENCLAW_GATEWAY_PASSWORD=
|
|
||||||
EOF
|
|
||||||
|
|
||||||
chmod 600 "${ENV_TMPFILE}"
|
|
||||||
echo "✅ .env written to ${ENV_TMPFILE} (mode 600)"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
# Step 5: Sync code to host (without .env in workspace)
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
- 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 "✅ Code synced to ${DEPLOY_PATH}"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
# Step 6: Build & Deploy
|
|
||||||
#
|
|
||||||
# The temp .env file is bind-mounted read-only into the
|
|
||||||
# docker:cli container so compose can resolve variables.
|
|
||||||
# It is NEVER written into the workspace directory.
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
- name: Build & Deploy
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
BUILD_ARGS=""
|
|
||||||
SERVICE_ARG=""
|
|
||||||
|
|
||||||
# Write the deploy script to a file to avoid nested quoting issues
|
|
||||||
cat > /tmp/nexus-deploy-script.sh << 'DEPLOYSCRIPT'
|
|
||||||
#!/bin/sh
|
|
||||||
set -e
|
|
||||||
trap 'rm -f /tmp/nexus-deploy-env' EXIT
|
|
||||||
cat > /tmp/nexus-deploy-env
|
|
||||||
|
|
||||||
# ── Graceful shutdown (preserves DB volume integrity) ──
|
|
||||||
docker compose --env-file /tmp/nexus-deploy-env stop postgres 2>/dev/null || true
|
|
||||||
docker compose --env-file /tmp/nexus-deploy-env down --remove-orphans 2>/dev/null || true
|
|
||||||
echo "Postgres volume preserved (nexus-postgres) — no WAL reset"
|
|
||||||
|
|
||||||
BUILD_ARGS="${DEPLOY_BUILD_ARGS:-}"
|
|
||||||
SERVICE="${DEPLOY_SERVICE:-}"
|
|
||||||
|
|
||||||
if [ -n "$SERVICE" ]; then
|
|
||||||
echo "Deploying service: $SERVICE"
|
|
||||||
docker compose --env-file /tmp/nexus-deploy-env build $BUILD_ARGS $SERVICE
|
|
||||||
docker compose --env-file /tmp/nexus-deploy-env up -d --force-recreate $SERVICE
|
|
||||||
else
|
|
||||||
echo 'Deploying all services'
|
|
||||||
docker compose --env-file /tmp/nexus-deploy-env build $BUILD_ARGS
|
|
||||||
docker compose --env-file /tmp/nexus-deploy-env up -d --force-recreate
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo 'Waiting for services to become healthy (up to 180s)...'
|
|
||||||
for i in $(seq 1 36); do
|
|
||||||
STATUS=$(docker compose --env-file /tmp/nexus-deploy-env ps -a 2>/dev/null | tail -n +2)
|
|
||||||
if echo "$STATUS" | grep -q 'unhealthy'; then
|
|
||||||
echo " [$i/36] Unhealthy containers - failing fast"
|
|
||||||
docker compose --env-file /tmp/nexus-deploy-env ps -a
|
|
||||||
docker compose --env-file /tmp/nexus-deploy-env logs --tail=30
|
|
||||||
exit 1
|
|
||||||
elif echo "$STATUS" | grep -q 'starting'; then
|
|
||||||
echo " [$i/36] Still starting..."
|
|
||||||
sleep 5
|
|
||||||
else
|
|
||||||
echo 'All containers healthy'
|
|
||||||
docker compose --env-file /tmp/nexus-deploy-env ps -a
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
echo 'Timeout waiting for services'
|
|
||||||
docker compose --env-file /tmp/nexus-deploy-env ps -a
|
|
||||||
docker compose --env-file /tmp/nexus-deploy-env logs --tail=20
|
|
||||||
exit 1
|
|
||||||
DEPLOYSCRIPT
|
|
||||||
|
|
||||||
docker run --rm \
|
|
||||||
-e "DEPLOY_BUILD_ARGS=${BUILD_ARGS:-}" \
|
|
||||||
-e "DEPLOY_SERVICE=${SERVICE_ARG:-}" \
|
|
||||||
-v "${DEPLOY_PATH}:/workspace/nexus" \
|
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
||||||
-v /tmp/nexus-deploy-script.sh:/deploy.sh:ro \
|
|
||||||
-w /workspace/nexus \
|
|
||||||
-i \
|
|
||||||
docker:cli \
|
|
||||||
sh /deploy.sh < "${ENV_TMPFILE}"
|
|
||||||
|
|
||||||
rm -f /tmp/nexus-deploy-script.sh
|
|
||||||
|
|
||||||
echo "✅ Docker compose up completed"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
# Step 7: Clean up temp .env
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
- 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 "🧹 Temp .env removed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
# Step 8: Health Check (exponential backoff)
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
- 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 "✅ Health check passed (attempt $RETRY/$MAX)"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "⏳ Attempt $RETRY/$MAX failed, waiting ${WAIT}s..."
|
|
||||||
sleep $WAIT
|
|
||||||
# Fibonacci-ish backoff: 1,2,3,5,8,13
|
|
||||||
NEXT=$((WAIT + RETRY))
|
|
||||||
[ $NEXT -le 15 ] && WAIT=$NEXT || WAIT=15
|
|
||||||
done
|
|
||||||
echo "❌ Health check failed after $MAX attempts"
|
|
||||||
exit 1
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
# Step 9: Smoke Test
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
- name: Smoke Test
|
|
||||||
run: |
|
|
||||||
echo "🔍 Smoke test..."
|
|
||||||
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 " ✅"
|
|
||||||
PASS=$((PASS + 1))
|
|
||||||
else
|
|
||||||
echo " ❌ (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 ""
|
|
||||||
echo "Results: $PASS passed, $FAIL failed"
|
|
||||||
if [ "$FAIL" -gt 0 ]; then
|
|
||||||
echo "❌ Smoke test failed!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "✅ Smoke test passed — v${{ steps.version.outputs.version }} is live"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
# Step 10: Deployment Summary
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
- name: Deployment Summary
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
TRIGGER="${{ github.event_name == 'workflow_run' && 'Auto (CI success)' || 'Manual (workflow_dispatch)' }}"
|
|
||||||
echo ""
|
|
||||||
echo "═══════════════════════════════════════"
|
|
||||||
echo " 📦 Deploy Summary"
|
|
||||||
echo "═══════════════════════════════════════"
|
|
||||||
echo " Version: v${{ steps.version.outputs.version }}"
|
|
||||||
echo " Git ref: main"
|
|
||||||
echo " Service: all"
|
|
||||||
echo " Trigger: ${TRIGGER}"
|
|
||||||
echo " Actor: @${{ gitea.actor }}"
|
|
||||||
echo " Status: ${{ job.status }}"
|
|
||||||
echo "═══════════════════════════════════════"
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
# Step 11: Failure → Reviewer Handoff
|
|
||||||
#
|
|
||||||
# On failure: DevOps (Architekt) analyses the log,
|
|
||||||
# notifies Reviewer (Code-Fixer) with the exact error.
|
|
||||||
# This output provides a ready-to-copy message.
|
|
||||||
# ═══════════════════════════════════════════════════
|
|
||||||
- name: 🔴 Failure — Reviewer Handoff
|
|
||||||
if: failure()
|
|
||||||
run: |
|
|
||||||
echo ""
|
|
||||||
echo "┌─────────────────────────────────────────────────────────────┐"
|
|
||||||
echo "│ 🔴 DEPLOY FAILED — Reviewer muss fixen │"
|
|
||||||
echo "├─────────────────────────────────────────────────────────────┤"
|
|
||||||
echo "│ │"
|
|
||||||
echo "│ Version: v${{ steps.version.outputs.version }}"
|
|
||||||
echo "│ Job: ${{ gitea.server_url }}/${{ gitea.repository }}/actions/runs/${{ gitea.run_id }}"
|
|
||||||
echo "│ │"
|
|
||||||
echo "│ → DevOps (Architekt) analysiert den Fehler │"
|
|
||||||
echo "│ → Reviewer (Code-Fixer) behebt das Problem │"
|
|
||||||
echo "│ → DevOps verifiziert mit neuem Deploy │"
|
|
||||||
echo "│ │"
|
|
||||||
echo "│ Rollback: Trigger 'Rollback to Previous Version' │"
|
|
||||||
echo "│ workflow manuell in Gitea Actions. │"
|
|
||||||
echo "│ │"
|
|
||||||
echo "└─────────────────────────────────────────────────────────────┘"
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
rollback:
|
rollback:
|
||||||
name: Rollback Nexus
|
name: Rollback Nexus
|
||||||
runs-on: ubuntu-latest
|
runs-on: linux
|
||||||
env:
|
env:
|
||||||
DEPLOY_PATH: /home/projekte_bao/openclaw/data/openclaw/workspace/nexus
|
DEPLOY_PATH: /home/projekte_bao/openclaw/data/openclaw/workspace/nexus
|
||||||
ENV_TMPFILE: /tmp/nexus-rollback-env
|
ENV_TMPFILE: /tmp/nexus-rollback-env
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ adapter-backed agent runtime, not a dependency of the frontend or domain model.
|
|||||||
> Backend-Brücke und Gateway-Integration geprüft. Siehe
|
> Backend-Brücke und Gateway-Integration geprüft. Siehe
|
||||||
> [`docs/architecture-board-first-orchestration.md`](docs/architecture-board-first-orchestration.md)
|
> [`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**
|
> CI runs automatically on every push. CD runs **inside the green CI run**
|
||||||
> on main or can be triggered **manually** (workflow_dispatch). Deploy reads
|
> on main or can be triggered **manually** (workflow_dispatch). Deploy reads
|
||||||
> `VERSION` but does not mutate Git or create tags. Rollback and database backup
|
> `VERSION` but does not mutate Git or create tags. Rollback and database backup
|
||||||
> are separate manual workflows.
|
> are separate manual workflows.
|
||||||
@@ -351,20 +351,21 @@ Every push to `main` triggers `.gitea/workflows/ci.yaml`:
|
|||||||
|
|
||||||
CI must never break. If it does, Reviewer fixes.
|
CI must never break. If it does, Reviewer fixes.
|
||||||
|
|
||||||
### CD — Auto + Manual (CD v3)
|
### CD — Auto + Manual (CD v4)
|
||||||
|
|
||||||
Deployment can happen automatically or manually:
|
Deployment can happen automatically or manually:
|
||||||
|
|
||||||
#### Auto-Deploy (after successful CI on main)
|
#### Auto-Deploy (after successful CI jobs on main)
|
||||||
|
|
||||||
- Triggered by `workflow_run` after `CI - Build & Test` succeeds on `main`
|
- Runs as the final `Deploy Nexus` job in `.gitea/workflows/ci.yaml`
|
||||||
|
- Starts only after backend, frontend, and security jobs succeed on `main`
|
||||||
- Deploys the current `main` version after CI succeeds.
|
- Deploys the current `main` version after CI succeeds.
|
||||||
- Skips automatically if the triggering commit contains `[skip ci]`
|
- This replaces `workflow_run`, which did not create deploy runs in this Gitea 1.26.3 installation.
|
||||||
- The deploy workflow reads `VERSION`; it does not mutate Git, bump versions, or create tags
|
- The deploy script reads `VERSION`; it does not mutate Git, bump versions, or create tags
|
||||||
|
|
||||||
#### Manual Deploy (`workflow_dispatch`)
|
#### Manual Deploy (`workflow_dispatch`)
|
||||||
|
|
||||||
1. DevOps triggers `Deploy Nexus v2` in Gitea Actions
|
1. DevOps triggers `Deploy Nexus Manual` in Gitea Actions
|
||||||
2. Workflow validates `VERSION`, builds and deploys `main`
|
2. Workflow validates `VERSION`, builds and deploys `main`
|
||||||
3. Health check + smoke test verify the deployment
|
3. Health check + smoke test verify the deployment
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -1,6 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
> Letzte Aktualisierung: 2026-06-21
|
> Letzte Aktualisierung: 2026-06-24
|
||||||
|
|
||||||
|
- 2026-06-24: **Gitea CI/CD auf CD v4 repariert.**
|
||||||
|
- Root Cause: Gitea 1.26.3 erzeugte nach grünen CI-Runs 303/304 keinen `workflow_run`-Deploy; `ubuntu-latest` passte zudem nicht zum belegbar funktionierenden Runner-Label `linux`.
|
||||||
|
- Fix: Auto-Deploy läuft jetzt als `needs`-Job in `ci.yaml` nach Backend, Frontend und Security. `deploy.yaml` ist ein kleiner manueller Fallback via `workflow_dispatch`.
|
||||||
|
- Deploy-Logik wurde in `.gitea/scripts/deploy-nexus.sh` zusammengeführt. Secrets bleiben in `/tmp`, es wird kein Owner-Passwort injiziert, und Deploy mutiert Git-History nicht.
|
||||||
|
- Rollback- und Backup-Workflow nutzen ebenfalls das Runner-Label `linux`.
|
||||||
|
|
||||||
- 2026-06-21: **Permanenter Owner-Passwort-Persistenz-Fix (SeedAudit + Single Source of Truth).**
|
- 2026-06-21: **Permanenter Owner-Passwort-Persistenz-Fix (SeedAudit + Single Source of Truth).**
|
||||||
- Root Cause: Passwort-Injektion über Deploy-Runtime erzeugte einen unnötigen zweiten Pfad neben der DB und verursachte Drift nach DB-Reseed.
|
- Root Cause: Passwort-Injektion über Deploy-Runtime erzeugte einen unnötigen zweiten Pfad neben der DB und verursachte Drift nach DB-Reseed.
|
||||||
|
|||||||
+18
-17
@@ -1,45 +1,46 @@
|
|||||||
# Deployment
|
# Deployment
|
||||||
|
|
||||||
> Letzte Aktualisierung: 2026-06-21
|
> Letzte Aktualisierung: 2026-06-24
|
||||||
> Status: ✅ CD v3 (Auto + Manual) + Owner-Passwort-Persistenz (SeedAudit)
|
> Status: ✅ CD v4 (Auto inside CI + Manual) + Owner-Passwort-Persistenz (SeedAudit)
|
||||||
> Live-URL: https://nexus.noveria.net
|
> Live-URL: https://nexus.noveria.net
|
||||||
|
|
||||||
## CD-Philosophie (v3)
|
## CD-Philosophie (v4)
|
||||||
|
|
||||||
- **CI läuft automatisch** bei jedem Push → darf nie brechen
|
- **CI läuft automatisch** bei jedem Push → darf nie brechen
|
||||||
- **CD auto + manuell**: Automatischer Deploy nach CI-Success auf main; manueller Deploy via `workflow_dispatch`
|
- **CD auto + manuell**: Automatischer Deploy als abschließender CI-Job auf main; manueller Deploy via `workflow_dispatch`
|
||||||
- **Loop-Schutz**: Commits mit `[skip ci]` werden von Auto-Deploys ignoriert
|
- **Loop-Schutz**: Deploy mutiert Git nicht und erzeugt deshalb keine Deploy-Schleifen
|
||||||
- Deploy liest und validiert `VERSION`, mutiert aber weder Git noch Tags
|
- Deploy liest und validiert `VERSION`, mutiert aber weder Git noch Tags
|
||||||
- **Rollback** als eigener Workflow, manuell triggerbar
|
- **Rollback** als eigener Workflow, manuell triggerbar
|
||||||
- **Database-Backup** als eigener Workflow, manuell triggerbar (optionaler Nightly-Schedule)
|
- **Database-Backup** als eigener Workflow, manuell triggerbar (optionaler Nightly-Schedule)
|
||||||
|
|
||||||
## Workflows
|
## Workflows
|
||||||
|
|
||||||
### Deploy (`.gitea/workflows/deploy.yaml`)
|
### Auto-Deploy (`.gitea/workflows/ci.yaml`)
|
||||||
|
|
||||||
|
**Trigger**:
|
||||||
|
- **Automatisch**: Der Job `Deploy Nexus` läuft nach `backend`, `frontend` und `security`.
|
||||||
|
- Bedingung: Push auf `main`, alle CI-Jobs grün.
|
||||||
|
- Grund: Gitea 1.26.3 hat für grüne CI-Runs 303/304 keinen `workflow_run`-Deploy erzeugt; der zuverlässigste Ersatz ist ein `needs`-gesteuerter Job im selben Workflow.
|
||||||
|
|
||||||
|
### Manual Deploy (`.gitea/workflows/deploy.yaml`)
|
||||||
|
|
||||||
**Trigger**:
|
**Trigger**:
|
||||||
- **Automatisch**: Nach erfolgreicher CI (`workflow_run` auf `CI - Build & Test`)
|
|
||||||
→ Deployt `main` mit dem im Repo gesetzten `VERSION`-Wert
|
|
||||||
- **Manuell**: Via Gitea Actions → `workflow_dispatch`
|
- **Manuell**: Via Gitea Actions → `workflow_dispatch`
|
||||||
|
|
||||||
**Loop-Schutz**:
|
**Loop-Schutz**:
|
||||||
- Version-Bump-Commits enthalten `[skip ci]` → Gitea startet keine neue CI
|
- Deploy erstellt keine Commits, Tags oder Version-Bumps. Dadurch entsteht keine CI/CD-Schleife.
|
||||||
- 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**: keine. Der manuelle Deploy nutzt denselben Main-Deploy-Pfad wie der Auto-Deploy.
|
**Inputs**: keine. Der manuelle Deploy nutzt denselben Main-Deploy-Pfad wie der Auto-Deploy.
|
||||||
|
|
||||||
**Ablauf**:
|
**Ablauf**:
|
||||||
1. Job-Level-Guard: Auto-Deploys fuer `[skip ci]`-Commits werden gar nicht gestartet
|
1. Checkout von `main` (beim manuellen Deploy) oder Nutzung des geprüften CI-Checkouts (Auto-Deploy)
|
||||||
2. Checkout von `main`
|
2. `.gitea/scripts/deploy-nexus.sh`
|
||||||
3. `VERSION` lesen und SemVer validieren
|
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
|
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
|
5. Code-Sync zum Host-Deploy-Pfad
|
||||||
6. `docker compose build && up -d --force-recreate`
|
6. `docker compose build && up -d --force-recreate --remove-orphans --wait`
|
||||||
7. `.env`-Tempfile wird mit `shred` gelöscht
|
7. `.env`-Tempfile wird mit `shred` gelöscht
|
||||||
8. Health-Check (Backoff, 6 Versuche)
|
8. Health-Check und Smoke-Test (`/dashboard`, `/health`, `/api/v1/operations/snapshot` erwartet `401`)
|
||||||
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`)
|
### Backup (`.gitea/workflows/backup.yaml`)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user