feat: systemd deployment
This commit is contained in:
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/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"
|
||||
@@ -0,0 +1,41 @@
|
||||
# Prod web process: gunicorn master + uvicorn ASGI workers, behind Caddy.
|
||||
#
|
||||
# `systemctl reload polylan-submitter` sends HUP to the gunicorn master: it
|
||||
# keeps the :7777 socket open, boots new workers on the new code, then
|
||||
# gracefully stops the old ones - no downtime. See deploy/systemd/deploy.sh.
|
||||
#
|
||||
# Install:
|
||||
# sudo cp deploy/systemd/polylan-submitter.service /etc/systemd/system/
|
||||
# sudo systemctl daemon-reload
|
||||
# sudo systemctl restart polylan-submitter # first switch from daphne needs a full restart
|
||||
|
||||
[Unit]
|
||||
Description=PolyLAN submitter (gunicorn + uvicorn workers)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=caddy
|
||||
Group=caddy
|
||||
WorkingDirectory=/home/caddy/polylan-submitter/polylan_submitter
|
||||
# EnvironmentFile=/home/caddy/polylan-submitter/.env
|
||||
|
||||
# No --preload: with it, HUP re-forks from the already imported (old) code.
|
||||
# --forwarded-allow-ips replaces daphne's --proxy-headers: trust
|
||||
# X-Forwarded-* only from Caddy (local here).
|
||||
ExecStart=/home/caddy/polylan-submitter/.venv/bin/gunicorn polylan_submitter.asgi:application \
|
||||
--worker-class uvicorn_worker.UvicornWorker \
|
||||
--workers 2 \
|
||||
--bind 0.0.0.0:7777 \
|
||||
--graceful-timeout 20 \
|
||||
--forwarded-allow-ips 127.0.0.1
|
||||
ExecReload=/bin/kill -s HUP $MAINPID
|
||||
|
||||
# SIGTERM to the master only, it then drains its workers (graceful-timeout).
|
||||
KillMode=mixed
|
||||
TimeoutStopSec=30
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Reference in New Issue
Block a user