Files
UmbrelApps/Docs/Plannen/Actief/008-Umbrelapp/PLAN.md
T
HarmenandClaude Opus 5 89aa06639b Het relay-programma: de relay van Evolu met onze eigen allowlist
tools/evolu-relay/src/ bevat nu een eigen programma dat createRelay uit
@evolu/nodejs aanroept met de twee terugroepfuncties die het bedoelde
uitbreidpunt vormen. De relay zelf komt uit npm en wordt niet nagebouwd of
aangepast; de opstartvolgorde is overgenomen uit apps/relay/src/index.ts van
Evolu zelf.

De opzet is drie bestanden met een harde scheiding, en die scheiding is de reden
dat hier iets te testen valt. policy.js bevat het beleid als pure functies: geen
bestanden, geen netwerk, geen klok. store.js is de enige plek met schijf erin.
index.js doet niets anders dan lezen, doorgeven en opslaan.

Het beleid: de eerste eigenaar die zich meldt wordt geleerd, een schakelaar
bepaalt of er nog nieuwe bij mogen, en een eigenaar is te blokkeren, alsnog toe
te laten of te vergeten. Geweigerde pogingen worden onthouden voor de pagina,
afgekapt op twintig, want elke poging is een id dat de ander zelf verzint. Een
onleesbaar owners.json wordt opzij geschoven en de app gaat dan dicht in plaats
van open: we weten dan niet wie er toegelaten was, en met de leerstand aan zou de
eerstvolgende die verbindt de nieuwe eigenaar worden.

Met test: node tests/test_limiter.mjs, 60 toetsen, en de toetsen gaan over de
guards en niet over het gelukkige pad. De beslissende regel is muteertest gedaan
en de juiste toets viel om: een geblokkeerde eigenaar mag er niet alsnog in
doordat de leerstand aanstaat. Het bestand is .mjs omdat de repo-root geen
package.json heeft en een .js daar als CommonJS gelezen zou worden.

build.sh bouwt niet langer de repo van Trezor maar onze eigen Dockerfile, dus git
is er niet meer voor nodig en de pin zit nu in package.json. De image is
node:24-slim en niet alpine, want better-sqlite3 heeft binaries voor glibc en niet
voor musl. Er is nog geen package-lock.json; het script waarschuwt daarvoor en het
staat als taak.

Onderweg bleek een aanname van vanmiddag fout: de 1 MB uit de gepubliceerde image
geldt per schrijfactie en niet per eigenaar. Er valt dus geen labelgeschiedenis
tegenaan te lopen. Dat is rechtgezet in het plan en in de naslag, en het getal is
overgenomen als bewuste keuze met RELAY_MAX_WRITE_BYTES ernaast.

Wat er niet in zit en ook niet gegokt is: data per eigenaar wissen. Het beleid kan
een eigenaar vergeten, maar zijn berichten staan in de SQLite van de relay, en dat
is andermans schema.

Tests: alle vier groen (32, 54, 39 en 60 goed, 0 fout).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 11:12:41 +02:00

25 KiB

Umbrelapp - plan

App: Evolu Relay. Ontwerp en afbakening. Dit bestand lees je zelden, alleen bij twijfel over scope of architectuur. Status staat in TAKEN.md, geschiedenis in PROGRESS.md, onbesliste punten in OPEN.md.

Gepromoveerd uit Masterplannen/ op 25-08-2026, toen de gebruiker besloot het pakket meteen te maken in plaats van te wachten op de proefopstelling.

1. Doel

Een tweede app in deze store: whatsnext-evolu-relay/, met een manifest en een compose, geïnstalleerd op de Umbrel van de gebruiker, met Trezor Suite die erop synchroniseert.

2. Afbakening

Het pakket en de installatie: manifest, compose, het bouwrecept voor de image, en de controles op het apparaat zelf.

De store zelf valt hier buiten: die bestaat en serveert Electrum Gate al. Wat erbij komt is één map.

3. Niet-doelen

  • Geen bereikbaarheid van buiten het thuisnetwerk. Masterplan Bereikbaarheid. Dit plan is af als het op het eigen netwerk werkt.
  • Geen inlevering bij de officiële store. Masterplan Publicatie-Relay.
  • Geen wijzigingen aan de relay zelf. Wat Trezor levert, draaien we.
  • Geen eigen statuspagina. Zie OPEN.md punt 2.

4. Ontwerp

