---
title: "Installatieplan — Beelink Ubuntu Scrum4Me server en worker-aanpassingen"
status: draft
audience: [maintainer, operator, ai-agent]
language: nl
last_updated: 2026-05-10
---

# Installatieplan — Beelink Ubuntu Scrum4Me server en worker-aanpassingen

## Doel

Deze notitie beschrijft de huidige Beelink-installatie en het vervolgplan om de Scrum4Me workers geschikt te maken voor drie rollen:

```text
worker-idea
worker-implementation
worker-orchestrator
```

De server draait nu als LAN-host voor Scrum4Me. Productie-internettoegang met domein en HTTPS is nog een latere stap.

## Hardware

| Onderdeel | Waarde |
|---|---|
| Machine | Beelink mini-PC |
| CPU | Intel Core i5-12450H |
| RAM zichtbaar in Ubuntu | 16 GB |
| Max RAM volgens hardware | 32 GB |
| Disk | 468 GB bruikbaar na Ubuntu-installatie |
| IP | `192.168.0.154` |

Opmerking: Ubuntu ziet momenteel ongeveer 16 GB RAM. De hardware meldt een maximum van 32 GB, maar dat betekent niet dat 32 GB bruikbaar/geplaatst is.

## Huidige Installatie

### Ubuntu

Ubuntu Server is geïnstalleerd op de hele disk.

Belangrijke keuzes:

- Ubuntu Server 24.04 LTS.
- Geen Ubuntu Desktop.
- Geen LVM.
- Geen aparte GPU-drivers.
- Geen Windows dual boot meer.
- Hostname: `scrum4me-server`.
- Sleep/hibernate uitgeschakeld.
- Swapfile vergroot naar 16 GB.

Controle:

```bash
hostnamectl
free -h
swapon --show
df -h
```

### Directorystructuur

Alle service-data staat onder:

```text
/srv/scrum4me
```

Structuur:

```text
/srv/scrum4me/postgres       database data
/srv/scrum4me/repos          GitHub clones
/srv/scrum4me/worker-cache   worker caches
/srv/scrum4me/worker-logs    worker logs
/srv/scrum4me/worker-state   worker state
/srv/scrum4me/backups        Postgres backups
/srv/scrum4me/compose        Docker Compose files
/srv/scrum4me/caddy          Caddy config/data
```

### Docker

Docker Engine draait native op Ubuntu.

Controle:

```bash
docker run hello-world
docker compose version
```

### Postgres

Postgres draait als Docker container:

```text
container: scrum4me-postgres
image: postgres:17
```

Host mapping:

```text
127.0.0.1:5432 -> postgres:5432
```

Host-app gebruikt:

```env
DATABASE_URL="postgresql://scrum4me:<password>@127.0.0.1:5432/scrum4me"
DIRECT_URL="postgresql://scrum4me:<password>@127.0.0.1:5432/scrum4me"
```

Containers gebruiken:

```env
DATABASE_URL=postgresql://scrum4me:<password>@postgres:5432/scrum4me
DIRECT_URL=postgresql://scrum4me:<password>@postgres:5432/scrum4me
```

DB-test:

```bash
docker exec -e PGPASSWORD="$DBPASS" scrum4me-postgres \
  psql -h 127.0.0.1 -U scrum4me -d scrum4me \
  -c "select current_user, current_database();"
```

### Scrum4Me Web

Repo:

```text
/srv/scrum4me/repos/Scrum4Me
```

Build:

```bash
cd /srv/scrum4me/repos/Scrum4Me
rm -rf .next
npm run build
```

Runtime:

```text
systemd service: scrum4me-web
```

Service startcommand:

```bash
npm run start -- -H 0.0.0.0
```

Controle:

```bash
systemctl status scrum4me-web --no-pager
curl -I http://127.0.0.1:3000/login
```

### Caddy

Caddy draait als Docker container:

```text
container: scrum4me-caddy
```

Caddy reverse proxyt:

```text
http://192.168.0.154 -> Caddy -> 172.18.0.1:3000 -> Scrum4Me web
```

Caddyfile:

```caddyfile
:80 {
  reverse_proxy 172.18.0.1:3000
}
```

Controle:

```bash
curl -I http://192.168.0.154/login
docker logs --tail=50 scrum4me-caddy
```

### LAN Session Config

Omdat de server nu via HTTP op LAN draait, is secure session cookie tijdelijk uitgezet.

Env:

```env
SESSION_COOKIE_SECURE="false"
```

Code-aanpassing:

```ts
secure: process.env.SESSION_COOKIE_SECURE === 'true',
```

Later, bij domein + HTTPS:

