loreal-sla-calculator/deploy.sh
Vadym Samoilenko 8843af6402 Add analytics link to page headers, event tracking to calculator, update deploy script
- 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>
2026-03-17 16:15:12 +00:00

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 ""