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..."
|
echo "🗄️ Dumping PostgreSQL cluster..."
|
||||||
|
|
||||||
docker exec "${BACKUP_CONTAINER_NAME}" \
|
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 }}"
|
| gzip > "${{ steps.meta.outputs.filename }}"
|
||||||
|
|
||||||
SIZE=$(du -h "${{ steps.meta.outputs.filename }}" | cut -f1)
|
SIZE=$(du -h "${{ steps.meta.outputs.filename }}" | cut -f1)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
run: |
|
run: |
|
||||||
corepack enable
|
corepack enable
|
||||||
corepack prepare pnpm@latest --activate
|
corepack prepare pnpm@10.12.1 --activate
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
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:
|
# Triggers:
|
||||||
# 1. AUTOMATIC after successful CI on main (workflow_run)
|
# 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
|
# → Commits marked with [skip ci] are filtered at job level
|
||||||
# (prevents version-bump loops).
|
# (prevents version-bump loops).
|
||||||
# 2. MANUAL via workflow_dispatch with full parameter control.
|
# 2. MANUAL via workflow_dispatch with full parameter control.
|
||||||
@@ -17,9 +17,8 @@ run-name: 🚀 Deploy v2 by @${{ gitea.actor }}
|
|||||||
#
|
#
|
||||||
# Version Management:
|
# Version Management:
|
||||||
# The VERSION file in the repo root is the single source of truth.
|
# The VERSION file in the repo root is the single source of truth.
|
||||||
# Version bumps happen in the Dev workflow BEFORE merge to main.
|
# Deploy only reads, validates, and logs the version.
|
||||||
# The deploy workflow only reads, validates, and logs the version.
|
# Version changes happen before merge to main, not during deploy.
|
||||||
# The [skip ci] filter remains as a safety layer for auto-triggers.
|
|
||||||
# ───────────────────────────────────────────────────────
|
# ───────────────────────────────────────────────────────
|
||||||
concurrency:
|
concurrency:
|
||||||
group: deploy-production
|
group: deploy-production
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
# Environment
|
# Environment
|
||||||
.env
|
.env
|
||||||
!.env.example
|
|
||||||
!.env.template
|
!.env.template
|
||||||
|
|
||||||
# IDE
|
# 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)
|
> [`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 can run **automatically after successful CI**
|
||||||
> on main (patch-bump default) or can be triggered **manually** (workflow_dispatch) with
|
> on main or can be triggered **manually** (workflow_dispatch). Deploy reads
|
||||||
> full parameter control. Main deploys bump/tag a release; arbitrary `git_ref` deploys
|
> `VERSION` but does not mutate Git or create tags. Rollback and database backup
|
||||||
> stay read-only. Rollback and database backup are separate manual workflows.
|
> are separate manual workflows.
|
||||||
> See [phases/deployment.md](phases/deployment.md) for full CD documentation.
|
> See [phases/deployment.md](phases/deployment.md) for full CD documentation.
|
||||||
|
|
||||||
## Current foundation
|
## Current foundation
|
||||||
@@ -26,7 +26,7 @@ adapter-backed agent runtime, not a dependency of the frontend or domain model.
|
|||||||
## Local/container start
|
## Local/container start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.template .env
|
||||||
# Replace every placeholder, especially POSTGRES_PASSWORD, JWT_KEY and BOOTSTRAP_OWNER_EMAIL.
|
# Replace every placeholder, especially POSTGRES_PASSWORD, JWT_KEY and BOOTSTRAP_OWNER_EMAIL.
|
||||||
docker compose up --build -d
|
docker compose up --build -d
|
||||||
curl http://127.0.0.1:18880/health
|
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)
|
#### Auto-Deploy (after successful CI on main)
|
||||||
|
|
||||||
- Triggered by `workflow_run` after `CI - Build & Test` succeeds on `main`
|
- Triggered by `workflow_run` after `CI - Build & Test` succeeds on `main`
|
||||||
- Uses safe defaults: `patch` bump, all services, main ref
|
- Deploys the current `main` version after CI succeeds.
|
||||||
- Skips automatically if the triggering commit contains `[skip ci]` (version-bump commits)
|
- Skips automatically if the triggering commit contains `[skip ci]`
|
||||||
- The version-bump commit itself uses `[skip ci]` → no infinite CI→Deploy→Bump→CI loops
|
- The deploy workflow reads `VERSION`; it does not mutate Git, bump versions, or create tags
|
||||||
|
|
||||||
#### Manual Deploy (`workflow_dispatch`)
|
#### Manual Deploy (`workflow_dispatch`)
|
||||||
|
|
||||||
1. DevOps triggers `Deploy to Production` in Gitea Actions (or Iris auto-approves)
|
1. DevOps triggers `Deploy Nexus v2` in Gitea Actions
|
||||||
2. Chooses version bump type: patch (default) / minor / major
|
2. Workflow validates `VERSION`, builds and deploys `main`
|
||||||
3. Optionally scopes to a single service or specific git ref
|
3. Health check + smoke test verify the deployment
|
||||||
4. Workflow bumps VERSION, creates git tag, builds and deploys
|
|
||||||
5. Health check + smoke test verify the deployment
|
|
||||||
|
|
||||||
#### Rollback (`workflow_dispatch`)
|
#### 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)
|
## CD-Philosophie (v3)
|
||||||
|
|
||||||
- **CI läuft automatisch** bei jedem Push → darf nie brechen
|
- **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`
|
- **CD auto + manuell**: Automatischer Deploy nach CI-Success auf main; manueller Deploy via `workflow_dispatch`
|
||||||
- **Loop-Schutz**: Version-Bump-Commits enthalten `[skip ci]` — kein Re-Trigger der CI, kein Infinite-Loop
|
- **Loop-Schutz**: Commits mit `[skip ci]` werden von Auto-Deploys ignoriert
|
||||||
- **Main-Deploys** duerfen VERSION bumpen und einen Git-Tag setzen
|
- Deploy liest und validiert `VERSION`, mutiert aber weder Git noch Tags
|
||||||
- **Nicht-Main-Deploys** (anderer `git_ref`) deployen read-only und mutieren Git nicht
|
|
||||||
- **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)
|
||||||
|
|
||||||
@@ -20,7 +19,7 @@
|
|||||||
|
|
||||||
**Trigger**:
|
**Trigger**:
|
||||||
- **Automatisch**: Nach erfolgreicher CI (`workflow_run` auf `CI - Build & Test`)
|
- **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`
|
- **Manuell**: Via Gitea Actions → `workflow_dispatch`
|
||||||
|
|
||||||
**Loop-Schutz**:
|
**Loop-Schutz**:
|
||||||
@@ -28,26 +27,19 @@
|
|||||||
- Auto-Deploy prüft zusätzlich `github.event.workflow_run.head_commit.message` auf `[skip ci]`
|
- Auto-Deploy prüft zusätzlich `github.event.workflow_run.head_commit.message` auf `[skip ci]`
|
||||||
- Beide Mechanismen zusammen verhindern Endlosschleife: CI → Deploy → Bump → CI …
|
- Beide Mechanismen zusammen verhindern Endlosschleife: CI → Deploy → Bump → CI …
|
||||||
|
|
||||||
**Inputs** (nur bei `workflow_dispatch`):
|
**Inputs**: keine. Der manuelle Deploy nutzt denselben Main-Deploy-Pfad wie der Auto-Deploy.
|
||||||
| Input | Typ | Default | Beschreibung |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `version_bump` | choice (patch/minor/major) | patch | Version-Bump-Typ |
|
|
||||||
| `service` | string | (all) | Einzelner Service oder alle |
|
|
||||||
| `no_cache` | boolean | false | Docker-Build-Cache deaktivieren |
|
|
||||||
| `git_ref` | string | main | Branch/Tag/Commit zum Deployen |
|
|
||||||
|
|
||||||
**Ablauf**:
|
**Ablauf**:
|
||||||
1. Job-Level-Guard: Auto-Deploys fuer `[skip ci]`-Commits werden gar nicht gestartet
|
1. Job-Level-Guard: Auto-Deploys fuer `[skip ci]`-Commits werden gar nicht gestartet
|
||||||
2. Checkout des gewählten Git-Refs
|
2. Checkout von `main`
|
||||||
3. Wenn `git_ref = main`: Version-Bump + Git-Tag + Push
|
3. `VERSION` lesen und SemVer validieren
|
||||||
4. Wenn `git_ref != main`: VERSION nur lesen, kein Push, kein Tag
|
4. **Safe Secret Handling**: `.env` wird aus Secret-Umgebungsvariablen in `/tmp/nexus-deploy-env` geschrieben (mode 600), **NICHT** im Workspace
|
||||||
5. **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. Code-Sync zum Host-Deploy-Pfad
|
6. `docker compose build && up -d --force-recreate`
|
||||||
7. `docker compose build && up -d --wait --force-recreate`
|
7. `.env`-Tempfile wird mit `shred` gelöscht
|
||||||
8. `.env`-Tempfile wird mit `shred` gelöscht
|
8. Health-Check (Backoff, 6 Versuche)
|
||||||
9. Health-Check (exponentieller Backoff, 6 Versuche)
|
9. Smoke-Test (`/dashboard`, `/health`, `/api/v1/operations/snapshot` erwartet `401`)
|
||||||
10. Smoke-Test (`/dashboard`, `/health`, `/api/v1/operations/snapshot` erwartet `401`)
|
10. Bei Fehler: Reviewer-Handoff-Meldung mit Job-URL
|
||||||
11. Bei Fehler: Reviewer-Handoff-Meldung mit Job-URL
|
|
||||||
|
|
||||||
### Backup (`.gitea/workflows/backup.yaml`)
|
### 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`
|
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`
|
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
|
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
|
## Offene Arbeit
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user