erpnext/scripts/ci/validate-docker-compose.sh
epistemophiliac 0c8d593d40 Add production ERPNext Coolify stack with CI gates
Single compose file for Coolify: MariaDB, Redis, idempotent site creation,
migrations on redeploy, SERVICE_URL_FRONTEND_8080 routing, and Forgejo Actions
readiness validation vendored from production-ci-readiness skill.
2026-06-16 17:52:02 -04:00

109 lines
3.5 KiB
Bash
Executable file

#!/usr/bin/env bash
# Validate docker-compose for production/Coolify deploy safety
# SPDX-License-Identifier: Apache-2.0
set -euo pipefail
REPO="${1:-.}"
REPO="$(cd "$REPO" && pwd)"
COOLIFY_STRICT="${COOLIFY_STRICT:-0}"
ERR=0
WARN=0
err() { echo "ERROR $*"; ERR=$((ERR + 1)); }
warn() { echo "WARN $*"; WARN=$((WARN + 1)); }
info() { echo "INFO $*"; }
pass() { echo "OK $*"; }
COMPOSE_FILE=""
for f in docker-compose.yml docker-compose.yaml compose.yml; do
if [ -f "$REPO/$f" ]; then
COMPOSE_FILE="$REPO/$f"
break
fi
done
if [ -z "$COMPOSE_FILE" ]; then
echo "INFO No docker-compose file — skip"
exit 0
fi
echo "=== validate-docker-compose: $COMPOSE_FILE ==="
# Coolify-only keys (exclude_from_hc) are stripped before docker compose config
COMPOSE_VALIDATE_FILE="$COMPOSE_FILE"
if grep -q 'exclude_from_hc:' "$COMPOSE_FILE"; then
COMPOSE_VALIDATE_FILE="$(mktemp)"
sed '/exclude_from_hc:/d' "$COMPOSE_FILE" > "$COMPOSE_VALIDATE_FILE"
info "[DC-00] stripped exclude_from_hc for docker compose config"
fi
# docker compose config
if command -v docker >/dev/null 2>&1; then
if docker compose -f "$COMPOSE_VALIDATE_FILE" config -q 2>/dev/null; then
pass "[DC-01] docker compose config OK"
else
err "[DC-01] docker compose config failed"
docker compose -f "$COMPOSE_VALIDATE_FILE" config 2>&1 | tail -20 || true
fi
else
warn "[DC-01] docker not available — syntax not fully validated"
fi
[ "$COMPOSE_VALIDATE_FILE" != "$COMPOSE_FILE" ] && rm -f "$COMPOSE_VALIDATE_FILE"
# Coolify: no host ports on HTTP services
if grep -E '^\s+ports:' "$COMPOSE_FILE" >/dev/null 2>&1; then
err "[DC-02] ports: found — Coolify uses SERVICE_URL_* instead"
else
pass "[DC-02] no host ports (Coolify-safe)"
fi
# SERVICE_URL on frontend with port 8080 (Frappe nginx default)
if grep -q 'SERVICE_URL_FRONTEND_8080' "$COMPOSE_FILE"; then
pass "[DC-03] SERVICE_URL_FRONTEND_8080 present"
else
err "[DC-03] missing SERVICE_URL_FRONTEND_8080 on frontend"
fi
# Bind mounts ./scripts on long-running services (heuristic)
if grep -E '^\s+-\s+['\''"]?\./scripts' "$COMPOSE_FILE" >/dev/null 2>&1; then
if grep -B30 './scripts' "$COMPOSE_FILE" | grep -qE 'restart:\s+(unless-stopped|always)'; then
if [ "$COOLIFY_STRICT" = "1" ]; then
err "[DC-04] ./scripts bind mount on restarting service — Coolify may miss files"
else
warn "[DC-04] ./scripts bind mount — prefer inline compose for Coolify daemons"
fi
else
info "[DC-04] ./scripts mount on non-restarting service (may be OK)"
fi
fi
# SERVICE_URL on compose (Coolify magic vars)
if grep -qE 'SERVICE_URL_[A-Z0-9_]+' "$COMPOSE_FILE"; then
if grep -qE 'networks:' "$COMPOSE_FILE" && ! grep -qE '^\s+default:' "$COMPOSE_FILE"; then
warn "[DC-05] SERVICE_URL_* with custom networks — risk Traefik 504; see coolify-docker-compose skill"
fi
fi
# fabric-price-oracle / gateway port hints
if grep -q 'fabric-gateway' "$COMPOSE_FILE"; then
if grep -A80 'fabric-gateway:' "$COMPOSE_FILE" | grep -qE "['\"]?3000:3000|GATEWAY_PORT"; then
pass "[DC-08] fabric-gateway publishes port 3000"
else
warn "[DC-08] fabric-gateway may not publish host port 3000"
fi
fi
# Retired oracle stub
if grep -q 'fabric-oracle:' "$COMPOSE_FILE"; then
if grep -A15 'fabric-oracle:' "$COMPOSE_FILE" | grep -qE 'profiles:|legacy|Retired|exit 0'; then
pass "[DC-09] fabric-oracle retired/profiled"
else
warn "[DC-09] fabric-oracle still active — should be legacy stub"
fi
fi
echo "compose errors=$ERR warnings=$WARN"
[ "$ERR" -eq 0 ] || exit 1
exit 0