Alles hieronder rust op Upstream-evolu-relay.md, waar de feiten met bron-URL per stuk staan. De vorm van een Umbrel-app staat in Umbrel-appstore-spec.md.

Let op de tweedeling in deze paragraaf. §4a tot en met §4f beschrijven het pakket zoals het op 25-08-2026 gebouwd is, met de relay van Trezor, een Postgres en de quota-manager. Dat pakket gaat eruit: de proef van 28-08 (§6a) bewees dat de kale Evolu-relay werkt, en §4g en §4h beschrijven wat ervoor in de plaats komt. De oude paragrafen blijven staan tot de verbouwing gedaan is, want ze leggen uit waaróm er iets weggaat; daarna verdwijnen ze.

4a. Drie containers, twee images

De relay en de quota-manager komen uit dezelfde image met een ander command: bovenstrooms is het één codebase met meerdere startscripts. Daarnaast een Postgres. Dat is de hele stack.

Het command staat expliciet in de compose en leunt niet op de CMD van de Dockerfile: daar staat yarn start, met bovenstrooms zelf een commentaar dat het misschien een van de twee specifieke scripts had moeten zijn.

4b. De quota-manager gaat mee, en waarom dat geen keuze was

De relay weigert elke eigenaar die geen rij in de limietentabel heeft (isOwnerAllowed()), en de quota-manager maakt die rijen. Er is geen HTTP-koppeling tussen de twee: geen URL in de configuratie, ze delen alleen de database.

Dat laatste opent een tweede weg, die hier bewust niet gekozen is: de rij met de hand in de database zetten en de quota-manager weglaten. Minder bewegende delen, maar het is schrijven in andermans schema en dat breekt bij de eerste migratie. De container kost weinig; het risico van zelf in hun tabellen schrijven is groter dan de winst.

4c. De app-proxy zonder inlog, en dat is het patroon en geen omweg

Trezor Suite is geen browser met een sessiecookie. Achter app_proxy met de standaardinstelling krijgt het een inlogpagina in plaats van de relay.

De oplossing is PROXY_AUTH_ADD: "false", met port in het manifest als de poort waar Suite naartoe wijst. Dat is niet zelfbedacht: de eigen nostr-relay-app in de officiële store doet exact dit, om precies dezelfde reden, en Gitea en Budibase doen het ook. Daarmee is de zwaarste ontwerpvraag van dit plan beantwoord met een precedent in plaats van met een gok.

Het alternatief dat is afgevallen: een eigen ports: op de service, zoals Electrum Gate met 50022 doet. Dat kan, maar het vraagt een tweede poort die op de host vrij moet zijn, terwijl de proxy-poort er al is.

De prijs staat in de compose genoemd en hoort ook hier: wie die poort kan bereiken, bereikt de relay zonder aanmelding. Wat de schade beperkt is de weigering uit §4b. Dat is een reden om deze app niet zonder meer naar het internet open te zetten, en het is de kern van het risico in Publicatie-Relay.

4d. De poort is 3851 en niet 4000

port in het manifest is de poort waarop umbrelOS de app aanbiedt. 4000 zou aansluiten op de eigen poort van de relay, maar het is een veelgebruikte poort en een botsing op de host merk je pas als de app niet start. Dat heeft bij Electrum Gate een dag gekost, met 50002 tegen Fulcrum. 3851 sluit aan op de 3850 van die app.

4e. De database

Postgres onder ${APP_DATA_DIR}/data/postgres, niet in een naamloos Docker-volume: dat is wat umbrelOS bewaart en in de back-up meeneemt. Het wachtwoord komt uit ${APP_PASSWORD}, per installatie afgeleid, want deze repo is publiek.

PGDATA staat op een submap van de mount en niet op de wortel: Postgres weigert een datamap die al iets anders bevat, en een mount kan een lost+found hebben.

De image is postgres:17-alpine en niet postgres kaal zoals bovenstrooms: dat laatste is latest, en een grote-versiesprong migreert de datamap niet vanzelf. Dan start de database niet meer.

backupIgnore blijft leeg. Electrum Gate gebruikt dat veld wel, maar daar gaat het om een log en een statusbestand; hier ís de database de waarde van de app.

4f. De image bouwen we zelf, en het recept staat in de repo

Trezor publiceert geen image: hun werkproces duwt naar een eigen Amazon ECR en op Docker Hub staat niets. Er is dus een bouwstap, en die moet ergens.

