#!/usr/bin/env bash # ============================================================================== # docker-update.sh — Automatic daily update for the Dockerized EpicNext-CMS. # # Steps: # 1. Verify the working tree is clean (uncommitted changes abort). # 2. git pull (fast-forward only). # 3. Run CMS migrations on the HOST (the slim runtime container has no source). # 4. docker compose build (auto-detects pnpm/yarn/npm via lockfile). # 5. docker compose up -d && wait for a healthy container. # 6. Record everything in update.log. # # Exit codes: 0 ok, 1 update skipped, 2 build/deploy failed, 3 health failed. # ============================================================================== set -uo pipefail DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$DIR" || exit 2 LOG_FILE="${LOG_FILE:-$DIR/logs/docker-update.log}" PM2_APP="${PM2_APP:-next}" # host-side CMS that must stay stopped (port 3002) log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; } die() { log "ERROR: $*"; exit "${2:-2}"; } touch "$LOG_FILE" log "=== Start docker-update ===" # --- 0. Guard: uncommitted changes would break git pull / taint deploys --- if ! { git diff --quiet --exit-code && git diff --cached --quiet --exit-code; }; then die "working tree has uncommitted changes; commit or stash first" 1 fi # --- 1. Pull latest --- git pull --ff-only --quiet 2>>"$LOG_FILE" pull_status=$? if [ $pull_status -ne 0 ]; then die "git pull failed (status $pull_status)" 1 fi log "git pull OK: $(git rev-parse --short HEAD)" # --- 2. Host-side migrations (idempotent; only applies CMS-owned tables) --- if [ -f pnpm-lock.yaml ] && command -v pnpm >/dev/null 2>&1; then pnpm db:migrate >>"$LOG_FILE" 2>&1 || die "db:migrate (pnpm) failed" elif command -v npm >/dev/null 2>&1; then npm run db:migrate >>"$LOG_FILE" 2>&1 || die "db:migrate (npm) failed" else die "no package manager found for migrations" 2 fi log "db:migrate OK" # --- 3. Rebuild the image --- docker compose build >>"$LOG_FILE" 2>&1 || die "docker compose build failed" 2 log "docker compose build OK" # --- 4. Recreate the container --- docker compose up -d >>"$LOG_FILE" 2>&1 || die "docker compose up failed" 2 log "docker compose up OK" # --- 5. Wait for health (up to ~4 min) --- healthy=0 for i in $(seq 1 16); do status="$(docker inspect --format='{{.State.Health.Status}}' epicnext-cms 2>/dev/null || true)" case "$status" in healthy) healthy=1; break ;; unhealthy) break ;; esac sleep 15 done if [ "$healthy" -eq 1 ]; then log "CMS healthy after update (commit $(git rev-parse --short HEAD))" else log "WARNING: container not healthy (status='${status:-unknown}')" # Leave the container running so it can be debugged; report failure exit. exit 3 fi # --- 6. Make sure the stale host-side PM2 CMS stays stopped --- if command -v pm2 >/dev/null 2>&1 && pm2 jlist >/dev/null 2>&1; then if pm2 list 2>/dev/null | grep -q "${PM2_APP}"; then pm2 stop "$PM2_APP" >/dev/null 2>&1 && log "pm2 '${PM2_APP}' kept stopped (avoids port 3002 clash)" fi fi log "=== docker-update finished OK ===" exit 0