diff --git a/README.md b/README.md index 789d71f6..780dcf1d 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,29 @@ The CMS will be available at `http://localhost:3002`. > **Reverse proxy:** This setup is designed to run behind the existing nginx on the host. nginx serves the Nitro/Octane client (`/client/`, `/nitro-client/`) and `/gamedata/` directly from `/var/www/Octane/dist` and `/var/www/Gamedata`, and proxies `/` to the CMS on `127.0.0.1:3002`. Point `APP_URL`/`NEXT_PUBLIC_APP_URL` at the public site URL. +### Automatic Updates + +A cron-safe update script is included: `scripts/docker-update.sh`. It pulls the +latest `main`, runs CMS migrations on the host, rebuilds the image, recreates the +container, waits for a healthy status, and makes sure the stale host-side PM2 CMS +(`pm2 stop next`) stays stopped so it can't clash on port 3002. + +```bash +./scripts/docker-update.sh # run once manually +# log: logs/docker-update.log +``` + +Set it up as a nightly cron job (03:30 example): + +```bash +crontab -e +# 30 3 * * * /var/www/atom-nexst/scripts/docker-update.sh >> /var/www/atom-nexst/logs/docker-update.cron.log 2>&1 +``` + +The script aborts safely (exit 1) if the working tree has uncommitted changes so +a `git pull` can never clobber local edits, and leaves the container running if +health fails so you can debug it (exit 3). + ### Docker Commands | Command | Description | @@ -165,6 +188,7 @@ The CMS will be available at `http://localhost:3002`. | `docker compose restart cms` | Restart the CMS container | | `docker compose pull && docker compose up -d --build` | Update and redeploy | | `pnpm db:migrate` (on the **host**) | Run database migrations (slim image has no source) | +| `./scripts/docker-update.sh` | Full automated update (manual or cron) | ### How Package Manager Detection Works diff --git a/scripts/docker-update.sh b/scripts/docker-update.sh new file mode 100755 index 00000000..609bcec9 --- /dev/null +++ b/scripts/docker-update.sh @@ -0,0 +1,89 @@ +#!/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 \ No newline at end of file