Niet in de app-map. Dat is technisch mogelijk (build: in plaats van image:, en umbreld roept gewoon docker compose aan), maar drie dingen maken het verkeerd, en het derde is fataal: de broncode zou in onze repo moeten staan, de bouw duurt minuten tijdens het starten van de app, en een Dockerfile staat niet in de update-whitelist, dus elke nieuwe versie zou een deïnstallatie plus herinstallatie vragen.

De gekozen vorm: tools/evolu-relay/build.sh haalt de broncode op een vastgezette commit en bouwt hun Dockerfile. Het recept is versiebeheerd en na te lezen, de uitkomst staat in de Docker-opslag of later in een register, en de compose verwijst alleen naar de tag. We bouwen niet hun Dockerfile na; wat wij toevoegen is uitsluitend de pin.

Voorlopig blijft die tag lokaal, op verzoek van de gebruiker: dat is de kortste weg naar een installatie die je kunt proberen. Compose haalt niets op zolang de image lokaal bestaat, dus dezelfde regel werkt later ook als de tag naar een register wijst. Waar dat register komt te staan is OPEN.md punt 1.

4g. De limiter is niet een eigen relay maar twee functies

Beslist op 28-08-2026 door de gebruiker, na de geslaagde proef. De feiten met bron staan in Upstream-evolu-relay.md §8.

createRelay uit @evolu/nodejs neemt twee terugroepfuncties: isOwnerAllowed(ownerId) en isOwnerWithinQuota(ownerId, requiredBytes). Trezor doet precies dit en niets meer; hun hele quota-manager met Postgres bestaat alleen om de tabel te vullen die die twee functies raadplegen. Wij vullen ze met eigen logica en houden verder alles van bovenstrooms. Er wordt dus geen relay nagebouwd en niets van Evolu aangepast.

De logica die erin komt, zoals de gebruiker hem formuleerde:

  • de eerste eigenaar die zich meldt, wordt de eigenaar. Geen id intikken, want Suite toont je OwnerId nergens;
  • een schakelaar die bepaalt of er nog nieuwe eigenaars bij mogen. Dit is wat de leer-variant bruikbaar maakt in een huishouden: openzetten, tweede apparaat koppelen, weer dichtzetten;
  • data per eigenaar wissen, zodat een verkeerd geleerde eigenaar geen herinstallatie kost.

De prijs die we hiermee accepteren: de bouwstap en het eigen register komen terug, want dit vraagt een eigen image. Wat níet terugkomt is de Postgres, het wachtwoord en het onderhoud aan andermans schema. Het bouwrecept in tools/evolu-relay/ blijft dus bestaan, maar het bouwt straks niet meer de repo van Trezor; het bouwt een klein eigen programma tegen @evolu/nodejs.

Twee dingen om niet over te slaan. De gepubliceerde image laat isOwnerAllowed weg en zet isOwnerWithinQuota op 1 MB per schrijfactie, niet per eigenaar: de relay geeft door hoeveel bytes déze actie nodig heeft en telt niets op. Trezor telt wel op, maar uit hun eigen tabel, en die hebben wij niet. Wij nemen die 1 MB over als bewuste keuze, instelbaar met RELAY_MAX_WRITE_BYTES: één labelwijziging is klein, dus een schrijfactie die daarboven uitkomt is eerder een fout of misbruik dan normaal gebruik. En de allowlist is staat: die hoort onder ${APP_DATA_DIR}/data/, naast de relay-database en niet erin.

Wat er bewust níet in gaat: een geheim pad in de URL. De cliënt neemt een pad in de relay-URL letterlijk over, dus Zoraxy had alleen een onraadbaar pad kunnen doorlaten. Dat is een laag vóór de relay in plaats van erin, en het is met één proxyregel alsnog toe te voegen. De gebruiker ziet er geen reden voor nu de allowlist er komt, en dat klopt: het beschermt tegen hetzelfde, alleen eerder in de keten.

4h. De poorten omgedraaid: pagina achter de inlog, relay ernaast

Beslist op 28-08-2026, en het draait §4c om. Daar stond de relay achter de app-proxy met de inlog uit, en dat maakte een statuspagina onmogelijk zonder die pagina net zo bloot te leggen.

De nieuwe indeling volgt het patroon dat Electrum Gate in deze repo al gebruikt:

  • de pagina achter app_proxy, met de umbrelOS-inlog gewoon aan. Dat is de app-tegel op het dashboard;
  • de relay op een eigen ports:-regel, waar Zoraxy met TLS op een eigen subdomein naartoe wijst.

