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.
194 lines
8.8 KiB
Markdown
194 lines
8.8 KiB
Markdown
# 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 ~30–60 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.
|