98 lines
2.5 KiB
Bash
98 lines
2.5 KiB
Bash
#!/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
|