dienst_echo_whatsappgroep/AGENTS.md

352 lines
20 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.

# Dienst Bot — Productie-informatie
## 🚨 VERPLICHT: bridgestore is GEEN waarheid over bezorging
> Geldt ook hier (wuzapi-tijdperk): afhandeling/acks van de bridge zijn géén
> bezorgbewijs. Deze sectie beschrijft de bewezen wwebjs-fabricatie (historie, tot
> wuzapi-migratie 2026-10-11); de les blijft: alleen het apparaat van de ontvanger is
> waarheid. Bij wuzapi geldt: de `/chat/send/*`-respons `{"code":200}` bevestigt
> serverontvangst, niet zichtbaarheid op het doelapparaat.
De wwebjs-api **bridgestore is niet betrouwbaar** als bewijs dat een bericht bezorgd/gelezen
is. Bevestigd 2026-09-24: een dienst-PNG werd 3× naar dezelfde ontvanger "verzonden"
(success + message-ID), de store toonde 3 berichten **allemaal ack 3**, maar de ontvanger
zag in de chat maar **één** bericht (ghost-sends: id geretourneerd, nooit echt zichtbaar).
- **`/chat/fetchMessages`-acks en de lokale message-store van wwebjs-api zijn een lokale
registratie, géén bezorgbewijs.** Ze kunnen ack 1/2/3 tonen voor berichten die nooit
aankwamen.
- **Ook de live `message_ack`-progressie is sinds 2026-09-26 géén bezorgbewijs meer:**
de staged-ghost van dienst 5219 blz 2 kreeg via de webhook een `ack=2` afgegeven voor een
bericht dat nooit getransmitteerd was (stond niet eens in de WhatsApp-app van de eigenaar).
De bridge resolvet media-sends **optimistisch op lokale staging** en fabriceert daarna
ack-events + delivery-info. Ack-tracking (stopt bij ack ≥ 2) kan deze klasse per ontwerp
niet vangen.
- **Enige geldige bewijzen van levering:** (1) de **zichtbare werkelijkheid op het
apparaat** van de ontvanger/betrokkene; (2) `getInfo` (`message.getInfo()`): bij een
staged-ghost blijft `read` leeg terwijl `delivery` gevuld kan zijn — het enige
betrouwbare bridge-signaal is `read`/`readRemaining`.
- **Trek NOOIT een leveringsconclusie uit een later gelezen fetchMessages-ack.** Claim geen
"bezorgd/gelezen" op basis van de store — dat geeft verkeerde adviezen.
## Waar draait de bot?
De bot draait op de **nextcloud** server (192.168.5.252) als **systemd service**.
| Server | Locatie |
|--------|---------|
| nextcloud (192.168.5.252) | systemd service `dienst-bot.service` |
| nextcloud (192.168.5.252) | systemd service `telegram-bot.service` |
| ~~MacMini~~ | ~~LaunchDaemon (verouderd, gestopt juli 2026)~~ |
## Bestanden & paden (lokaal)
| Wat | Pad |
|-----|-----|
| Source code | `/Users/eddy/src/dienst_echo_whatsappgroep/` |
| Environment (.env) | `/Users/eddy/src/dienst_echo_whatsappgroep/.env` |
| Script | `src/index.js` |
| Groep | `Dienst opvragen EdGPT-EBS` (`120363407613652082@g.us`) |
## Nextcloud server (systemd)
### Poort-overzicht
| Poort | Service | Type | Beheer |
|-------|---------|------|--------|
| **8082** | wuzapi | systemd service | WhatsApp bridge (whatsmeow, 1 sessie EdGpt) |
| **3007** | wuzapi-multiplexer | systemd service | Webhook router per chat-groep |
| **3002** | dienst-bot | systemd service | Dienst opvragen bot (dit project) |
| **3003** | aankondigingen-webhook | systemd service | Losse webhook receiver (EBS-aankondigingen) |
| **3004** | telegram-bot | systemd service | Telegram watchdog (dit project) |
| **3006** | ebs-merplus | systemd service | MerPlus→EBS aankondigingen doorstuurbot |
| ~~3001~~ | ~~wwebjs-api~~ | ~~Docker container~~ | **Vervangen door wuzapi (2026-10-11), container gestopt** |
| ~~3000~~ | Forgejo | systemd service | **NIET onze service!** Niet aanraken. |
### dienst-bot
| Wat | Waarde |
|-----|--------|
| Systemd service | `dienst-bot.service` |
| Working directory | `/home/aiaiaiaiagent/whatsapp-scripts/dienst_echo_whatsappgroep` |
| User | `aiaiaiaiagent` |
| Poort dienst-bot | `3002` |
| Poort telegram-bot | `3004` |
| Health check dienst-bot | `curl http://localhost:3002/health` |
| Health check telegram-bot | `curl http://localhost:3004/health` |
### Commando's
```bash
# Status
ssh aiaiaiaiagent@nextcloud "systemctl status dienst-bot"
ssh aiaiaiaiagent@nextcloud "systemctl status telegram-bot"
# Herstarten
ssh aiaiaiaiagent@nextcloud "sudo systemctl restart dienst-bot"
ssh aiaiaiaiagent@nextcloud "sudo systemctl restart telegram-bot"
# Logs
ssh aiaiaiaiagent@nextcloud "journalctl -u dienst-bot --since today"
ssh aiaiaiaiagent@nextcloud "journalctl -u telegram-bot --since today"
```
## WhatsApp API (wuzapi)
De WhatsApp-bridge is sinds **2026-10-11 wuzapi** (vroeger wwebjs-api op :3001, container
gestopt). wuzapi draait als systemd service op **:8082**. Volledige installatie/
config/credentials staan in de vault ([[Wuzapi]] + [[Whatsapp rest-api]] historie).
| Wat | Waarde |
|-----|--------|
| Systemd service | `wuzapi.service` |
| URL | `http://127.0.0.1:8082` |
| Binary | `/usr/local/bin/wuzapi` (SELinux `bin_t`) |
| Datadir | `/home/aiaiaiaiagent/wuzapi/` (.env + SQLite `dbdata/`) |
| Sessie | `EdGpt` (user id `09efd1be…`, een sessie voor heel nummer 31616173970) |
| User token | `EdGpt-BOhLAsGkIfVZnKVi` (vault) |
| Webhook | `http://127.0.0.1:3007/webhook` (multiplexer) |
| Events | Message |
> **1 sessie + multiplexer (besluit 2026-10-11):** wuzapi koppelt per user één
> whatsmeow-device. Twee users op hetzelfde nummer delen dezelfde `users.jid` en
> conflicts in `resolveDeviceStore` (events gingen willekeurig naar één van beide
> webhooks). Daarom: **1 sessie (EdGpt)** voor alle WhatsApp-verkeer van het nummer, en de
> **multiplexer** (:3007) routeert per chat-groep naar de juiste bot (zie hieronder).
### Multiplexer (`src/multiplexer.js`, systemd `wuzapi-multiplexer.service`)
Ontvangt de wuzapi-webhook op :3007 en stuurt per chat-groep door:
| Chat-groep | Naar |
|---|---|
| `120363407613652082@g.us` (Dienst opvragen) | dienst-bot :3002 |
| `120363429978048879@g.us` (Anonieme aankondigingen) | EBS-MerPlus :3006 |
### Sessie status
```bash
curl http://127.0.0.1:8082/session/status -H "Token: $TOKEN"
# → {"code":200,"data":{"LoggedIn":true,"Connected":true},...}
```
### Wuzapi-commando's
```bash
ssh aiaiaiaiagent@nextcloud
sudo -A systemctl status wuzapi # bridge status
sudo -A journalctl -u wuzapi --since today # logs
# Webhook-verzending zien:
sudo -A journalctl -u wuzapi | grep "Calling user webhook"
# Multiplexer routing (+ dienst-bot + MerPlus):
sudo -A journalctl -u wuzapi-multiplexer / -u dienst-bot / -u ebs-merplus --since today
```
### wuzapi-API-knoppen (voor de bot)
- Send text: `POST /chat/send/text` `{Phone, Body}` — header `Token:` (user token)
- Send image: `POST /chat/send/image` `{Phone, Image:"data:image/jpeg;base64,...", Caption?}`
- Send document: `POST /chat/send/document` `{Phone, Document, FileName, Caption?}`
- Mark read: `POST /chat/markread` `{Id:[...], Chat}`
- Admin (users/webhook-beheer): `Authorization:` header met admin-token (vault)
- Webhook-payload: `{type:"Message", event:{Info:{Chat:{User,Server}, Sender, IsFromMe, ID}, Message:{...}}, base64?, mimeType?, fileName?}`
## Release-proces
1. Code wijzigen lokaal in `/Users/eddy/src/dienst_echo_whatsappgroep/`
2. Commit + pushen naar remote: `git add -A && git commit -m "..." && git push origin main`
3. Git pull op nextcloud:
```bash
ssh aiaiaiaiagent@nextcloud "cd ~/whatsapp-scripts/dienst_echo_whatsappgroep && git pull && npm ci"
```
NB: `diensten/`, `.env`, en `AGENTS.md` staan in `.gitignore` — die worden niet overschreven door `git pull`.
Alleen als `.env` gewijzigd is, sync deze expliciet (géén `--delete`, géén volledige rsync):
```bash
rsync -avz /Users/eddy/src/dienst_echo_whatsappgroep/.env aiaiaiaiagent@nextcloud:/home/aiaiaiaiagent/whatsapp-scripts/dienst_echo_whatsappgroep/.env
```
4. Herstarten op nextcloud:
```bash
ssh aiaiaiaiagent@nextcloud "sudo systemctl restart dienst-bot"
ssh aiaiaiaiagent@nextcloud "sudo systemctl restart telegram-bot"
ssh aiaiaiaiagent@nextcloud "sudo systemctl restart wuzapi-multiplexer" # als multiplexer.js wijzigde
```
Voor EBS-MerPlus geldt dezelfde flow in zijn eigen repo (`~/whatsapp-scripts/EBS-MerPlus`,
service `ebs-merplus` :3006) — inclusive herstart.
5. Logs checken:
```bash
ssh aiaiaiaiagent@nextcloud "journalctl -u dienst-bot --since '5 minutes ago'"
ssh aiaiaiaiagent@nextcloud "journalctl -u telegram-bot --since '5 minutes ago'"
```
## Configuratie
De `.env` bevat alle instellingen (niet in git). Template staat in `.env.example`.
> **De secties hieronder (`Resilience maatregelen`, ack-tracking, RCA staged-ghost,
> Media-levering twee foutklassen, `dienst-media-test.js`) beschrijven de wwebjs-api-
> historie vóór de wuzapi-migratie van 2026-10-11.** Bewaard voor context/RCA; de
> huidige bridge is wuzapi (geen wwebjs-container meer). De staged-ghost-race en
> ack-fabricatie waren eigen aan de wwebjs-fork.
## Resilience maatregelen (juni 2026)
Deze maatregelen zijn toegevoegd na een incident met HTTP 500 van de wwebjs-api en een EADDRINUSE restart-loop.
### Retry-logic (`src/whatsappApi.js`)
- `sendTextMessage` en `sendImageMessage` hebben ingebouwde retry met exponential backoff
- Maximaal 3 pogingen, met vertraging van 1s → 2s → 4s
- Wordt geactiveerd bij 5xx HTTP-fouten (500, 502, 503, 504) en connectiefouten (ECONNRESET, ECONNREFUSED, ETIMEDOUT)
- Niet-retryable fouten (4xx) worden direct doorgelaten
### Port binding en graceful shutdown (`src/index.js`)
- Gebruikt de Node.js HTTP server met `reuseAddr: true` om TIME_WAIT poortconflicten te voorkomen
- Bij `EADDRINUSE` wordt automatisch na 5 seconden opnieuw geprobeerd te binden
- **Graceful shutdown**: bij SIGTERM/SIGINT worden actieve connecties netjes afgesloten (max 10s timeout)
### Ack-tracking voor hangende media — VERWIJDERD (dwaalspoor, 2026-09-26)
Er stond een ack-tracker (`src/ackTracker.js`) die `message_ack`-events volgde en bij
ack<2 na `ACK_STUCK_TIMEOUT_MS` een admin-melding gaf. **Verwijderd uit de code**: de
bridge fabriceert acks (dienst 5219 blz 2 kreeg `ack=2` voor een bericht dat nooit
getransmitteerd was) — ze zijn géén bezorgbewijs en gaven verkeerde signalen. Daarom ook
`message_ack`-verwerking in de bot weg. Enige betrouwbare bronnen: het apparaat en
`getInfo` (`read`/`readRemaining`). Zie RCA hieronder.
- **Media-pacing zit bridge-breed** (wwebjs-api, niet de bot): bij een staged-ghost-race
(2026-09-26, dienst 5219 blz 2) overlappen opeenvolgende media-sends in de ene
chromium-tab en verliest de 2e (bestaat in store + ack2 maar wordt nooit verzonden).
De bridge serialiseert nu álle media-sends over alle sessies/clients door één keten
(`serializeMediaSend` in `wwebjs-api-build-p25/src/utils.js`) en wacht
**`MEDIA_SEND_DELAY_MS`** (bridge-`.env`: `/home/aiaiaiaiagent/wwebjs-api/.env`, nu `500`
sinds 2026-09-28 teruggezet van 1000; delay lost de race niet op, zie RCA)
ná elke media-send.
- **Pre-render = de standaard sinds 2026-10-05.** De sœur-repo **whatsappdienst**
rendert álle dienstschema's vooraf als **één lange JPEG per dienst** (meerpagina =
`magick … -append` tot één beeld; zie https://git.eddydevink.nl/eddy/whatsappdienst/issues/2)
en `dienst-tijden.json` verwijst naar één bestand per dienst. `dienst-bot` (deze repo)
stuurt **gewoon de bestanden uit `paginas[].bestandsnaam`** zoals voorheen — geen
runtime-merge, geen pacing. Single send = geen staged-ghost (2e pagina van een paar
verliest). UX: één scrollbaar beeld per dienst.
- Tijdelijk had de bot een runtime-merge als fallback (`MERGE_PAGES`/`MAGICK_BIN`/
`PAGE_SEND_DELAY_MS`); dat is verwijderd zodra whatsappdienst single-file levert. De
historie (ack-gate-falsificatie, JPEG-conversie, 4027-casus) staat hieronder.
- **Bridge-`.env`-wijzigingen**: `env_file` wordt bij container-create gelezen → een
`MEDIA_SEND_DELAY_MS`-wijziging werkt pas na een **recreate**
(`cd ~/wwebjs-api && sudo -A docker compose up -d`); een kale `docker restart` (ook de
wekelijkse timer) herlaadt de `.env` **niet**. Recreate = sessie-herverbinding, alleen
met expliciete toestemming (hard verbod op container-restart staat in de vault).
Config/code/waarom volledig gedocumenteerd in vault [[Whatsapp rest-api]]
("Bridge-wide media-send throttle") en [[Dienst media bezorgtest]] (RCA 2026-09-26).
Tijdelijke mitigatie — permanente fix (fork: send óf echt transmetteren óf zichtbaar
falen) staat nog open.
**Ack-semantiek (wwebjs 1.34.7):** ack 1 = verstuurd naar server (één grijs vinkje), ack 2 = bezorgd op apparaat (twee vinkjes), ack 3 = geopend/gelezen. Let op: eerdere notities die "één grijs vinkje (ack 2)" noemden, zaten er dus naast. **Let op 2 (2026-09-26):** óók deze acks zijn geen leveringsbewijs — zie RCA hieronder.
> **Verificatie 2026-10-05 — de "permanente fix"-richting is hiermee gefalsifieerd overruled.** Gemeten (live webhook-instrumentatie) dat **geghoste sends wél ack=1 én ack=2 events afgeven** (055.png/jpg ghosten met acks 1+2; alleen ack=3 bleef uit doordat het bericht nooit zichtbaar was). Daardoor: (1) een `ack ≥ 1`-gate (upstream-advies #201849/#201727) vangt ghosts **niet**; (2) `await sendMsgResultPromise` stond al aan — `waitUntilMsgSent:true` in de fork (clientController.js `sendOptions`) en wordt al geawait (Utils.js) maar resolved óók optimistisch (= no-op). Er is **geen server-zijdig signaal dat ghost vs. bezorgd discrimineert** (store, live acks, getInfo/read, ontvangende sessie — alles fabricerbaar of gebruikersafhankelijk). Enige waarheid = apparaat. De geplande fork-patch is daarom afgeblazen vóór bouwen/recreate. Werkende mitigaties i.p.v. daarvan: **alle dienstafbeeldingen als JPEG** (convert_png_to_jpg.sh in Whatsapp_Dienst/diensten/png/, json op `.jpg`; PNG-versies behouden) + bot `PAGE_SEND_DELAY_MS=3000` (gemeten transmissie-L ≈ 0.83 s × 3.5). Verlies blijft positioneel/contextafhankelijk (4027-blz2 verliest vaker dan bv. 4427), beeldstatistiek discrimineert níet (4027-055 ≈ 4427-202 qua mean/std/entropy). Bij gemist: blz los opnieuw sturen (werkt).
### RCA 2026-09-26: staged-ghost = race-conditie, géén bestandprobleem
Casus: dienst 5219 (2 pagina's), 09:29 gevraagd in de groep, bot stuurt beide pagina's
prive. App toont alleen blz 1; **blz 2 stond in het geheel niet in de WhatsApp-app van de
eigenaar** (geen transmissie). Toch: bridge retourneerde success + message-ID (`3EB040AD…`),
store toont het bericht, live `message_ack` gaf `ack=2`, `getInfo` toonde `delivery` gevuld
maar `read` leeg. Geen enkele fout in de container-logs.
Mechanisme (bewezen uit fork-broncode + logs):
- De send loopt via `WAWebSendMsgChatAction.addAndSendMsgToChat(chat, message)` →
`[msgPromise, sendMsgResultPromise]`. De bridge zet `waitUntilMsgSent: true`
(stock wwebjs-api, `clientController.js`), maar de promise resolve **ook optimistisch op
lokale staging** — een echte serverbevestiging wordt nooit afgewacht. HTTP-success +
message-ID betekenen dus **niet** "verzonden".
- **Patroon (gebruiker, consistent met logs):** de ghost treedt alleen op bij **2+ media
direct achter elkaar**, en dan altijd de **2e** van het paar; enkele sends falen nooit.
- Verklaring: de bot wacht per pagina op het HTTP-signaal (staging), niet op echte
transmissie → blz 2 wordt gestart terwijl blz 1's **echte** upload/transmissie nog in
de lucht is. Twee echte media-transmissies overlappen in de ene chromium-tab → de 2e
verliest (intermitterend) → staged-ghost.
- Gevolgen: **`MEDIA_SEND_DELAY_MS` kan dit niet oplossen** (wacht op optimistisch
signaal); **poppler/re-render maskeert het alleen** (nieuwe poging = nieuwe race-rol,
soms gewonnen — wassen neus); **`sendMessageViaUi` lost het óók niet op** (wacht op
dezelfde promises, alleen omzeilt het de media-editor); **ack-tracking kan het niet
vangen** (stopt bij gefabriceerde ack ≥ 2).
Enige betrouwbare signalen: apparaat-waarheid; `getInfo` `read`/`readRemaining` (blijft
leeg bij staged). Aanbevolen richting (nog niet geïmplementeerd): de fork moet ná
`addAndSendMsgToChat` écht wachten op transmissiebevestiging — pas dan serialiseren
multi-page sends vanzelf en is de race weg.
## Media-levering: twee foutklassen (verwar bloem niet met onkruid)
**1. Staged-ghost-race (de hoofdklasse, 2026-09-26)** — zie RCA hierboven: een
direct-opvolgende media-send overlapt in de ene tab en de 2e verliest.
Bestand bytes zijn nooit het probleem; **re-render (poppler) is een dwaalspoor en geen
fix** — het maskeerde de race alleen (nieuwe poging = nieuwe race-rol). Mitigatie =
bridge-breed throttle (`MEDIA_SEND_DELAY_MS`, 500 — delay loss effect, RCA 26-09; teruggezet
van 1000 op 2026-09-28) — zie hierboven. Bij vermoeden:
opnieuw versturen, apparaat als waarheid.
**1b. Transparante (alpha) PNG — WEL een bestandsklasse (2026-09-28, casus 4039).**
Dienst 4039 (2 blz): blz 79 kwam **nooit** aan — ongeacht volgorde (078 eerst/laatst),
timing (500/1000 ms), UI-reset ertussen of vice versa; verschijnt in beide stores met
server-locators maar rendert niet op het apparaat. Verse `pdftoppm`-render (ondoorzichtig
sRGB, 147 KB i.p.v. 186 KB RGBA) kwam **wel** aan; na vervanging van `4039-079.png`
kwamen beide blz van 4039 aan. Vermoedelijke oorzaak: de opgeslagen PNG's zijn RGBA met
~90% volledig transparante pixels; WhatsApp's re-encode-pipeline (PNG→JPEG) is berucht
onbetrouwbaar met transparante PNGS (o.a. wwebjs-issues #2695 (silent fail PNG send,
multi-device), #3603 (transparante PNG blurred), #201772 (image-send hang); loss of alpha op transparante PNG's in officiële clients ([StackOverflow 37554514](https://stackoverflow.com/questions/37554514/transparent-png-images-in-whatsapp))).
554 van de 555 opgeslagen PNG's zijn RGBA met (deels transparante) achtergrond → zelfde risico op andere diensten (mogelijk de echte oorzaak van de klasse-1 "staged ghosts": de bestand-inhoud i.p.v. timing). De bot stuurt sinds `07b511e` mimetype op basis van extensie; ondoorzichtig renderen bij de bron lost het op; documents via `sendMediaAsDocument` omzeilt de image-pipeline maar is afgewezen als productie-richting.
**2. Ontvanger offline/weigert (ack-1-hang)** — bericht staat gequeued, apparaat pakt het
niet op. Check eerst óf het apparaat online is wacht af; persoonsgebonden.
> **Detectie/verificatie:** de bridgestore en live acks zijn **geen** bezorgbewijs (bridge
> fabriceert ze). Enige betrouwbare bronnen: het apparaat en `getInfo`
> (`message.getInfo()` → `read`/`readRemaining`). `dienst-media-test.js` (server,
> `/home/aiaiaiaiagent/whatsapp-scripts/dienst-media-test.js`) bestaat nog maar meet acks;
> zijn `ack≥3`-criterium is daardoor onbetrouwbaar — bevestig altijd op het apparaat/read.
**Afbeeldingsformaat:** sinds `07b511e` leidt `sendImageMessage` de mimetype af uit de
extensie — `png`→`image/png`, `jpg`/`jpeg`→`image/jpeg` (onbekend→`image/png`). De bot is
dus voorbereid als de bron-repo overgaat naar JPEG; de `bestandsnaam` uit
`dienst-tijden.json` wordt al extensie-agnostisch ingelezen (`getPngFilePaths`).
### Script-parameters (voor gebruik)
```bash
ssh aiaiaiaiagent@nextcloud
node /home/aiaiaiaiagent/whatsapp-scripts/dienst-media-test.js \
--map /home/aiaiaiaiagent/whatsapp-scripts/Whatsapp_Dienst/diensten/png \
--json /home/aiaiaiaiagent/whatsapp-scripts/Whatsapp_Dienst/diensten/dienst-tijden.json \
--chat 275749852475605@lid \
--batch 20 --wacht-minuten 10 --rondes 3
```
## Telegram Watchdog (`src/telegramBot.js`)
Sinds juli 2026 draait er een apart telegram-bot proces als backup communicatiekanaal.
| Wat | Waarde |
|-----|--------|
| Systemd service | `telegram-bot.service` |
| Poort | `3004` |
| Health check | `curl http://localhost:3004/health` |
### Hoe het werkt
- **Startup**: stuurt "online" melding naar admin via Telegram
- **Health checks** (elke 5 min): checkt dienst-bot (`/health`) en wwebjs-api (`/session/status`)
- **Statusovergangen**: alleen bij up→down of down→up wordt een notificatie gestuurd (geen spam)
- **Fallback**: als `notifyAdmin()` in dienst-bot faalt (WhatsApp API down), wordt `POST /notify` naar telegram-bot gestuurd
### .env variabelen
| Variabele | Doel |
|-----------|------|
| `TELEGRAM_BOT_TOKEN` | Token van @BotFather |
| `TELEGRAM_ADMIN_CHAT_ID` | Jouw Telegram chat-ID |
| `TELEGRAM_BOT_PORT=3004` | Poort voor interne `/notify` endpoint |
| `DIENST_BOT_HEALTH_URL` | URL voor dienst-bot health check |
| `WWEBJS_API_SESSION_URL` | URL voor wwebjs-api session status |
| `HEALTH_CHECK_INTERVAL_MS=300000` | Interval tussen health checks |
| `TELEGRAM_BOT_URL` | (in dienst-bot .env) URL voor Telegram fallback |