```env
SESSION_COOKIE_SECURE="true"
```

Daarna:

```bash
rm -rf .next
npm run build
sudo systemctl restart scrum4me-web
```

### Migrations

De database is gemigreerd.

Belangrijke migration-notitie:

`20260506101436_restore_todos_table` kan op een bestaande DB falen met:

```text
relation "todos" already exists
```

Voor deze server is de juiste aanpak:

```bash
npx prisma migrate resolve --applied 20260506101436_restore_todos_table
npx prisma migrate deploy
```

Controle:

```bash
npx prisma migrate status
docker exec -it scrum4me-postgres psql -U scrum4me -d scrum4me -c "\dt public.users"
```

### Admin en Product

Admin user is aangemaakt via:

```bash
npx tsx scripts/create-admin.ts janpeter '<password>'
```

Login werkt.

Product aanmaken werkt.

### Backups

Backup-script:

```text
/srv/scrum4me/backup-postgres.sh
```

Script:

```bash
#!/usr/bin/env bash
set -euo pipefail

BACKUP_DIR="/srv/scrum4me/backups"
STAMP="$(date +%Y%m%d-%H%M%S)"
FILE="$BACKUP_DIR/scrum4me-$STAMP.sql.gz"

mkdir -p "$BACKUP_DIR"

docker exec scrum4me-postgres pg_dump -U scrum4me scrum4me | gzip > "$FILE"

find "$BACKUP_DIR" -type f -name 'scrum4me-*.sql.gz' -mtime +14 -delete

echo "backup written: $FILE"
```

Test:

```bash
/srv/scrum4me/backup-postgres.sh
ls -lh /srv/scrum4me/backups
```

Cron:

```cron
15 3 * * * /srv/scrum4me/backup-postgres.sh >> /srv/scrum4me/backups/backup.log 2>&1
```

## Worker-Idea Installatie

Worker compose-service:

```text
worker-idea
container: scrum4me-worker-idea
health: http://127.0.0.1:18081/health
```

Belangrijke env-waarden:

```env
SCRUM4ME_BASE_URL=http://caddy
SCRUM4ME_TOKEN=<raw Scrum4Me API token>

DATABASE_URL=postgresql://scrum4me:<password>@postgres:5432/scrum4me
DIRECT_URL=postgresql://scrum4me:<password>@postgres:5432/scrum4me

GH_TOKEN=<GitHub token>
GH_PRECLONE_REPOS=madhura68/Scrum4Me,madhura68/scrum4me-mcp,madhura68/scrum4me-docker

CLAUDE_CODE_OAUTH_TOKEN=<Claude Code OAuth token>
```

Token-validatie:

```bash
read -s -p "Scrum4Me token: " TOKEN; echo
curl -i -H "Authorization: Bearer $TOKEN" http://127.0.0.1:3000/api/products
unset TOKEN
```

Verwacht:

```text
HTTP/1.1 200 OK
```

Worker health:

```bash
curl http://127.0.0.1:18081/health
```

Gezonde idle-output bevat:

```json
{
  "status": "idle",
  "heartbeatAgeSeconds": 1,
  "consecutiveFailures": 0
}
```

## Huidige Worker-Beperking

De Docker worker is gezond, maar Scrum4Me UI toont mogelijk nog:

```text
geen Claude worker actief
```

Oorzaak:

- De Docker health-server draait altijd.
- De daemon-loop draait altijd.
- Maar de DB-tabel `claude_workers` wordt nu alleen bijgewerkt door de MCP stdio-server.
- Die MCP stdio-server start pas binnen een echte Claude/MCP job-run.
- Bij een lege queue is de Docker worker dus idle en gezond, maar verschijnt hij niet als actieve worker in de UI.

Controle:

```bash
docker exec -it scrum4me-postgres psql -U scrum4me -d scrum4me \
  -c "select t.id, t.label, w.id as worker_id, w.last_seen_at from api_tokens t left join claude_workers w on w.token_id=t.id order by t.created_at desc;"
```

Gezonde Docker-worker maar lege presence:

```text
label       | worker_id | last_seen_at
worker-idea |           |
```

## Worker Aanpassingsplan

### Doelrollen

```text
worker-idea
  IDEA_GRILL
  IDEA_MAKE_PLAN
  PLAN_CHAT

worker-implementation
  TASK_IMPLEMENTATION
  SPRINT_IMPLEMENTATION
  later STORY_IMPLEMENTATION

worker-orchestrator
  PR_REVIEW
  CI_TRIAGE
  MERGE_CONFLICT_RESOLUTION
  REPAIR_FAILED_JOB
  CONTEXT_SUMMARY
```

