Files
aura-crm/production/deploy-agent

deploy-agent

Optional. This is a host-level self-update mechanism for a specific kind of production deployment (own VPS, git-based deploy, no external orchestrator). It is not required to run this project — docker compose up alone is a complete base install, and the Settings → Обновления tab in the CRM (internal/selfupdate) simply won't do anything if deploy-agent isn't installed (DEPLOY_AGENT_TOKEN unset → those API routes 503, fail-closed). Skip this directory entirely unless you specifically want the in-app "check/apply update" button.

Standalone host process (not a Docker service, not part of backend's Go module) that runs git fetch/git pull/docker compose build/docker compose up for /root/production on request, over a Unix domain socket. Exists so the CRM's "Обновления" tab can offer a check/apply-update button without giving the backend container a docker.sock mount or a copy of this repo's .git — either would turn a compromised backend process into full host control. Only the one socket file this agent listens on gets bind-mounted into backend (see ../docker-compose.yml's backend.volumes and DEPLOY_AGENT_SOCKET_PATH in .env); no TCP port, nothing else about the host reachable through it.

What it does

  • GET /checkgit fetch + list commits between HEAD and origin/<branch>. Read-only.
  • POST /apply — fetch, fast-forward-only pull, docker compose build, docker compose up -d for backend+web only (never postgres/minio/ telegram-bot-api — narrower blast radius, faster). Serialized: a second /apply while one is running gets a 409, not a second concurrent deploy stepping on the same working tree.

Both require Authorization: Bearer <DEPLOY_AGENT_TOKEN>.

Install

cd /root/production/deploy-agent
go build -o deploy-agent .
cp deploy-agent.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now deploy-agent
journalctl -u deploy-agent -f   # confirm it's listening, agent.sock now exists

agent.env (this directory) already holds a generated DEPLOY_AGENT_TOKEN — the same value must be set as DEPLOY_AGENT_TOKEN in ../.env so backend can authenticate to the socket. Neither agent.env nor the compiled deploy-agent binary nor agent.sock are committed (see ../.gitignore).

Order matters: ../docker-compose.yml bind-mounts the specific file deploy-agent/agent.sock into backend (not the whole directory — see that file's own comment on why), and Docker can only bind-mount a path that already exists at container start. So:

  1. Install/start deploy-agent first (above) — this creates agent.sock.
  2. Only then docker compose up -d (or restart) backend.

If backend was already running before agent.sock existed, its container was created without that mount — docker compose up -d backend again (after the agent is up) to recreate it with the socket attached; a plain restart of an existing container won't pick up a new bind mount.

Security model, and what it does NOT protect against

  • Runs as root, same as everything else on this host today (see deploy-agent.service's own comment). The actual boundary is: only a process that can (a) reach agent.sock on the filesystem AND (b) present the bearer token can trigger a deploy. On this host that's exactly the backend container (via the bind mount) — nothing else.
  • backend itself gates both endpoints behind auth.RequireRole("owner") (see internal/selfupdate) — a manager/master token can't reach this even indirectly, regardless of what's in the granular permissions system core issues (deliberately not reusing that system here — this is more sensitive than any existing permission, a hardcoded role check is a smaller, easier-to-audit surface than "whichever role happens to have this checkbox ticked").
  • Does not protect against: a compromised backend container's own process triggering deploys (it holds the token by design, so it can). If that container's trust boundary is ever a concern, the next hardening step is moving agent.env's token to something backend fetches per-request from a secrets manager instead of a static env var — not done here, out of scope for the MVP this ships as.
  • Does not protect against a bad commit on origin/main/apply pulls and deploys whatever's there, same as a human running git pull && docker compose up -d --build would. This button doesn't run tests or gate on CI status; it's exactly the manual deploy step, just one click instead of an SSH session.
  • /apply only rebuilds and restarts backend+web (see DEPLOY_AGENT_COMPOSE_SERVICES) — it never rebuilds this agent itself. A commit that changes deploy-agent/main.go still gets pulled onto disk, but the already-running agent process keeps executing its old compiled behavior until someone manually does go build -o deploy-agent . && systemctl restart deploy-agent. Self-restarting mid-request is more complexity than this MVP is worth; if deploy-agent's own logic changes, that's the one case still needing a manual step.

Follow-up hardening ideas (not implemented)

  • Dedicated non-root deploy user with a narrow sudoers entry for exactly git -C /root/production ... and docker compose ... in this directory, instead of running the whole agent as root.
  • Rotate DEPLOY_AGENT_TOKEN periodically (systemd timer + restart both the agent and backend).