scrum4me-docker/README.md
Scrum4Me Agent a7933f7420 docs(ST-mmuwreer): README deploy-procedure force-recreate na compose-wijziging
Voeg subsectie toe in "Updaten" die uitlegt dat docker compose restart
niet voldoende is bij volumes/tmpfs/ports-wijzigingen — force-recreate
is verplicht. Inclusief verify-stap met df -h /var/cache.
2026-05-03 18:23:06 +02:00

194 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# scrum4me-agent-runner
Headless Claude Code worker die de Scrum4Me job-queue (M13) leegtrekt vanaf
een QNAP NAS via Container Station. Geen Vercel, geen browser, geen
toetsenbord — Claude Code draait als daemon, claimt jobs uit
`mcp__scrum4me__wait_for_job`, voert ze uit in een per-job clone, en pusht
nooit zelf.
## Architectuur in één plaatje
```
┌─ QNAP TS-664 (Container Station) ─────────────────────────────┐
│ │
│ ┌─ container: agent-runner ────────────────────────────────┐ │
│ │ PID 1: tini → run-agent.sh (daemon-loop) │ │
│ │ ├─ health-server.js (8080 → host) │ │
│ │ └─ claude -p (per-batch, met MCP via stdio) │ │
│ │ └─ scrum4me-mcp → Neon Postgres │ │
│ │ │ │
│ │ /tmp/job-<id> ephemeral working trees │ │
│ │ /var/cache/repos bare git mirrors (volume) │ │
│ │ /var/cache/npm npm cache (volume) │ │
│ │ /var/log/agent run + job logs (volume) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ /share/Agent/cache /share/Agent/logs /share/Agent/state │
└────────────────────────────────────────────────────────────────┘
▼ HTTPS
Neon Postgres (Scrum4Me DB)
Vercel ─── Scrum4Me UI (gebruikers enqueueen jobs)
```
Eén `claude -p`-invocation roept intern `wait_for_job` aan totdat de
queue leeg is (≈600 s lege block-time → afsluiten). De wrapper start
`claude -p` opnieuw zodra hij eindigt, met exponentiële backoff bij
fouten.
## Wat zit waar
| Bestand | Doel |
|--------------------------|-----------------------------------------------------------------|
| `Dockerfile` | Ubuntu 22.04 + Node 22 + Claude Code + scrum4me-mcp + scripts |
| `docker-compose.yml` | Service-definitie, volumes, env-file, restart-policy, limits |
| `package.json` | Npm-dependencies van de runner zelf (alleen `scrum4me-mcp` pin) |
| `mcp-config.json` | Claude Code MCP-config (verwijst stdio naar scrum4me-mcp) |
| `CLAUDE.md` | Agent-rol-instructies, auto-geladen door `claude -p` |
| `bin/entrypoint.sh` | Container-startup: dirs, health-server, daemon-loop |
| `bin/run-agent.sh` | Daemon-loop met backoff, exit-code-routing en state-writes |
| `bin/check-tokens.sh` | Pre-flight: API-token, OAuth-token, DB-bereikbaarheid |
| `bin/job-prepare.sh` | Per-job: bare-fetch + clone-via-reference naar `/tmp/job-<id>` |
| `bin/job-cleanup.sh` | Per-job: logs naar `/var/log`, working tree weg |
| `bin/health-server.js` | HTTP-endpoint op 8080 dat state.json en marker-files leest |
| `bin/rotate-logs.sh` | Compress/cleanup van oude `.log`-bestanden |
| `.env.example` | Alle env-vars met uitleg |
## Vereisten op de NAS
- Container Station 2+ (Docker compose v2)
- Drie shares aangemaakt: `/share/Agent/cache`, `/share/Agent/logs`, `/share/Agent/state`
- Of één share `/share/Agent` waaronder de drie subdirs vallen
- Internet-uitgang naar `api.anthropic.com`, `github.com`, je Neon-host, `registry.npmjs.org`
## Deploy
```bash
# 1. Op je werkstation: token's regelen
# a. CLAUDE_CODE_OAUTH_TOKEN → draai `claude setup-token` (browser-flow)
# b. SCRUM4ME_TOKEN → log in als de dedicated agent-user in
# Scrum4Me, /settings/tokens, label "NAS-runner"
# c. DATABASE_URL/DIRECT_URL → Neon dashboard
# d. GH_TOKEN → github.com → Settings → Developer settings →
# Personal access tokens → Fine-grained.
# Repository access op madhura68/Scrum4Me +
# madhura68/scrum4me-mcp; Permissions:
# Contents (RW), Pull requests (RW),
# Metadata (R). Wordt gebruikt voor clone,
# push en `gh pr create` (auto_pr).
# 2. Repo op de NAS plaatsen
ssh admin@nas
cd /share/Agent
git clone https://github.com/<jij>/scrum4me-agent-runner.git
cd scrum4me-agent-runner
# 3. Env aanmaken
cp .env.example .env
chmod 600 .env
vi .env # vul alle waarden in
# 4. Build + start
docker compose build
docker compose up -d
# 5. Verifiëren
curl http://nas.local:8080/health
docker compose logs -f
```
## Updaten (handmatig, bewust)
`SCRUM4ME_TOKEN` of `CLAUDE_CODE_OAUTH_TOKEN` rouleer je via een rebuild:
```bash
cd /share/Agent/scrum4me-agent-runner
git pull
vi .env # nieuwe waarden
docker compose build # nieuwe scrum4me-mcp-versie als dat veranderd is
docker compose up -d
```
Dezelfde flow voor schema-drift in scrum4me-mcp: pin een nieuwe
`MCP_GIT_REF` in `.env` of in `docker-compose.yml`, rebuild.
### Wijzigingen in `docker-compose.yml` (volumes, tmpfs, env_file, ports)
> **Let op:** `docker compose restart` herstart alleen het proces in de
> bestaande container met de **oude** config. Wijzigingen in volumes,
> tmpfs-mounts, env_file of ports worden daarmee **niet** doorgevoerd.
Gebruik altijd `--force-recreate` als je `docker-compose.yml` is veranderd:
```bash
docker compose up -d --force-recreate agent
```
Verifieer daarna dat `/var/cache` op de NAS-overlay staat en **niet** op tmpfs:
```bash
docker exec scrum4me-agent df -h /var/cache
# Verwacht: Filesystem op /dev/mapper/cachedev* of een NAS-share
# Fout: tmpfs 16M ... (dan is force-recreate niet uitgevoerd)
```
## Health-endpoint
`GET http://<nas>:8080/health` retourneert:
```json
{
"status": "running", // running | idle | unhealthy | token-expired
"lastBatchAt": "2026-05-01T12:34:56Z",
"lastBatchExit": 0,
"consecutiveFailures": 0,
"tokenStatus": { "anthropic": "ok", "scrum4me": "ok", "db": "ok" }
}
```
HTTP-status: `200` als running/idle, `503` bij token-expired of als de
laatste heartbeat ouder is dan 5 minuten.
## Filesystem-grenzen
De agent-user heeft geen SSH-keys en geen toegang tot andere shares dan
`/share/Agent/*`. Wel een `~/.git-credentials` met de `GH_TOKEN` voor
HTTPS-clone/push (zie volgende sectie) — die token is scoped tot de twee
configured repos en mag worden gerouleerd door rebuild + redeploy.
## Repo bootstrap (clone-on-start)
Bij elke container-start runt `bin/repo-bootstrap.sh` (als de
`agent`-user, ná drop-privileges) en zet zo'n setup neer:
1. Configureert git's credential-helper met `GH_TOKEN` zodat
`git clone`/`push` naar `https://github.com/...` zonder prompt werkt.
2. Voor elke repo in `GH_PRECLONE_REPOS` (komma-gescheiden owner/name):
- Bestaat `~/Projects/<name>/.git` al? → `git fetch origin --prune`
- Anders → fresh `git clone`
Daarna vindt scrum4me-mcp's `resolveRepoRoot` (in `wait_for_job`) de
clone via z'n convention-fallback `~/Projects/<name>/.git`. Worktrees
voor jobs landen vervolgens onder `~/.scrum4me-agent-worktrees/<jobId>/`
zodat de hoofd-clone niet wordt aangeraakt.
Push gaat over dezelfde token: `git push -u origin feat/story-<id>`
slaagt zonder prompt. `gh pr create` (voor producten met `auto_pr=true`)
gebruikt dezelfde `GH_TOKEN` via de `gh` CLI's standaard env-detect.
## Bekende grenzen
- **Eén actieve job tegelijk.** De wrapper-loop is sequentieel. Voor
parallellisme zou je meerdere containers met dezelfde `SCRUM4ME_TOKEN`
kunnen draaien — `wait_for_job` gebruikt `FOR UPDATE SKIP LOCKED` dus
dat is veilig op DB-niveau, maar dan moet je je `node_modules`-cache
per container scheiden.
- **OAuth-token: 1 jaar geldig.** Bij verloop schrijft de wrapper een
`TOKEN_EXPIRED`-marker en wordt de container `unhealthy`. Geen
auto-rotatie.
- **`npm install` per job** kost op een N5095 ~3060 s per Next.js-clone,
óók met de pnpm-store. Voor zeer kleine fixes is dat de dominante
factor. Kan later vervangen worden door een persistente warm-`node_modules`
per repo als dat een knelpunt wordt.