feat(deploy): webhook auto-deploy Forgejo → VPS Paris (TD-04)
Some checks are pending
CI / quality (push) Waiting to run
Some checks are pending
CI / quality (push) Waiting to run
This commit is contained in:
parent
85c760abee
commit
0ae2db3d8c
6 changed files with 333 additions and 37 deletions
98
deploy/deploy.sh
Normal file
98
deploy/deploy.sh
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
#!/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
|
||||
Loading…
Add table
Add a link
Reference in a new issue