Suite ziet daarmee nooit een inlogpagina en de pagina is nooit onbeschermd. Dat lost open punt 2 op zonder de tweede poort die daar als bezwaar stond, want die tweede poort is er toch al.

Zoraxy moet de WebSocket-upgrade doorlaten, anders lijkt het beeld op dat van 28-08: een server die leeft en een cliënt die niets doet. De handshake-curl uit §6a stap 3 is de test.

De pagina volgt hetzelfde ontwerpsysteem als Electrum Gate, aangewezen door de gebruiker op 28-08-2026: HomeGit/Docs/website-design-system.html. Dat is geen kwestie van smaak maar van herkenning, want de twee apps staan straks naast elkaar op hetzelfde dashboard. Electrum Gate neemt die tokens letterlijk over (data-theme maal data-accent, dezelfde accentkleur) en dat is het vertrekpunt. Eén ding niet overnemen uit het bronbestand: de verwijzing naar Google Fonts. De pagina van Electrum Gate heeft die bewust niet, en een app op een Umbrel hoort niet stuk te gaan of te vertragen op een lettertype dat van buiten moet komen.

De pagina praat niet met Docker. Dezelfde regel als bij Electrum Gate: geen socket. De pagina en het relay-proces delen een map onder ${APP_DATA_DIR}/data/, en een knop op de pagina zet daar een vlagbestand neer dat het relay-proces oppakt. Dat geldt zowel voor de schakelaar als voor het wissen: het wissen raakt de SQLite van de relay, en dat hoort het proces te doen dat die database bezit, niet een tweede container die er langszij in schrijft.

5. Raakvlakken

  • Proefopstelling leverde alle feiten waarop §4 rust, en heeft nog twee open vragen die dit plan raken: of Trezor Suite een eigen relay accepteert, en hoe je een eigenaar registreert.
  • Bereikbaarheid begint waar dit plan ophoudt.
  • Publicatie-Relay erft §4c als grootste review-risico, en §4f als grootste praktische eis.
  • Appstore (Electrum Gate) leverde de les die §4d stuurt, en de ervaring dat een store-URL wisselen een herinstallatie is.

6. Verificatie

Automatisch, en dat draait al: tests/test_appstore_vorm.py controleert voor élke app in deze repo dat het id gelijk is aan de mapnaam, dat het store-voorvoegsel klopt, dat de manifestvelden in de voorgeschreven volgorde staan, dat de app_proxy naar een bestaande service wijst en dat elke gemounte map in de repo bestaat. Het pint niets af op digests; dat is bewust, zie de kop van dat bestand.

Op de Umbrel, en dat is waar dit plan om gaat:

  1. de image bouwen met tools/evolu-relay/build.sh;
  2. de app installeren en zien dat alle drie de containers blijven draaien. De database is hier de twijfel: of het schema zichzelf aanmaakt is niet uitgezocht;
  3. Trezor Suite naar <umbrel>:3851 laten wijzen en een label synchroniseren. Dit is de enige controle die telt, en hij kan mislukken op de registratie uit §4b zonder dat er iets mis is met het pakket;
  4. de app stoppen en starten, en controleren dat de database het overleeft;
  5. controleren dat het toevoegen van deze app Electrum Gate niet raakt.

Meld per controle of hij gedaan is, en meld ook expliciet welke niet.

6a. Testprotocol: praat Trezor Suite met de kale relay?

Geschreven 28-08-2026, als uitwerking van de eerste taak in TAKEN.md en van OPEN.md punt 6. Deze proef gaat niet over het pakket dat er nu staat: hij draait de kale docker.io/evoluhq/relay:latest los ernaast, met één commando, en raakt de app niet aan.

De vraag die hij beantwoordt: past de protocolversie van Suite (@evolu/web@3.0.0-next.1, met een eigen patch in .yarn/patches/) op de gepubliceerde image van Evolu? Dat is het enige dat nog niet uit de broncode af te leiden was.

Wat hij níet beantwoordt: of de relay veilig genoeg is om zo te draaien. Dat is de limiter uit OPEN.md punt 8, en die vraag komt pas ná een geslaagde uitslag.

Eigenaar: gebruiker. Dit is niet vanaf een laptop met alleen de repo te doen.