## Fase 1 — Presence Fix

### Probleem

Worker-health is nu container-lokaal, maar UI-presence is DB-gebaseerd.

Nu:

```text
curl :18081/health -> online
claude_workers     -> leeg
UI                 -> offline
```

### Gewenst gedrag

Zolang de Docker daemon-loop draait, moet `claude_workers.last_seen_at` vers blijven, ook als de queue leeg is.

### Aanpassing

Verplaats worker-presence naar `scrum4me-docker/bin/run-one-job.ts` of naar een kleine runner-level heartbeat naast `run-agent.sh`.

Aanbevolen: in `run-one-job.ts`, direct na `getAuth()`:

```ts
const { userId, tokenId } = await getAuth()
await registerWorker({ userId, tokenId })
const heartbeat = startHeartbeat({ userId, tokenId, intervalMs: 10_000 })
```

In `finally`:

```ts
heartbeat.stop()
```

Niet unregisteren bij normale idle-exit. Anders gaat de UI-indicator flikkeren tussen iteraties.

### Acceptatie

Bij lege queue:

```bash
curl http://127.0.0.1:18081/health
```

toont:

```text
status idle
```

En:

```sql
select token_id, last_seen_at, now() - last_seen_at from claude_workers;
```

toont een recente `last_seen_at`.

## Fase 2 — Role-Aware Workers

### Probleem

De huidige worker claimt elke job die beschikbaar is. Daardoor kan `worker-idea` ook implementation jobs claimen.

### Nieuwe env

```env
SCRUM4ME_WORKER_ROLE=idea
```

Toegestane waarden:

```text
idea
implementation
orchestrator
```

### Claimfilter

`tryClaimJob` krijgt een role/capability-filter.

Mapping:

```text
idea:
  IDEA_GRILL
  IDEA_MAKE_PLAN
  PLAN_CHAT

implementation:
  TASK_IMPLEMENTATION
  SPRINT_IMPLEMENTATION

orchestrator:
  PR_REVIEW
  CI_TRIAGE
  MERGE_CONFLICT_RESOLUTION
  REPAIR_FAILED_JOB
  CONTEXT_SUMMARY
```

### Acceptatie

Test:

- Queue bevat één `IDEA_GRILL` en één `TASK_IMPLEMENTATION`.
- Alleen `worker-idea` actief: claimt alleen `IDEA_GRILL`.
- Alleen `worker-implementation` actief: claimt alleen `TASK_IMPLEMENTATION`.
- Beide actief: ieder claimt eigen jobtype.

## Fase 3 — DB/UI Uitbreiding

Breid `claude_workers` uit met:

```text
role
worker_name
container_name
last_status
last_job_id
last_error
```

UI toont dan:

```text
Idea worker            online / idle
Implementation worker  offline
Orchestrator           online / idle
```

## Fase 4 — Orchestrator Jobs

Nieuwe job kinds:

```text
PR_REVIEW
CI_TRIAGE
MERGE_CONFLICT_RESOLUTION
REPAIR_FAILED_JOB
CONTEXT_SUMMARY
```

Orchestrator mag:

- PR's inspecteren.
- CI-fouten samenvatten.
- Merge conflicts analyseren.
- Repair jobs aanmaken.
- Context capsules schrijven.
- Human escalation vragen.
- Draft PR naar ready begeleiden.

Orchestrator mag niet:

- Vrij featurewerk implementeren.
- Dezelfde branch tegelijk wijzigen als implementation-worker.
- Auto-mergen zonder checks.
- Secrets of tokens loggen.

## Fase 5 — Deployment

Na code-aanpassing:

```bash
cd /srv/scrum4me/repos/scrum4me-docker
git pull
cd /srv/scrum4me/compose
docker compose build worker-idea
docker compose up -d --force-recreate worker-idea
```

Checks:

```bash
curl http://127.0.0.1:18081/health
docker logs -f scrum4me-worker-idea
docker exec -it scrum4me-postgres psql -U scrum4me -d scrum4me \
  -c "select token_id, last_seen_at from claude_workers;"
```

## Aanbevolen Volgorde Vanaf Nu

1. Test één `IDEA_GRILL` job met de huidige worker.
2. Implementeer Fase 1: runner-level presence.
3. Rebuild `worker-idea`.
4. Verifieer UI online/idle bij lege queue.
5. Implementeer Fase 2: role-aware claiming.
6. Voeg `worker-implementation` toe.
7. Voeg pas daarna `worker-orchestrator` toe.

Niet meteen drie workers starten zonder role-aware claimfilter.
