#!/usr/bin/env bash # Zero-downtime prod deploy: pull, sync deps, build front, migrate, then # gracefully reload gunicorn (see polylan-submitter.service). # # Run from anywhere as the user owning the checkout (caddy), needs sudo for # systemctl: # ./deploy/systemd/deploy.sh # # Migrations run while the old code still serves requests, so keep them # backward compatible (nullable/default columns, drop/rename in a later deploy). set -euo pipefail SERVICE="${SERVICE:-polylan-submitter}" # Extra units to restart after the web reload, e.g. RQ workers: # RESTART_SERVICES="polylan-submitter-worker" ./deploy/systemd/deploy.sh RESTART_SERVICES="${RESTART_SERVICES:-}" REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" APP_DIR="$REPO_DIR/polylan_submitter" PYTHON="$REPO_DIR/.venv/bin/python" # gunicorn's master doesn't reload itself on HUP, so a server upgrade needs a # full restart instead. server_versions() { "$PYTHON" - <<'EOF' 2>/dev/null || true import importlib.metadata as m for pkg in ("gunicorn", "uvicorn", "uvicorn-worker"): try: print(pkg, m.version(pkg)) except m.PackageNotFoundError: print(pkg, "missing") EOF } cd "$REPO_DIR" echo "==> git pull" git pull --ff-only before="$(server_versions)" echo "==> uv sync" uv sync --frozen --no-dev after="$(server_versions)" echo "==> frontend build" cd "$APP_DIR" pnpm install --frozen-lockfile pnpm build echo "==> migrate" "$PYTHON" manage.py migrate --noinput echo "==> collectstatic" "$PYTHON" manage.py collectstatic --noinput if [ "$before" != "$after" ]; then echo "==> server packages changed, full restart of $SERVICE" sudo systemctl restart "$SERVICE" else echo "==> graceful reload of $SERVICE" sudo systemctl reload "$SERVICE" fi for unit in $RESTART_SERVICES; do echo "==> restart $unit" sudo systemctl restart "$unit" done echo "==> done"