Voorbereiding

  1. Een host met Docker en poort 4000 vrij. De Umbrel is het handigst; daar moet het met sudo, want de gebruiker umbrel zit niet in de groep docker. Naast Suite op de PC mag ook.
  2. De app whatsnext-evolu-relay staat uit, en dat blijft zo. Er is dus geen botsing, ook niet op 3851.
  3. Het Trezor-apparaat bij de hand: het aanzetten van de synchronisatie vraagt een bevestiging op het apparaat zelf.
  4. Een tweede Suite-installatie is niet nodig, maar wel de enige manier om de proef helemaal rond te krijgen. Zie stap 7.

De stappen

1. De image halen en bekijken. Dit legt meteen twee dingen vast die verderop nodig zijn: waar de relay zijn data neerzet, en de digest waarop later te pinnen valt.

sudo docker pull docker.io/evoluhq/relay:latest
sudo docker image inspect docker.io/evoluhq/relay:latest --format '{{index .RepoDigests 0}} | cmd={{.Config.Cmd}} | ports={{.Config.ExposedPorts}} | volumes={{.Config.Volumes}}'

Noteer de digest en het datapad. Onze naslag zegt /app/data, maar dat komt uit de documentatie van Evolu en niet uit de image; wat hier uitkomt is de waarheid.

Gemeten op 28-08-2026: evoluhq/relay:latest staat op sha256:dbbe0ca13a78beffcfcbedadd4081c4452e81599801d375d93b0861b411535d2. Dat is de digest die bij een pull op tag gemeld wordt, dus de index-digest, en daarmee precies wat er bij een pin in de compose hoort. Nog niet gecontroleerd is of die index arm64 bevat; dat telt pas bij Publicatie-Relay.

2. Starten, op de voorgrond. Het log is de helft van de meting, dus laat dit venster staan.

sudo docker run --rm --name evolu-proef -p 4000:4000 docker.io/evoluhq/relay:latest

Een goede start meldt één regel: Evolu Relay started on port 4000 (gemeten 28-08-2026). Meer komt er bij het opstarten niet, dus dat is het hele startsignaal.

En healthy in docker ps betekent hier minder dan het lijkt. De healthcheck van de image opent met Node een TCP-verbinding naar 127.0.0.1:4000 en sluit hem meteen; er gaat geen enkel verzoek overheen. Groen zegt dus alleen dat de poort binnen de container accepteert, niet dat de relay iets zinnigs doet. Dat is dezelfde fout als de bovenstroomse Postgres-healthcheck uit §4e, en de les herhaalt zich: nemen we deze image in het pakket op, dan hoort er een healthcheck bij die wél iets vraagt. Vergat je --name, dan vind je de naam met sudo docker ps --filter ancestor=evoluhq/relay:latest --format '{{.ID}} {{.Names}}'.

3. Luistert hij, en spreekt hij WebSocket? In een tweede venster:

curl -si --max-time 5 -H 'Connection: Upgrade' -H 'Upgrade: websocket' -H 'Sec-WebSocket-Version: 13' -H 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==' http://127.0.0.1:4000/

Het antwoord hoort HTTP/1.1 101 Switching Protocols te zijn (gemeten 28-08-2026). Connection refused betekent dat er niets luistert, en dan is stap 1 het antwoord.

Doe dit niet met een gewone GET, en al helemaal niet met een browser. Dat is op 28-08-2026 een half uur zoeken geworden: de relay negeert een verzoek zonder upgrade-headers volledig, dus curl geeft niets terug en de browser meldt na een minuut dat de site niet bereikbaar is. Dat ziet er identiek uit aan een dode poort of een firewall, terwijl er niets aan de hand is. Alleen de handshake hierboven meet iets.

4. Een nulmeting van de opslag. Hierop rust de belangrijkste conclusie, dus doe hem vóór je Suite aansluit.

Niet met docker diff, en dat is een valstrik die hier op 28-08-2026 is ingelopen. De image declareert /app/data als VOLUME (gemeten: volumes=map[/app/data:{}]), dus Docker hangt daar een naamloos volume in. docker diff toont uitsluitend de schrijflaag van de container en kijkt niet in een volume: die uitvoer blijft leeg, ook na een geslaagde synchronisatie. Een lege uitkomst zou dan als "er landt niets" gelezen worden, en dat is precies de verkeerde conclusie.

Meet daarom op het volume zelf. Eerst het hostpad opzoeken:

