#!/bin/bash # Rolling zero-downtime deploy for APIX. # Triggered by Gitea Actions (act_runner, host process) on push to main. # Requires: Docker on the runner host. Maven runs inside a container. # # Strategy: a/b container pairs per service. Caddy health-routes between them. # Deploy restarts a-stack then b-stack sequentially — one instance always healthy. set -euo pipefail INFRA_DIR="/opt/apix/infra" ENV_FILE="/opt/apix/.env" COMPOSE="docker compose -f $INFRA_DIR/docker-compose.yml --env-file $ENV_FILE" log() { echo "[deploy $(date -u +%H:%M:%S)] $*"; } wait_healthy() { local service=$1 local container="infra-${service}-1" local max=90 local elapsed=0 log "Waiting for $service to become healthy..." while [ $elapsed -lt $max ]; do status=$(docker inspect "$container" \ --format '{{.State.Health.Status}}' 2>/dev/null || echo "missing") if [ "$status" = "healthy" ]; then log "$service is healthy." return 0 fi sleep 5 elapsed=$((elapsed + 5)) done log "ERROR: $service did not become healthy after ${max}s (last status: $status)" docker logs "$container" --tail 20 2>&1 || true return 1 } # ── 1. Sync infra config to /opt/apix/infra ────────────────────────────────── log "Syncing infra config..." cp infra/Caddyfile "$INFRA_DIR/Caddyfile" cp infra/docker-compose.yml "$INFRA_DIR/docker-compose.yml" cp infra/Dockerfile.* "$INFRA_DIR/" [ -f infra/prometheus.yml ] && cp infra/prometheus.yml "$INFRA_DIR/" # ── 2. Build JARs (Maven runs in Docker — no JDK/Maven required on host) ────── log "Building JARs..." # GITHUB_WORKSPACE is set by act_runner; work dir is mounted at the same host path. BUILD_ROOT="${GITHUB_WORKSPACE:-$(pwd)}" docker run --rm \ -v "${BUILD_ROOT}:/workspace" \ -v "/home/deploy/gitea-runner/.m2:/root/.m2" \ -w /workspace \ maven:3.9-eclipse-temurin-21 \ mvn clean package -DskipTests -q # ── 3. Build Docker images ──────────────────────────────────────────────────── log "Building Docker images..." docker build -f infra/Dockerfile.registry -t apix-registry:latest . -q docker build -f infra/Dockerfile.portal -t apix-portal:latest . -q docker build -f infra/Dockerfile.demo -t apix-demo:latest . -q docker build -f infra/Dockerfile.spider -t apix-spider:latest . -q # ── 4. Rolling restart: a-stack (registry-a → portal-a + demo-a) ───────────── log "Rolling restart: a-stack..." $COMPOSE up -d --no-deps --force-recreate registry-a wait_healthy "registry-a" $COMPOSE up -d --no-deps --force-recreate portal-a demo-a wait_healthy "portal-a" wait_healthy "demo-a" # ── 5. Rolling restart: b-stack ─────────────────────────────────────────────── log "Rolling restart: b-stack..." $COMPOSE up -d --no-deps --force-recreate registry-b wait_healthy "registry-b" $COMPOSE up -d --no-deps --force-recreate portal-b demo-b wait_healthy "portal-b" wait_healthy "demo-b" # ── 6. Spider (cron job — restart acceptable) ───────────────────────────────── log "Restarting spider..." $COMPOSE up -d --no-deps --force-recreate spider # ── 7. Reload Caddy ─────────────────────────────────────────────────────────── log "Reloading Caddy..." docker exec infra-caddy-1 caddy reload --config /etc/caddy/Caddyfile log "Deploy complete. All services updated with zero downtime."