scripts/deploy.sh backs up the database (pg_dump), checks out the release, runs npm ci, prisma migrate deploy and generate, builds the frontend, restarts the PM2 apps and health-checks them. Any failure puts back the previous code and build, and restores the dump if new migrations had run. deployment.md §12 now uses it. tsconfig.tsbuildinfo leaves git so builds don't leave the tree dirty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
116 lines
4.3 KiB
Bash
Executable File
116 lines
4.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Updates the Hub: back up the database, check out the release, install, migrate, build,
|
|
# restart PM2 and check it answers. If any step fails, it goes back to the previous version:
|
|
# the code, its dependencies and build, and the database too if new migrations had run.
|
|
#
|
|
# Usage (on the server, as the site user, from the repo folder):
|
|
# bash scripts/deploy.sh # the latest main
|
|
# bash scripts/deploy.sh v1.2.0 # a tag (or any commit)
|
|
#
|
|
# Optional settings (environment variables):
|
|
# HUB_BACKUP_DIR where database dumps go (default ~/backups/events-hub; the newest 10 are kept)
|
|
# HUB_PM2_BACKEND PM2 name of the backend (default events-hub-backend)
|
|
# HUB_PM2_FRONTEND PM2 name of the frontend (default events-hub-frontend)
|
|
set -Eeuo pipefail
|
|
|
|
# The checkout below replaces this file, and bash reads a script as it runs — so run from a
|
|
# copy that can't change underneath us.
|
|
if [ -z "${HUB_DEPLOY_COPY:-}" ]; then
|
|
COPY="$(mktemp)"
|
|
cp "$0" "$COPY"
|
|
HUB_DEPLOY_COPY=1 HUB_DEPLOY_DIR="$(cd "$(dirname "$0")/.." && pwd)" exec bash "$COPY" "$@"
|
|
fi
|
|
trap 'rm -f "$0"' EXIT
|
|
|
|
cd "$HUB_DEPLOY_DIR"
|
|
TARGET="${1:-main}"
|
|
BACKUP_DIR="${HUB_BACKUP_DIR:-$HOME/backups/events-hub}"
|
|
PM2_BACKEND="${HUB_PM2_BACKEND:-events-hub-backend}"
|
|
PM2_FRONTEND="${HUB_PM2_FRONTEND:-events-hub-frontend}"
|
|
FRONTEND_PORT=3200 # matches "start" in frontend/package.json
|
|
|
|
describe() { git describe --tags --exact-match 2>/dev/null || git rev-parse --short HEAD; }
|
|
PREVIOUS="$(git rev-parse HEAD)"
|
|
PREVIOUS_NAME="$(describe)"
|
|
|
|
if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
|
|
echo "!!> There are local changes to tracked files; deploy would overwrite them:"
|
|
git status --short --untracked-files=no
|
|
exit 1
|
|
fi
|
|
|
|
# The database URL from backend/.env, without Prisma-only options (pg_dump rejects ?schema=).
|
|
DB_URL="$(cd backend && node -e "require('dotenv').config(); const u = new URL(process.env.DATABASE_URL); u.search = ''; console.log(u.toString())")"
|
|
BACKEND_PORT="$(cd backend && node -e "require('dotenv').config(); console.log(process.env.PORT || 5200)")"
|
|
|
|
echo "==> Deploying $TARGET (currently $PREVIOUS_NAME)"
|
|
|
|
echo "==> Backing up the database"
|
|
mkdir -p "$BACKUP_DIR"
|
|
BACKUP="$BACKUP_DIR/events_hub-$(date +%Y%m%d-%H%M%S)-$PREVIOUS_NAME.dump"
|
|
pg_dump --dbname="$DB_URL" -Fc -f "$BACKUP"
|
|
echo " $BACKUP"
|
|
ls -1t "$BACKUP_DIR"/events_hub-*.dump | tail -n +11 | xargs -r rm -f
|
|
|
|
MIGRATED=0
|
|
RESTARTED=0
|
|
rollback() {
|
|
# set -E passes this trap into ( … ) subshells too; only the script itself rolls back.
|
|
if [ "$BASH_SUBSHELL" -gt 0 ]; then return; fi
|
|
trap - ERR
|
|
set +e
|
|
echo "!!> Deploy failed — going back to $PREVIOUS_NAME"
|
|
git checkout -q -- . 2>/dev/null
|
|
git checkout -q "$PREVIOUS"
|
|
if [ "$MIGRATED" = 1 ]; then
|
|
echo "!!> Restoring the database from $BACKUP"
|
|
pg_restore --clean --if-exists --no-owner --single-transaction --dbname="$DB_URL" "$BACKUP" \
|
|
|| echo "!!> Restore failed. Restore by hand: pg_restore --clean --if-exists --no-owner -d \"<DATABASE_URL>\" $BACKUP"
|
|
fi
|
|
(cd backend && npm ci --silent)
|
|
(cd frontend && npm ci --silent && npm run build --silent)
|
|
if [ "$RESTARTED" = 1 ]; then pm2 restart "$PM2_BACKEND" "$PM2_FRONTEND" --update-env; fi
|
|
echo "!!> Back on $PREVIOUS_NAME. Look at the output above for what failed."
|
|
exit 1
|
|
}
|
|
trap rollback ERR
|
|
|
|
# A tag or commit as is; main as the latest origin/main (detached, so a local main is never
|
|
# in the way).
|
|
if [ "$TARGET" = "main" ]; then
|
|
git fetch --quiet origin main
|
|
git checkout -q --detach origin/main
|
|
else
|
|
git fetch --tags --quiet origin
|
|
git checkout -q "$TARGET"
|
|
fi
|
|
DEPLOYED="$(describe)"
|
|
|
|
echo "==> Backend: install, migrate and generate"
|
|
(cd backend && npm ci)
|
|
if [ -n "$(git diff --name-only "$PREVIOUS" HEAD -- backend/prisma/migrations)" ]; then MIGRATED=1; fi
|
|
(cd backend && npm run prisma:deploy)
|
|
|
|
echo "==> Frontend: install and build"
|
|
(cd frontend && npm ci && npm run build)
|
|
|
|
echo "==> Restarting PM2"
|
|
RESTARTED=1
|
|
pm2 restart "$PM2_BACKEND" "$PM2_FRONTEND" --update-env
|
|
|
|
echo "==> Checking it answers"
|
|
check() {
|
|
for _ in $(seq 1 30); do
|
|
if curl -fsS -o /dev/null "$1"; then return 0; fi
|
|
sleep 1
|
|
done
|
|
echo "!!> No answer from $1 after 30 seconds"
|
|
return 1
|
|
}
|
|
check "http://127.0.0.1:$BACKEND_PORT/api/health"
|
|
check "http://127.0.0.1:$FRONTEND_PORT/"
|
|
|
|
trap - ERR
|
|
pm2 save --force >/dev/null
|
|
echo "==> Deployed $TARGET ($DEPLOYED). Backup: $BACKUP"
|