- Add analytics.html to STATIC_FILES in deploy.sh - Add trackEvent/getVisitorId to script.js with page_view, show_results, copy_email hooks - Add "Analytics →" nav link to index.html and market.html headers Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
161 lines
8.6 KiB
Bash
Executable file
161 lines
8.6 KiB
Bash
Executable file
#!/bin/bash
|
|
# =============================================================================
|
|
# deploy.sh — idempotent deploy for loreal-sla-calculator
|
|
# Usage: bash /opt/loreal-sla-calculator/deploy.sh
|
|
# =============================================================================
|
|
set -euo pipefail
|
|
|
|
# ── Config ────────────────────────────────────────────────────────────────────
|
|
APP_NAME="loreal-sla-calculator"
|
|
REPO_DIR="/opt/${APP_NAME}"
|
|
WEB_DIR="/var/www/html/${APP_NAME}"
|
|
ENV_FILE="${REPO_DIR}/server/.env"
|
|
COMPOSE_FILE="${REPO_DIR}/docker-compose.yml"
|
|
|
|
# Static files to deploy to Apache web root
|
|
STATIC_FILES=(index.html auth.js script.js config.json market.html market-script.js analytics.html)
|
|
|
|
# ── Colours ───────────────────────────────────────────────────────────────────
|
|
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; BOLD='\033[1m'; NC='\033[0m'
|
|
log() { echo -e "${GREEN}[deploy]${NC} $1"; }
|
|
warn() { echo -e "${YELLOW}[warn]${NC} $1"; }
|
|
error() { echo -e "${RED}[error]${NC} $1"; exit 1; }
|
|
step() { echo -e "\n${BOLD}▶ $1${NC}"; }
|
|
|
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
compose() {
|
|
docker compose -f "$COMPOSE_FILE" "$@"
|
|
}
|
|
|
|
wait_healthy() {
|
|
local service="$1"
|
|
local max_wait=60
|
|
local elapsed=0
|
|
log "Waiting for ${service} to be healthy..."
|
|
while true; do
|
|
local cid
|
|
cid=$(compose ps -q "$service" 2>/dev/null || true)
|
|
if [ -n "$cid" ]; then
|
|
status=$(docker inspect --format='{{.State.Health.Status}}' "$cid" 2>/dev/null || echo "")
|
|
[[ "$status" == "healthy" ]] && { log "${service} is healthy."; return 0; }
|
|
fi
|
|
[[ $elapsed -ge $max_wait ]] && error "${service} did not become healthy within ${max_wait}s."
|
|
sleep 3
|
|
elapsed=$((elapsed + 3))
|
|
done
|
|
}
|
|
|
|
# ── 1. Prerequisites ──────────────────────────────────────────────────────────
|
|
step "Checking prerequisites"
|
|
command -v git >/dev/null 2>&1 || error "git is not installed"
|
|
command -v docker >/dev/null 2>&1 || error "docker is not installed"
|
|
docker compose version >/dev/null 2>&1 || error "docker compose v2 is not installed (need Docker >= 23)"
|
|
log "git $(git --version | awk '{print $3}'), docker $(docker --version | awk '{print $3}' | tr -d ',')"
|
|
|
|
# ── 2. Check .env ─────────────────────────────────────────────────────────────
|
|
step "Checking environment file"
|
|
if [ ! -f "$ENV_FILE" ]; then
|
|
warn ".env not found at ${ENV_FILE}. Creating from template..."
|
|
cp "${REPO_DIR}/server/.env.example" "$ENV_FILE"
|
|
chmod 600 "$ENV_FILE"
|
|
echo ""
|
|
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo -e "${YELLOW} ACTION REQUIRED: fill in secrets before deploying${NC}"
|
|
echo -e "${YELLOW} Edit: ${ENV_FILE}${NC}"
|
|
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo ""
|
|
error "Re-run deploy.sh after filling in ${ENV_FILE}"
|
|
fi
|
|
|
|
# Validate required variables are set and not placeholders
|
|
required_vars=(DATABASE_URL JWT_ACCESS_SECRET JWT_REFRESH_SECRET
|
|
POSTGRES_DB POSTGRES_USER POSTGRES_PASSWORD)
|
|
for var in "${required_vars[@]}"; do
|
|
val=$(grep -E "^${var}=" "$ENV_FILE" | cut -d= -f2- || true)
|
|
[ -z "$val" ] && error "Missing ${var} in ${ENV_FILE}"
|
|
[[ "$val" == *"CHANGE_ME"* ]] && error "${var} still has placeholder value in ${ENV_FILE}"
|
|
[[ "$val" == *"<"*">"* ]] && error "${var} still has placeholder value in ${ENV_FILE}"
|
|
done
|
|
log ".env OK"
|
|
|
|
# ── 3. Pull latest code ───────────────────────────────────────────────────────
|
|
step "Pulling latest code"
|
|
cd "$REPO_DIR"
|
|
git pull origin main
|
|
log "Code up to date: $(git log -1 --format='%h %s')"
|
|
|
|
# ── 4. Build Docker images ────────────────────────────────────────────────────
|
|
step "Building Docker images"
|
|
# --pull refreshes base image (node:20-alpine), cache still used for layers
|
|
compose build --pull
|
|
log "Build complete"
|
|
|
|
# ── 5. Start PostgreSQL ───────────────────────────────────────────────────────
|
|
step "Starting PostgreSQL"
|
|
compose up -d postgres
|
|
wait_healthy postgres
|
|
|
|
# ── 6. Run migrations ─────────────────────────────────────────────────────────
|
|
step "Running database migrations"
|
|
compose run --rm app node db/migrate.js
|
|
log "Migrations complete"
|
|
|
|
# ── 7. Start / restart app ────────────────────────────────────────────────────
|
|
step "Starting application"
|
|
compose up -d app
|
|
log "Container started"
|
|
|
|
# ── 8. Deploy static files ────────────────────────────────────────────────────
|
|
step "Deploying static files to ${WEB_DIR}"
|
|
rm -rf "${WEB_DIR}"
|
|
mkdir -p "${WEB_DIR}"
|
|
for f in "${STATIC_FILES[@]}"; do
|
|
if [ -f "${REPO_DIR}/${f}" ]; then
|
|
cp "${REPO_DIR}/${f}" "${WEB_DIR}/${f}"
|
|
log " copied ${f}"
|
|
else
|
|
warn " ${f} not found in repo — skipped"
|
|
fi
|
|
done
|
|
|
|
# ── 9. Health check ───────────────────────────────────────────────────────────
|
|
step "Health check"
|
|
sleep 3
|
|
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3100/api/health || echo "000")
|
|
if [ "$HTTP_STATUS" = "200" ]; then
|
|
log "API responded HTTP 200 ✓"
|
|
else
|
|
error "Health check failed (HTTP ${HTTP_STATUS}). Check logs: docker compose -f ${COMPOSE_FILE} logs app"
|
|
fi
|
|
|
|
# ── 10. First-run Apache hint ─────────────────────────────────────────────────
|
|
FIRST_RUN_FLAG="${REPO_DIR}/.deployed"
|
|
if [ ! -f "$FIRST_RUN_FLAG" ]; then
|
|
touch "$FIRST_RUN_FLAG"
|
|
echo ""
|
|
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo -e "${BOLD} First deploy complete. Add this to your Apache VirtualHost:${NC}"
|
|
echo ""
|
|
echo " # Serve static files directly"
|
|
echo " Alias /loreal-sla-calculator /var/www/html/loreal-sla-calculator"
|
|
echo " <Directory /var/www/html/loreal-sla-calculator>"
|
|
echo " Options -Indexes"
|
|
echo " AllowOverride None"
|
|
echo " Require all granted"
|
|
echo " </Directory>"
|
|
echo ""
|
|
echo " # Proxy API to Node.js"
|
|
echo " ProxyPass /loreal-sla-calculator/api http://localhost:3100/api"
|
|
echo " ProxyPassReverse /loreal-sla-calculator/api http://localhost:3100/api"
|
|
echo ""
|
|
echo " Then run: sudo a2enmod proxy proxy_http && sudo systemctl reload apache2"
|
|
echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
fi
|
|
|
|
# ── Done ──────────────────────────────────────────────────────────────────────
|
|
echo ""
|
|
echo -e "${GREEN}${BOLD}Deploy complete${NC} — $(date '+%Y-%m-%d %H:%M:%S')"
|
|
echo -e " Commit : $(git -C "$REPO_DIR" log -1 --format='%h %s')"
|
|
echo -e " App : http://localhost:3100/api/health"
|
|
echo -e " Static : ${WEB_DIR}"
|
|
echo ""
|