diff --git a/docs/runbooks/tailscale-setup.md b/docs/runbooks/tailscale-setup.md new file mode 100644 index 0000000..5e578a5 --- /dev/null +++ b/docs/runbooks/tailscale-setup.md @@ -0,0 +1,446 @@ +# Ubuntu-omgeving (Postgres + app) via Tailscale bereikbaar maken vanaf de Mac + +## Context + +Er zijn twee Scrum4Me-omgevingen: +- **Omgeving 1** — productie: Vercel + Neon (managed Postgres). +- **Omgeving 2** — nieuw: een eigen Ubuntu-server (`scrum4me-srv`) die de + **volledige Scrum4Me-app (Next.js achter een reverse proxy) + zelf-gehoste + Postgres** gaat draaien. + +Het doel: vanaf de Mac (`janpeters-macbook-pro`) omgeving 2 kunnen gebruiken — +voor (1) een DB-client (psql/GUI), (2) de `scrum4me-docker` runner lokaal in +Docker, en (3) lokale dev van de hoofd-Scrum4Me-app. + +Tailscale is al geïnstalleerd en verbonden op beide machines: +- `janpeters-macbook-pro` → `100.73.234.116` +- `scrum4me-srv` → `100.118.195.120` (Linux, SSH aan) + +Wat nog ontbreekt: zowel Postgres als de Next.js-app op de Ubuntu-server +luisteren standaard alleen op `localhost` en zijn nog niet bereikbaar over de +Tailscale-interface. De database zelf is **al volledig ingericht** (schema + +data) — er is geen migratie- of seed-werk nodig, alleen netwerk-, auth- en +connectie-configuratie. + +**Beslissingen (van de gebruiker):** +- App-deploy op Ubuntu: **reverse proxy** (nginx/Caddy) vóór Next.js. +- DB-toegang: **hele tailnet** mag erbij (`100.64.0.0/10`) — bewuste keuze; + later eventueel te versmallen via Tailscale ACLs/groups. +- Postgres-rol: **nog onzeker** — het plan voegt een controle toe en adviseert + een dedicated rol. + +**Canonieke `SCRUM4ME_BASE_URL`:** `http://100.118.195.120` (reverse proxy op +poort 80 op de Tailscale-interface, **plain HTTP**). Tailscale (WireGuard) +verzorgt de transportencryptie binnen de tailnet, dus een tweede TLS-laag is +hier niet nodig. Dit raw-IP-adres resolvet ook vanuit een Docker-container +(geen MagicDNS-afhankelijkheid). HTTPS op de proxy is optionele hardening — zie +de noot onderaan; kies je daarvoor, gebruik dan een hostnaam die óók vanuit +Docker oplost en pas álle URL's hieronder consistent aan. + +**Bevinding uit de codebase:** in `scrum4me-docker` is de DB-koppeling puur +config. Zowel `bin/run-one-job.ts` (regel 30, 115) als de MCP-server +(`mcp-config.json` regel 9-10) lezen `DATABASE_URL` / `DIRECT_URL` uit de +omgeving. `bin/check-tokens.sh` (regel 35-38) doet bovendien een harde +`curl ${SCRUM4ME_BASE_URL}/api/products` — onbereikbaarheid is fataal +(regel 52-57). Er zijn **geen code-wijzigingen** nodig — alleen `.env`. + +## Voorwaarden (aantoonbaar voldaan vóór uitvoering) + +- [ ] Tailscale actief op beide machines (`tailscale status` toont beide nodes) +- [ ] SSH naar scrum4me-srv werkt (`ssh scrum4me-srv echo ok`) +- [ ] DB-schema aanwezig (tabellen + data) — géén migratie nodig + +## Voorwaarden (input van de gebruiker nodig) + +- Postgres-rol + wachtwoord + databasenaam op de Ubuntu-server (de "USER", + "PASS", "DBNAME" hieronder). Niet in de chat delen — alleen lokaal invullen. +- De reverse proxy biedt de app aan op `http://100.118.195.120` (poort 80). + Wijkt dit af, pas dan overal de canonieke URL consistent aan. + +--- + +## Deel A — Ubuntu: Postgres openstellen op de Tailscale-interface + +Uit te voeren op `scrum4me-srv` (via `ssh scrum4me-srv` of `tailscale ssh`). + +1. **Tailscale-IP bevestigen** + ```bash + tailscale status + tailscale ip -4 # verwacht: 100.118.195.120 + ``` + +2. **`listen_addresses` uitbreiden** — Postgres bindt standaard alleen aan + localhost. Vind het configbestand en pas aan: + ```bash + sudo -u postgres psql -c 'SHOW config_file;' # bv. /etc/postgresql/16/main/postgresql.conf + ``` + Zet in dat bestand: + ``` + listen_addresses = 'localhost,100.118.195.120' + ``` + Bewust **niet** `'*'` — zo bindt Postgres alleen aan localhost + het + Tailscale-adres, nooit aan de publieke interface. + + > ⚠️ **Boot-order:** door aan `100.118.195.120` te binden moet `tailscale0` + > al bestaan bij boot. Stap A6 maakt de systemd-ordering verplicht — sla A6 + > niet over, anders faalt Postgres na een reboot. + +3. **Rol, auth-methode en grants controleren/instellen** (voorkomt een + login- of permission-fout ná goede netwerkconfig). De rol is nog onzeker, + dus eerst inventariseren: + ```bash + sudo -u postgres psql -c '\du' + sudo -u postgres psql -c 'SHOW password_encryption;' + ``` + **Advies:** gebruik (of maak) een **dedicated runtime-rol** die alleen de + rechten heeft die de app/runner nodig heeft — geen superuser: + ```sql + CREATE ROLE scrum4me_app LOGIN PASSWORD 'lokaal-wachtwoord'; + GRANT CONNECT ON DATABASE DBNAME TO scrum4me_app; + + -- runtime-rechten op het bestaande (gevulde) public-schema: + GRANT USAGE ON SCHEMA public TO scrum4me_app; + GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO scrum4me_app; + GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO scrum4me_app; + + -- zodat ook later toegevoegde tabellen/sequences werken: + ALTER DEFAULT PRIVILEGES IN SCHEMA public + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO scrum4me_app; + ALTER DEFAULT PRIVILEGES IN SCHEMA public + GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO scrum4me_app; + ``` + **Migraties:** deze runtime-rol krijgt bewust géén DDL-rechten. De DB is al + ingericht, dus in normale operatie draaien er geen migraties via deze rol. + Moet je later vanaf de Mac toch een Prisma-migratie draaien, gebruik dan de + DB-owner-rol (apart wachtwoord), niet `scrum4me_app`. + + **SCRAM-verifier:** stap A4 kiest `scram-sha-256`. Een rol waarvan het + wachtwoord nog als **md5** is opgeslagen kan dan niet inloggen. Forceer een + SCRAM-verifier door het wachtwoord opnieuw te zetten (alleen lokaal op de + server, niet in chat/docs delen): + ```sql + ALTER ROLE scrum4me_app WITH PASSWORD 'lokaal-wachtwoord'; + ``` + +4. **`pg_hba.conf` — toegang vanaf de tailnet toestaan** + ```bash + sudo -u postgres psql -c 'SHOW hba_file;' + ``` + Voeg een regel toe (boven de bestaande `host`-regels). De gebruiker koos + bewust voor toegang vanaf de **hele tailnet**; maak rol en database wel + expliciet i.p.v. `all all`: + ``` + # Scrum4Me clients via Tailscale + host DBNAME scrum4me_app 100.64.0.0/10 scram-sha-256 + ``` + Let op: hiermee mag elke tailnet-node mét geldige credentials verbinden. + Wil je dat later inperken, doe dat via Tailscale ACLs/groups of versmal + het CIDR naar specifieke node-IP's. + +5. **Firewall (defense-in-depth)** — alleen relevant als `ufw` actief is: + ```bash + sudo ufw status + sudo ufw allow in on tailscale0 to any port 5432 proto tcp + ``` + Open 5432 **nooit** generiek (`sudo ufw allow 5432` zonder interface) — + dat zou de DB internet-breed openstellen. + +6. **Boot-order — VERPLICHT.** Postgres bindt aan `100.118.195.120`, een adres + dat pas bestaat nadat `tailscaled` `tailscale0` heeft opgezet. + **Zonder deze override faalt Postgres bij reboot** ("cannot assign requested + address"). Voeg een systemd-override toe: + ```bash + sudo systemctl edit postgresql # of postgresql@-main + ``` + ```ini + [Unit] + After=tailscaled.service + Requires=tailscaled.service + ``` + +7. **Postgres herstarten en verifiëren** + ```bash + sudo systemctl restart postgresql + sudo ss -tlnp | grep 5432 # moet 127.0.0.1:5432 én 100.118.195.120:5432 tonen + ``` + +--- + +## Deel B — Ubuntu: de Scrum4Me-app (reverse proxy) bereikbaar maken op Tailscale + +De runner-tokencheck (`check-tokens.sh`) cURL't `${SCRUM4ME_BASE_URL}/api/products` +en faalt hard als die URL onbereikbaar is. De Next.js-app draait achter een +reverse proxy, dus de **proxy** moet op het Tailscale-adres luisteren — niet +alleen op `localhost`. Canoniek: `http://100.118.195.120` (poort 80, plain HTTP). + +1. **Reverse proxy op de Tailscale-interface laten luisteren (poort 80)** + - **nginx:** in het server-block het `listen`-adres aan het Tailscale-IP + binden: + ``` + listen 100.118.195.120:80; + ``` + `sudo nginx -t` → `sudo systemctl reload nginx`. + - **Caddy:** site-adres `http://100.118.195.120:80` in de `Caddyfile`. + - Next.js zelf mag op `127.0.0.1:` blijven; alleen de proxy is + extern bereikbaar. + +2. **Boot-order — VERPLICHT.** Net als Postgres bindt de proxy aan een adres + dat `tailscaled` eerst moet aanmaken. **Zonder deze override faalt nginx/Caddy + bij reboot.** + ```bash + sudo systemctl edit nginx # of caddy + ``` + ```ini + [Unit] + After=tailscaled.service + Requires=tailscaled.service + ``` + +3. **Firewall voor poort 80** — alleen bij actieve `ufw`: + ```bash + sudo ufw allow in on tailscale0 to any port 80 proto tcp + ``` + +4. **Lokaal op de server verifiëren** + ```bash + curl -fsS -H "Authorization: Bearer $SCRUM4ME_TOKEN" \ + http://100.118.195.120/api/products >/dev/null && echo OK + ``` + +--- + +## Deel C — Mac: connectiviteit verifiëren (DB én app) + +1. **Tailscale-bereik** (al bevestigd: `scrum4me-srv` zichtbaar op + `100.118.195.120`): + ```bash + tailscale status + tailscale ping scrum4me-srv + ``` + +2. **MagicDNS check** — kan de Mac de server op hostnaam bereiken? + ```bash + ping -c1 scrum4me-srv + ``` + Zo ja: native macOS-clients mogen `scrum4me-srv` als host gebruiken. + +3. **Postgres — TCP- en psql-test** + ```bash + nc -vz 100.118.195.120 5432 + psql "postgresql://USER:PASS@100.118.195.120:5432/DBNAME?sslmode=disable" -c '\dt' + ``` + +4. **App — vanaf de Mac én vanuit een Docker-container** (Docker Desktop heeft + geen Tailscale-MagicDNS; daarom de canonieke raw-IP-URL): + ```bash + # vanaf de Mac + curl -fsS -H "Authorization: Bearer $SCRUM4ME_TOKEN" \ + http://100.118.195.120/api/products >/dev/null && echo "mac OK" + + # vanuit een container (simuleert de runner) + docker run --rm --env SCRUM4ME_TOKEN alpine sh -lc \ + 'wget -qO- --header "Authorization: Bearer $SCRUM4ME_TOKEN" \ + http://100.118.195.120/api/products >/dev/null && echo "docker OK"' + ``` + Slaagt de container-test niet (geen route naar de tailnet vanuit Docker + Desktop), dan moet Tailscale in/naast de runner-container draaien — apart + uit te zoeken; eerst de directe route testen. + +--- + +## Deel D — De drie consumenten koppelen + +Welke host elke consument gebruikt verschilt — MagicDNS werkt wél native op +macOS, maar niet binnen een Docker-container: + +| Consumer | Host | Reden | +|---|---|---| +| DB-client (psql/TablePlus) native | `scrum4me-srv` | MagicDNS werkt op macOS | +| scrum4me-docker runner (Docker) | `100.118.195.120` | Docker Desktop heeft geen MagicDNS | +| Hoofd-app lokale dev | `scrum4me-srv` | MagicDNS werkt op macOS | + +### 1. DB-client (psql / TablePlus / DBeaver) — native op macOS +Connection-string: +``` +postgresql://USER:PASS@scrum4me-srv:5432/DBNAME?sslmode=disable +``` +GUI-clients: host `scrum4me-srv` (of `100.118.195.120`), poort `5432`, +SSL uit. + +### 2. scrum4me-docker runner — bewerk `/Users/janpetervisser/Development/scrum4me-docker/.env` +``` +DATABASE_URL=postgresql://USER:PASS@100.118.195.120:5432/DBNAME?sslmode=disable +DIRECT_URL=postgresql://USER:PASS@100.118.195.120:5432/DBNAME?sslmode=disable +SCRUM4ME_BASE_URL=http://100.118.195.120 +``` +Belangrijk: +- **Gebruik het rauwe Tailscale-IP, niet `scrum4me-srv`.** Docker Desktop- + containers krijgen geen Tailscale-MagicDNS; de hostnaam resolvet niet + binnen de container. +- Laat de Neon-specifieke params (`channel_binding=require`, + `sslmode=verify-full`) weg — die gelden niet voor zelf-gehoste Postgres. +- Zorg dat `SCRUM4ME_TOKEN` een token van **omgeving 2** is — de tokencheck + loopt tegen de Ubuntu-app, niet meer tegen Vercel. + +> `SCRUM4ME_TOKEN` haal je op via de Ubuntu-app: Settings → API Tokens → nieuw +> token aanmaken. Een bestaand Vercel-token werkt **niet** tegen de +> Ubuntu-omgeving. + +### 3. Hoofd-Scrum4Me-app (lokale dev) — bewerk `/Users/janpetervisser/Development/Scrum4Me` +Dit is een **andere repo** dan `scrum4me-docker`. In die repo de `.env.local` +(of `.env`) aanpassen. De app draait native op macOS, dus de MagicDNS-hostnaam +`scrum4me-srv` mag hier wél: +``` +DATABASE_URL=postgresql://USER:PASS@scrum4me-srv:5432/DBNAME?sslmode=disable +DIRECT_URL=postgresql://USER:PASS@scrum4me-srv:5432/DBNAME?sslmode=disable +``` + +--- + +## SSL-keuze + +Aanbeveling: **`sslmode=disable`** voor Postgres en **plain HTTP** voor de app- +URL. Tailscale (WireGuard) versleutelt het transport al end-to-end binnen de +tailnet; een tweede TLS-laag op een zelf-gehoste Postgres of op de proxy levert +hier vooral configuratie-gedoe op. + +Optionele hardening (later, samenhangend uit te voeren): +- TLS-certs op Postgres + `sslmode=require`. +- HTTPS op de reverse proxy. Doe dit dan met een **DNS-naam die ook vanuit + Docker oplost** (raw-IP + cert geeft validatiefouten), en pas `SCRUM4ME_BASE_URL` + én alle verificatie-`curl`s consistent aan naar die `https://`-hostnaam. + +## Twee omgevingen naast elkaar houden + +Omdat omgeving 1 (Neon) blijft bestaan: bewaar twee env-varianten, bv. +`.env.neon` en `.env.ubuntu`, en symlink de actieve naar `.env`: +```bash +ln -sf .env.ubuntu .env # activeer Ubuntu-omgeving +ln -sf .env.neon .env # activeer Neon-omgeving +``` +Lichtgewicht en voorkomt dat je per ongeluk de verkeerde DB raakt. + +## Te wijzigen bestanden + +**Op `scrum4me-srv`:** +- `postgresql.conf` — `listen_addresses`. +- `pg_hba.conf` — tailnet-regel voor `DBNAME` + dedicated rol. +- Postgres-rol — dedicated `scrum4me_app`-rol + grants + `ALTER ROLE ... PASSWORD`. +- nginx/Caddy-config — `listen` op `100.118.195.120:80`. +- systemd-overrides — `After=/Requires=tailscaled.service` voor `postgresql` + en de proxy. +- evt. `ufw`-regels voor poort 5432 en 80 op `tailscale0`. + +**Op de Mac:** +- `/Users/janpetervisser/Development/scrum4me-docker/.env` — `DATABASE_URL`, + `DIRECT_URL`, `SCRUM4ME_BASE_URL`, `SCRUM4ME_TOKEN`. +- `/Users/janpetervisser/Development/Scrum4Me/.env.local` — `DATABASE_URL`, + `DIRECT_URL` (andere repo). +- Géén codewijzigingen in `scrum4me-docker`. + +## Verificatie (end-to-end) + +1. **Netwerk:** `nc -vz 100.118.195.120 5432` vanaf de Mac slaagt. +2. **DB-client:** `psql ".../DBNAME?sslmode=disable" -c '\dt'` toont de + Scrum4Me-tabellen; een test-`INSERT`/`SELECT` bevestigt dat de + `scrum4me_app`-grants kloppen. +3. **App-bereik:** de `curl`/`docker run`-tests uit Deel C-4 geven beide `OK`. +4. **Reboot-test:** herstart `scrum4me-srv`; controleer daarna met + `sudo ss -tlnp` dat Postgres én de proxy weer op `100.118.195.120` luisteren, + en herhaal de Mac/Docker-connectiviteitstests. +5. **Runner:** na `.env`-update `docker compose up -d --force-recreate`, + dan `docker compose logs -f` — `check-tokens.sh` moet + "OK: 100.118.195.120:5432 reachable" én "OK: SCRUM4ME_TOKEN works" loggen, + en de daemon-loop moet een job kunnen claimen uit de Ubuntu-DB. +6. **Hoofd-app:** lokale dev-server in `/Users/janpetervisser/Development/Scrum4Me` + start en leest data uit de Ubuntu-DB. + +## Veelvoorkomende fouten + +| Fout | Oorzaak | Fix | +|---|---|---| +| `could not translate host name "scrum4me-srv"` | MagicDNS niet actief (Docker) | Gebruik raw IP `100.118.195.120` | +| `cannot assign requested address` bij Postgres-start | `tailscale0` bestaat nog niet | A6 systemd-override toevoegen | +| `FATAL: password authentication failed` | SCRAM-verifier niet bijgewerkt | `ALTER ROLE scrum4me_app WITH PASSWORD '...'` herhalen | + +--- + +# Addendum — uitvoering Ubuntu-kant 2026-05-14 + +> Deel A + B zijn uitgevoerd. De server bleek een **andere topologie** te hebben +> dan dit plan aannam: Postgres én de reverse proxy draaien als **Docker +> containers**, niet host-geïnstalleerd. Dit addendum beschrijft wat er feitelijk +> is gebeurd. Deel C + D (Mac-kant) staan nog open. + +## Vastgestelde topologie (wijkt af van de aannames) + +| Plan nam aan | Werkelijkheid op `scrum4me-srv` | +|---|---| +| Host-Postgres (`/etc/postgresql/...`, `systemctl postgresql`) | Docker container `scrum4me-postgres` (postgres:17), data-volume `/srv/scrum4me/postgres` | +| Host nginx/Caddy | Docker container `scrum4me-caddy` (caddy:2), al luisterend op `0.0.0.0:80` + `:443` | +| Migratie/seed mogelijk nodig | Bevestigd niet nodig — db `scrum4me` was gevuld | + +Concrete waarden die het plan openliet: +- **Host** = dit ís `scrum4me-srv` (`100.118.195.120`) — Deel A/B dus direct uitgevoerd, niet via SSH. +- **DBNAME** = `scrum4me` +- **Rol** = `scrum4me_app` aangemaakt (non-superuser, DML-only), wachtwoord lokaal gegenereerd via `openssl rand -hex 24`. + +## Deel A — zoals feitelijk uitgevoerd (Docker-variant) + +| Plan-stap | Aanpassing | +|---|---| +| **A2** `listen_addresses` in `postgresql.conf` | **N.v.t.** — de container luistert intern al op `0.0.0.0`. Host-exposure = Docker port-mapping. In `/srv/scrum4me/compose/docker-compose.yml` toegevoegd: `- "100.118.195.120:5432:5432"` náást de bestaande `127.0.0.1:5432:5432`. Specifiek IP i.p.v. `0.0.0.0` — Docker's iptables-DNAT scoped dan op dat IP, publiek blijft dicht. | +| **A3** rol + grants | Identiek SQL, maar uitgevoerd via `docker exec -i scrum4me-postgres psql -U scrum4me -d scrum4me`. Idempotent script (`CREATE ROLE` of `ALTER ROLE ... PASSWORD`). De `ALTER ROLE ... PASSWORD` zet meteen een SCRAM-verifier. **Let op:** `CREATE ROLE` op de gedeelde productie-DB wordt door de auto-mode classifier geblokkeerd — moet via een script dat de gebruiker zelf draait. | +| **A4** `pg_hba.conf` | Bestand zit in het data-volume: host-pad `/srv/scrum4me/postgres/pg_hba.conf` (root-owned, sudo nodig). Regel toegevoegd onderaan (append is veilig — first-match, geen conflict). **Bevinding:** de postgres-image heeft al een catch-all `host all all all scram-sha-256` — onze scoped regel is dáárdoor strikt genomen redundant. Echte bescherming = IP-scoped port-binding + ufw. Catch-all strakker maken = aparte taak (hij draagt de docker-netwerk-clients). | +| **A5** ufw | Identiek: `ufw allow in on tailscale0 to any port 5432 proto tcp`. | +| **A6** boot-order | **Niet** `postgresql.service` (bestaat niet) maar `docker.service`. Drop-in `/etc/systemd/system/docker.service.d/tailscale-order.conf`. Bewust `After=tailscaled.service` + **`Wants=`** i.p.v. het door het plan voorgestelde `Requires=` — `Requires` op `docker.service` is fragiel (faalt tailscaled ooit, dan start de hele docker-stack niet). `After=` lost de race op; `Wants=` trekt tailscaled mee zonder hard-fail. | +| **A7** restart + verify | `docker compose up -d postgres` (recreate — `restart` pakt port-wijzigingen niet). `ss -tln` toont nu `127.0.0.1:5432` én `100.118.195.120:5432`. Verificatie met een wegwerp-container: `docker run --rm --network host postgres:17 psql "postgresql://scrum4me_app:...@100.118.195.120:5432/scrum4me?sslmode=disable" -c '\dt'` — `--network host` simuleert exact hoe de Mac het ziet. | + +## Deel B — zoals feitelijk uitgevoerd (Docker-variant) + +| Plan-stap | Aanpassing | +|---|---| +| **B1** proxy op tailscale-interface | **Grotendeels al gedaan** — de Caddy-container publiceert al `0.0.0.0:80`, dus luistert al op `tailscale0`. Alleen een site-block toegevoegd aan `/srv/scrum4me/caddy/Caddyfile`: `100.118.195.120:80 { reverse_proxy 172.18.0.1:3000 }`. | +| **B2** boot-order proxy | **Niet nodig.** Caddy bindt aan `0.0.0.0:80`, niet aan een IP-specifiek adres — er is geen `tailscale0`-race. (Alleen Postgres had de IP-specifieke binding, vandaar dat A6 wél nodig was.) | +| **B3** ufw poort 80 | **Niet nodig.** Poort 80 stond al op `ALLOW IN Anywhere`. | +| **B4** verifiëren | `curl -sI http://100.118.195.120/` → `200 OK`, geen redirect. `/api/products` → 401 (bereikbaar, auth vereist). | + +## Bugs / valkuilen tegengekomen tijdens uitvoering + +1. **Caddy single-file bind-mount wordt stale na een atomic-rename edit.** + `/srv/scrum4me/caddy/Caddyfile` is als enkel bestand ge-bind-mount. Editors + (en de Edit-tooling) schrijven vaak via write-temp + rename = nieuwe inode. + De container blijft naar de oude inode wijzen → `caddy reload` leest de + **oude** content, schijnbaar zonder fout. Symptoom hier: het nieuwe + site-block leek "stil gedropt" door Caddy's adapter, maar de container zág + het block simpelweg niet. + **Fix / regel:** na een Caddyfile-edit `docker compose up -d --force-recreate + caddy` (of `restart`) — **niet** `caddy reload`. De recreate her-bindt de + mount op de nieuwe inode. (Eerder in het project werkte een Caddyfile-edit + wél, juist omdat daar toevallig een `restart` op volgde.) + +2. **`http://` vs `:80` syntax — bleek een rode haring.** + Aanvankelijk leek Caddy's Caddyfile-adapter `http://100.118.195.120` te + droppen. Geïsoleerd getest werkte beide syntaxen prima; het echte probleem + was bug #1 (stale mount). De definitieve regel gebruikt `100.118.195.120:80` + — ondubbelzinnig plain-HTTP-op-poort-80. + +## Verificatie-status + +| Plan verificatie-stap | Status | +|---|---| +| 1. `nc`/TCP naar 5432 | ✓ `psql` als `scrum4me_app` via `100.118.195.120:5432` werkt, ziet tabellen | +| 2. DB-client grants | ✓ `SELECT` op `idea_products` werkt onder `scrum4me_app` | +| 3. App-bereik | ✓ `http://100.118.195.120/` → 200, `/api/products` → 401 | +| 4. **Reboot-test** | ✗ **Nog niet gedaan** — productie-server niet herstart. Handmatig uitvoeren op rustig moment; check daarna `ss -tlnp \| grep 5432`. | +| 5. Runner | — Mac-kant (Deel C/D), nog open | +| 6. Hoofd-app lokale dev | — Mac-kant, nog open | + +## Gewijzigde bestanden op `scrum4me-srv` + +- `/srv/scrum4me/compose/docker-compose.yml` — postgres `ports`: extra `100.118.195.120:5432:5432` +- `/srv/scrum4me/caddy/Caddyfile` — site-block `100.118.195.120:80` +- `/srv/scrum4me/postgres/pg_hba.conf` — tailnet-regel (+ `.bak-`) +- `/etc/systemd/system/docker.service.d/tailscale-order.conf` — boot-order drop-in (nieuw) +- ufw — regel `5432/tcp on tailscale0` +- Postgres-rol `scrum4me_app` — aangemaakt met grants op db `scrum4me`