sudo docker inspect evolu-proef --format '{{range .Mounts}}{{.Type}} {{.Name}} -> {{.Source}}{{end}}'

En dan, met dat pad, de meting die je later herhaalt:

sudo ls -la <hostpad>

Schrijf de uitkomst op; leeg of alleen een verse database is een prima nulmeting. sudo du -sb <hostpad> geeft er een getal bij dat makkelijker te vergelijken is.

De nulmeting van 28-08-2026, meteen na de start: één bestand, evolu-relay.db, 40960 bytes, eigenaar uid 1001. Twee dingen die dat vastlegt. De relay maakt zijn eigen SQLite-database aan bij de start, dus de schemavraag die bij het Trezor-pakket openstaat bestaat hier niet. En hij draait niet als root, wat precies de reden is dat een zelfgemaakte bind-mount hier niet gebruikt wordt.

Wij openen dat bestand niet. Ook niet om te kijken of het label erin staat; dat is data van de gebruiker. ls en du zeggen genoeg.

5. Suite ernaartoe wijzen. Instellingen, dev-utils, het veld voor de relay-URL: http://<host>:4000, met de eigen opslaan-knop van dat veld. http:// volstaat, dat blijkt uit Trezor's eigen e2e-test. De quota-manager-URL laat je met rust: bij een eigen relay-URL negeert Suite hem toch, en dat is precies de bevinding waar deze richting op rust. Zet daarna de synchronisatie aan en bevestig op het apparaat.

6. Eén label maken, of een account hernoemen. Kijk daarna naar de drie signalen, in deze volgorde:

  • het log in venster 1: komt er een verbinding binnen, en blijft die staan?
  • sudo ls -la <hostpad> uit stap 4: is er in het volume iets bijgekomen of gegroeid? Kijk vooral naar de tijdstempel van evolu-relay.db en niet alleen naar de omvang: SQLite schrijft in pagina's, en een enkel label past mogelijk in de ruimte die er al is. Verschijnen er -wal of -shm-bestanden naast, dan is dat op zichzelf al bewijs van schrijfverkeer;
  • Suite zelf: zegt hij iets over de sync-status? Dit is het zwakste signaal van de drie, zie de valkuil hieronder.

7. Alleen met een tweede cliënt: de rondgang. Wijs een tweede Suite naar dezelfde relay, met dezelfde Trezor, en kijk of het label vanzelf verschijnt. Dit is de enige controle die beide richtingen bewijst. Heb je die niet, dan is stap 6 het eindpunt en meld je dat de leeskant ongemeten bleef.

De uitslag lezen

Waarneming Wat het betekent
Geen verbinding in het log, curl uit stap 3 werkte wel Suite komt er niet bij. Adresvorm, firewall of het verkeerde IP. Nog geen uitspraak over het protocol, dus eerst dit oplossen
Verbinding komt op en valt meteen weg, herhaald in een cyclus Dit is het beeld van een geweigerde handshake, en dus de meest waarschijnlijke vorm van een protocolmismatch. Uitslag: mislukt, en het log is de bevinding: schrijf het letterlijk over
Verbinding blijft staan, maar het volume verandert niet na een label Verbonden, maar er landt niets. Ook mislukt, en interessanter dan de vorige: dan zit het niet in de handshake maar in de laag erboven. Controleer eerst dat je naar het volume kijkt en niet naar docker diff
Verbinding blijft staan én het volume groeit Geslaagd op de schrijfkant. Dit is genoeg om de richting uit punt 6 te bevestigen en aan de limiter te beginnen
En de tweede cliënt ziet het label Volledig geslaagd. Beide richtingen bewezen

De valkuil, en hij is groot

Dat je het label in Suite ziet staan bewijst niets. Evolu is local-first: de schrijfactie landt eerst in de lokale database en daarna pas bij de relay. Bij een mislukte verbinding ziet de interface er dus precies hetzelfde uit. Vandaar dat de conclusie in de tabel hierboven aan de relaykant hangt en niet aan wat Suite toont.

Opruimen

Ctrl-C in venster 1; door --rm verdwijnt de container en met hem de opslag. Dat is voor een proef prima, maar het betekent ook dat een herhaling opnieuw bij nul begint, en dat je een geslaagde meting niet per ongeluk op oude data doet.

Zet daarna de relay-URL in Suite terug, anders blijft hij naar een relay wijzen die er niet meer is.

En laat de proefcontainer niet staan. Hij is onbeschermd bereikbaar op het hele netwerk, en dat is nu juist het gat dat de limiter uit OPEN.md punt 8 moet dichten.

