From 016c9424cec6a13d5f0fc912630e88b21a943c83 Mon Sep 17 00:00:00 2001 From: Saksham Das Date: Wed, 5 Aug 2026 20:24:25 -0400 Subject: [PATCH] Add polling-based auto-deploy: server picks up pushes to master scripts/auto-deploy.sh + a systemd timer (2min interval) that fetches origin/master and, if ahead, pulls/rebuilds/migrates/restarts - same sequence as the manual update steps in DEPLOYMENT.md, just scheduled. Polling instead of a Gitea webhook deliberately: no extra exposed service, no Docker socket mounted into a container, no shared secret to manage - it's the same trust boundary as a manual SSH deploy. Co-Authored-By: Claude Sonnet 5 --- DEPLOYMENT.md | 13 ++++++++ .../systemd/ci-agent-deploy.service | 9 ++++++ infrastructure/systemd/ci-agent-deploy.timer | 10 ++++++ scripts/auto-deploy.sh | 31 +++++++++++++++++++ 4 files changed, 63 insertions(+) create mode 100644 infrastructure/systemd/ci-agent-deploy.service create mode 100644 infrastructure/systemd/ci-agent-deploy.timer create mode 100644 scripts/auto-deploy.sh diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index 0945b01..18e5175 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -106,6 +106,8 @@ Also set `LLM_PROVIDER=anthropic` and `SEARCH_PROVIDER=brave` in `.env` (restart ## 8. Updating the deployment +Manually: + ```bash git pull docker compose -f docker-compose.prod.yml build api worker beat web @@ -113,6 +115,17 @@ docker compose -f docker-compose.prod.yml run --rm api alembic upgrade head docker compose -f docker-compose.prod.yml up -d ``` +**Or automatically**: `scripts/auto-deploy.sh` runs exactly that sequence, gated on "is `origin/master` ahead of `HEAD`" so it's a no-op most runs. Install it as a systemd timer (polls every 2 minutes — deliberately polling, not a Gitea webhook, so there's no extra exposed service, no Docker-socket-in-a-container, and no shared secret to manage): + +```bash +cp infrastructure/systemd/ci-agent-deploy.service infrastructure/systemd/ci-agent-deploy.timer /etc/systemd/system/ +chmod +x scripts/auto-deploy.sh +systemctl daemon-reload +systemctl enable --now ci-agent-deploy.timer +``` + +Once running, pushing to `master` on Gitea is enough — the server picks it up within ~2 minutes, no manual SSH step needed. Check `journalctl -u ci-agent-deploy.service` to see deploy runs. + ## 9. Backups Nothing backs itself up by default. At minimum, a nightly cron job on the host: diff --git a/infrastructure/systemd/ci-agent-deploy.service b/infrastructure/systemd/ci-agent-deploy.service new file mode 100644 index 0000000..e2de4ca --- /dev/null +++ b/infrastructure/systemd/ci-agent-deploy.service @@ -0,0 +1,9 @@ +[Unit] +Description=CI Agent auto-deploy (poll Gitea for new commits on master) +After=docker.service +Requires=docker.service + +[Service] +Type=oneshot +WorkingDirectory=/opt/ci-agent +ExecStart=/opt/ci-agent/scripts/auto-deploy.sh diff --git a/infrastructure/systemd/ci-agent-deploy.timer b/infrastructure/systemd/ci-agent-deploy.timer new file mode 100644 index 0000000..b38afae --- /dev/null +++ b/infrastructure/systemd/ci-agent-deploy.timer @@ -0,0 +1,10 @@ +[Unit] +Description=Run CI Agent auto-deploy every 2 minutes + +[Timer] +OnBootSec=2min +OnUnitActiveSec=2min +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/scripts/auto-deploy.sh b/scripts/auto-deploy.sh new file mode 100644 index 0000000..5327f9d --- /dev/null +++ b/scripts/auto-deploy.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Polls the public CIAgent repo for new commits on master and redeploys if +# found. Runs as a systemd timer on the server (infrastructure/systemd/) - +# not a webhook, deliberately: a webhook receiver would need either the +# Docker socket mounted into a container reachable from a request handler, +# or a new exposed service/nginx route/shared secret to manage. Polling +# does the same job with none of that - it's just this same sequence run on +# a timer, on the host, as root, exactly like a manual deploy. +# +# The repo is public, so `git fetch` here needs no credentials. + +set -euo pipefail +cd /opt/ci-agent + +git fetch origin master -q + +LOCAL=$(git rev-parse HEAD) +REMOTE=$(git rev-parse origin/master) + +if [ "$LOCAL" = "$REMOTE" ]; then + exit 0 +fi + +echo "$(date -Iseconds) deploying $REMOTE (was $LOCAL)" + +git merge --ff-only origin/master +docker compose -f docker-compose.prod.yml build api worker beat web +docker compose -f docker-compose.prod.yml run --rm api alembic upgrade head +docker compose -f docker-compose.prod.yml up -d + +echo "$(date -Iseconds) deploy complete: $REMOTE"