gmal-scope-builder/deploy.sh
Vadym Samoilenko c47bf46faa Make data/ path configurable; preserve on frontend redeploy
- docker-compose: DATA_DIR env var controls data volume mount
  (defaults to ./data for local, /var/www/html/gmal-scope-builder/data on server)
- deploy.sh: resolve DATA_DIR from .env, default to persistent web dir
- deploy.sh: rm only frontend files from WEB_DIR, preserve data/ subdir
- .env.example: document DATA_DIR variable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 19:03:38 +00:00

151 lines
7.9 KiB
Bash
Executable file

#!/usr/bin/env bash
# deploy.sh — idempotent deploy script for GMAL Scope Builder
# Run from the repo root: sudo ./deploy.sh
# First-time setup: clone repo to /opt/gmal-scope-builder, create .env, then run this script.
set -euo pipefail
# ── Config ────────────────────────────────────────────────────────────────────
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WEB_DIR="/var/www/html/gmal-scope-builder"
APACHE_CONF="/etc/apache2/sites-enabled/optical-dev.oliver.solutions.conf"
APACHE_MARKER="gmal-scope-builder" # Used to detect if block already added
APP_URL_PATH="/gsb"
BACKEND_PORT="8002"
HEALTH_URL="http://127.0.0.1:${BACKEND_PORT}/api/health"
# ── Colours ───────────────────────────────────────────────────────────────────
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
log() { echo -e "${GREEN}[deploy]${NC} $*"; }
warn() { echo -e "${YELLOW}[ warn ]${NC} $*"; }
die() { echo -e "${RED}[error ]${NC} $*" >&2; exit 1; }
# ── 1. Pre-flight checks ──────────────────────────────────────────────────────
log "Running pre-flight checks..."
[[ -f "$REPO_DIR/.env" ]] || die ".env not found at $REPO_DIR/.env — copy .env.example and fill in values"
# Check required env vars
for var in POSTGRES_PASSWORD ANTHROPIC_API_KEY AZURE_TENANT_ID AZURE_CLIENT_ID; do
grep -q "^${var}=" "$REPO_DIR/.env" || die "Missing required variable '$var' in .env"
done
command -v docker &>/dev/null || die "Docker is not installed"
sudo docker compose version &>/dev/null || die "Docker Compose plugin not available"
# Resolve DATA_DIR (from .env or default to persistent web dir)
DATA_DIR=$(grep "^DATA_DIR=" "$REPO_DIR/.env" | cut -d= -f2- | tr -d '"' || true)
DATA_DIR="${DATA_DIR:-/var/www/html/gmal-scope-builder/data}"
sudo mkdir -p "$DATA_DIR"
# Warn if no GMAL Excel file (non-fatal — data may already be in the DB)
if ! sudo ls "${DATA_DIR}/"*.xlsx &>/dev/null 2>&1; then
warn "No .xlsx file found in $DATA_DIR — GMAL ingest will need to be triggered manually after deploy"
fi
log "Pre-flight OK"
# ── 2. Pull latest code ───────────────────────────────────────────────────────
log "Pulling latest code from origin/main..."
git -C "$REPO_DIR" pull origin main
# ── 3. Build and start backend services ──────────────────────────────────────
log "Building Docker images and starting services (using build cache)..."
sudo docker compose -f "$REPO_DIR/docker-compose.yml" --env-file "$REPO_DIR/.env" \
up -d --build --remove-orphans
# ── 4. Wait for backend health ────────────────────────────────────────────────
log "Waiting for backend to become healthy..."
TIMEOUT=90
ELAPSED=0
until curl -sf "$HEALTH_URL" > /dev/null 2>&1; do
sleep 3
ELAPSED=$((ELAPSED + 3))
[[ $ELAPSED -ge $TIMEOUT ]] && {
warn "Backend logs:"
sudo docker compose -f "$REPO_DIR/docker-compose.yml" logs --tail=30 backend
die "Backend did not become healthy within ${TIMEOUT}s"
}
log " Still waiting... (${ELAPSED}s)"
done
log "Backend healthy at $HEALTH_URL"
# ── 5. Database migrations ────────────────────────────────────────────────────
# create_all() runs automatically inside start.sh on each container start (idempotent).
# Uncomment the line below once Alembic migrations are set up:
# sudo docker compose -f "$REPO_DIR/docker-compose.yml" exec -T backend alembic upgrade head
log "Database schema is managed by start.sh (create_all) — no separate migration step needed"
# ── 6. Build frontend ─────────────────────────────────────────────────────────
log "Building frontend via Node Docker container..."
sudo docker run --rm \
-v "$REPO_DIR/frontend:/app" \
-w /app \
node:20-alpine \
sh -c "npm ci --prefer-offline && npm run build"
# ── 7. Deploy frontend static files ──────────────────────────────────────────
log "Deploying frontend to $WEB_DIR..."
sudo mkdir -p "$WEB_DIR"
# Remove only frontend files — preserve data/ subdirectory
sudo find "${WEB_DIR:?}" -maxdepth 1 -mindepth 1 ! -name 'data' -exec rm -rf {} +
sudo cp -r "$REPO_DIR/frontend/dist/." "$WEB_DIR/"
sudo chown -R www-data:www-data "$WEB_DIR"
sudo chown -R root:root "$DATA_DIR" # data/ stays root-owned for Docker
log "Frontend deployed ($(find "$REPO_DIR/frontend/dist" -type f | wc -l) files)"
# ── 8. Apache config (idempotent) ─────────────────────────────────────────────
if sudo grep -q "$APACHE_MARKER" "$APACHE_CONF" 2>/dev/null; then
log "Apache block for $APP_URL_PATH already present — skipping"
else
log "Adding Apache config block for $APP_URL_PATH ..."
sudo python3 - << PYEOF
apache_conf = "$APACHE_CONF"
block = """
# ----------------------------------------------------------------
# GMAL Scope Builder — FastAPI backend at :$BACKEND_PORT
# ----------------------------------------------------------------
ProxyPass $APP_URL_PATH/api/ http://127.0.0.1:$BACKEND_PORT/api/
ProxyPassReverse $APP_URL_PATH/api/ http://127.0.0.1:$BACKEND_PORT/api/
# GMAL Scope Builder SPA
Alias $APP_URL_PATH $WEB_DIR
<Directory $WEB_DIR>
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
RewriteEngine On
RewriteBase $APP_URL_PATH/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.html [L]
</Directory>
"""
with open(apache_conf) as f:
content = f.read()
if "$APACHE_MARKER" not in content:
content = content.replace("</VirtualHost>", block + "\n</VirtualHost>")
with open(apache_conf, "w") as f:
f.write(content)
print("Apache config updated")
else:
print("Apache config already has $APACHE_MARKER block (written by another process)")
PYEOF
fi
# ── 9. Validate and reload Apache ─────────────────────────────────────────────
log "Validating Apache config..."
sudo apache2ctl configtest
log "Reloading Apache..."
sudo systemctl reload apache2
# ── Done ──────────────────────────────────────────────────────────────────────
echo ""
echo -e "${GREEN}╔══════════════════════════════════════════════════════╗${NC}"
echo -e "${GREEN}║ Deployment complete! ║${NC}"
echo -e "${GREEN}║ https://optical-dev.oliver.solutions${APP_URL_PATH}/ ║${NC}"
echo -e "${GREEN}╚══════════════════════════════════════════════════════╝${NC}"
echo ""
log "If this is the first deploy, trigger GMAL ingest:"
log " curl -X POST -H 'Authorization: Bearer <token>' https://optical-dev.oliver.solutions${APP_URL_PATH}/api/gmal/ingest"