Files
aura-crm/production/deploy-agent/README.md
T

106 lines
5.4 KiB
Markdown

# 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 /check``git 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
```bash
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`).