#!/usr/bin/env bash # Expria auto-deploy script (VPS Paris). # # Runs as the non-root `deploy` user, invoked by deploy/webhook-listener.mjs on # a verified push to origin/main. Fast-forwards the checkout, installs, builds, # and restarts expria-backend.service via ONE restricted sudo rule: # deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart expria-backend.service # # On a build or health-check failure it auto-rolls back to the previous commit. # All output goes to stdout -> journald (journalctl -u expria-deploy). set -euo pipefail REPO_DIR="/opt/expria/expria-backend" BRANCH="main" SERVICE="expria-backend.service" HEALTH_URL="${HEALTH_URL:-http://127.0.0.1:4000/}" LOCK_FILE="/tmp/expria-deploy.lock" log() { echo "$(date --iso-8601=seconds) [deploy] $*"; } # Serialize concurrent deploys: a second webhook waits here, or bails out. exec 9>"$LOCK_FILE" if ! flock -n 9; then log "another deploy is already in progress — aborting this run" exit 0 fi cd "$REPO_DIR" PREV="$(git rev-parse HEAD)" log "start (current=${PREV:0:8})" restart_backend() { sudo /usr/bin/systemctl restart "$SERVICE" } # Poll the liveness endpoint (GET / always returns 200 when the process is up). health_check() { for _ in $(seq 1 10); do if curl -fsS --max-time 3 "$HEALTH_URL" >/dev/null 2>&1; then return 0 fi sleep 2 done return 1 } rollback() { log "ROLLBACK to ${PREV:0:8}" if git reset --hard "$PREV" && npm ci && npm run build; then if restart_backend && health_check; then log "ROLLBACK ok — service healthy on ${PREV:0:8}" return 0 fi fi log "ROLLBACK FAILED — manual intervention required (was on ${PREV:0:8})" return 1 } # 1. Fetch + fast-forward only (refuses a diverged history, no destructive pull). git fetch --prune origin "$BRANCH" if ! git merge --ff-only "origin/$BRANCH"; then log "fast-forward failed (diverged history) — aborting, no changes applied" exit 1 fi NEW="$(git rev-parse HEAD)" log "pulled ${PREV:0:8} -> ${NEW:0:8}" if [ "$PREV" = "$NEW" ]; then log "already up to date — nothing to deploy" exit 0 fi # 2. Install + build (rollback on failure). if ! npm ci; then log "npm ci FAILED" rollback exit 1 fi if ! npm run build; then log "npm run build FAILED" rollback exit 1 fi # 3. Restart + health check (rollback on failure). if ! restart_backend; then log "systemctl restart FAILED" rollback exit 1 fi if health_check; then log "SUCCESS — deployed ${NEW:0:8}" else log "health check FAILED after restart" rollback exit 1 fi