Wat je terugkoppelt

Het log uit venster 1 (letterlijk, ook als het er saai uitziet), de inhoud van het volume van vóór en na, de digest uit stap 1, en of stap 7 gedaan is. Met die vier is de vervolgsessie beslisbaar zonder de proef te herhalen.

Wat er op 28-08-2026 gemeten is, en wat nog steeds niet

De proef is geslaagd: de protocolversie klopt. Trezor Suite op de desktop stuurde elf labels naar de kale relay en die kwamen aan. De database in het volume groeide van 40960 naar 49152 bytes en de tijdstempel verzette van 07:55 naar 08:38. Geen Postgres, geen quota-manager, geen eigenaarsregistratie, en toch verkeer. Daarmee is docker.io/evoluhq/relay:latest bruikbaar als zelf-gehoste relay en is de richting uit OPEN.md punt 6 een gemeten feit in plaats van een verwachting.

Wat daarmee níet bewezen is: de leeskant. Er is geen tweede cliënt geweest, dus dat een ander apparaat die labels ook terugkrijgt is nog niet gezien. Stap 7 blijft open.

De weg ernaartoe kostte een halve dag en leverde het volgende op, zodat het niet opnieuw uitgezocht hoeft te worden.

De relay zelf is in orde. Hij haalt, start en luistert. De WebSocket-handshake slaagt zowel vanaf de Umbrel zelf als vanaf een Mac elders op het netwerk, met een correcte Sec-WebSocket-Accept. De poort is gepubliceerd op alle interfaces. Aan de serverkant is er dus niets te repareren.

Hij maakt zijn database zelf aan. Direct na de start staat er één bestand in het volume, evolu-relay.db van 40960 bytes, eigenaar uid 1001. Daarmee vervalt in deze richting de schemavraag die bij het Trezor-pakket nog openstaat, en het bevestigt dat de relay niet als root draait.

Drie meetinstrumenten bleken onbruikbaar, en alle drie op dezelfde manier: ze gaven een vals negatief. Dit is de duurste les van de dag, want elk ervan leek een probleem aan te wijzen dat er niet was.

  • docker diff kijkt niet in een volume. Omdat /app/data als VOLUME gedeclareerd is, blijft die uitvoer leeg, ook na een geslaagde synchronisatie. Meet op het hostpad uit docker inspect.
  • Een browser of een gewone curl krijgt niets terug. De relay negeert een verzoek zonder upgrade-headers volledig: geen antwoord, geen foutcode. Dat ziet er identiek uit aan een dode poort of een firewall. Alleen de handshake uit stap 3 meet iets.
  • healthy in docker ps zegt niets over werking. De healthcheck opent een TCP-verbinding en sluit hem meteen.

iOS is voor deze proef geen bruikbaar platform gebleken. Wat er werkt: het veld voor de relay-URL bestaat in de iOS-app onder dev-utils, het valideert (ws:// wordt geweigerd met "Enter a valid url", dus het wil http(s)://, precies zoals Trezor's eigen e2e-test), en de ingevulde waarde overleeft een geforceerde afsluiting. Wat er niet werkt: de app maakt geen enkele verbinding. Met een wachter die elke halve seconde keek is er nooit een poging op poort 4000 aangekomen, en in de iOS-instellingen verscheen geen schakelaar voor "Lokaal netwerk", wat erop wijst dat de app het nooit geprobeerd heeft.

Twee lezingen blijven mogelijk en zijn van buitenaf niet te scheiden: de app kan op dit platform geen eigen relay gebruiken, of het synchroniseren zelf kwam niet op gang omdat de bevestiging op het apparaat ontbrak. Dat is een eigen vraag geworden en niet langer deel van deze proef, want anders sluiten we twee onbekenden tegelijk uit. De proef verhuist naar de desktop-Suite op de Mac, die aantoonbaar bij de relay kan.

Op de Mac lukte het meteen, en daar hoort één waarschuwing bij die de proef bijna alsnog verkeerd deed aflopen: de relay logt geen enkele verbinding. Het venster blijft na de startregel stil, ook terwijl er labels binnenkomen. "Ik zie niets gebeuren" is hier dus geen waarneming maar een eigenschap van de relay. Alleen het volume vertelt of er iets geland is, en dat is precies waarom de nulmeting uit stap 4 niet overgeslagen mag worden.