Plan Eigenimage, fase 5, op keuze van de gebruiker: een image met de relay erbij en geen tweede recept. agent.py, nginx.conf en index.html verhuizen naar tools/evolu-relay/ naast src/; de Dockerfile blijft op node:24-slim en haalt nginx en python3 uit apt. Drie containers uit een image: de relay als node via de compose, de agent en nginx als root. Anders dan alleen verplaatst: user www-data in nginx.conf (Debian heeft geen gebruiker nginx), geen USER meer in de image, de versie in de kop via api/status met RELAY_APP_VERSION. VERSION 0.6.0, manifest 0.6.0, drie keer dezelfde tag in de compose, ongepind tot de eerste push. Tests mee verhuisd; de vormtest toetst de drie tags tegen VERSION. Niet gebouwd: er is hier geen Docker. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
253 lines
15 KiB
Markdown
253 lines
15 KiB
Markdown
# UmbrelApps - projectspecifieke afspraken
|
|
|
|
De algemene werkafspraken staan in `HomeGit/CLAUDE.md` en worden hier niet herhaald. Hieronder alleen wat
|
|
eigen is aan dit project.
|
|
|
|
**Deze repo is één community app store met twee apps erin.** umbrelOS leest per store één repo, en dat is
|
|
de reden dat ze samen in één repo zitten in plaats van elk in een eigen. Eerst de regels die voor allebei
|
|
gelden, dan per app wat alleen daar geldt.
|
|
|
|
| App | Map | Toestand |
|
|
|-|-|-|
|
|
| **Electrum Gate** | `whatsnext-electrum-gate/` | draait op de Umbrel, wordt gebruikt. Sinds 0.1.0 met een eigen image en dus een bouwstap: zie **Electrum Gate** hieronder |
|
|
| **Evolu Relay** | `whatsnext-evolu-relay/` | draait op de Umbrel sinds 28-08-2026 en wordt gebruikt. Let op de bouwstap: zie **Evolu Relay** hieronder |
|
|
|
|
**Zet geen CLAUDE.md of andere werkbestanden in een app-map.** umbreld kopieert bij installatie de héle
|
|
app-map naar `~/umbrel/app-data/<app-id>/` met `rsync --archive`, dus alles wat daar staat belandt op het
|
|
apparaat en in de back-up. Documentatie hoort in `Docs/`, tests in `tests/`, allebei in de repo-root.
|
|
|
|
## Umbrel-regels die je niet mag omzeilen
|
|
|
|
Deze gelden voor élke app hier, want het zijn eigenschappen van umbrelOS. Met bron in
|
|
[Docs/Referenties/Umbrel-appstore-spec.md](Docs/Referenties/Umbrel-appstore-spec.md).
|
|
|
|
**Geen accolade-variabelen in een `*.template` die er niet horen.** umbreld haalt bij elke start elk
|
|
`${...}` in zo'n bestand door `envsubst`, ook een variabele die niet bestaat, en die wordt dan leeg. Dat
|
|
sloopt Python-code, en in een nginx-config betekent het geen `$host`, geen `log_format` en geen
|
|
`access_log` met variabelen. Moet er tóch een dollarteken in een nginx-directive, dan hoort die directive
|
|
in een bestand dat het `command`-blok van de compose wegschrijft (daar is `$$` te ontsnappen), of in een
|
|
eigen image, waar geen envsubst aan te pas komt. Beide apps doen sinds september 2026 het tweede; er
|
|
staat geen `*.template` meer in deze repo, maar de regel blijft gelden voor wie er een bij zet.
|
|
|
|
**Wat bij een update meekomt is een whitelist**: `docker-compose.yml`, `*.template`, `exports.sh`, `torrc`,
|
|
`hooks` en `umbrel-app.yml`. Al het andere bereikt een bestaande installatie nooit, zonder foutmelding.
|
|
Zet logica die later nog moet kunnen wijzigen dus in de compose, in een `*.template` of in een eigen
|
|
image, en nooit in een los bestand of in een submap van de app-map.
|
|
|
|
**Een wijziging zonder verhoging van `version` in `umbrel-app.yml` wordt niet uitgerold.** Geen melding,
|
|
geen fout; umbrelOS ziet hetzelfde nummer en doet niets. Dat heeft hier een keer een dag gekost. De
|
|
versies staan per app los van elkaar.
|
|
|
|
**Alle gebruikersstaat onder `${APP_DATA_DIR}/data/...`, en nooit erbuiten schrijven.** Elke map die bij
|
|
de eerste start moet bestaan, heeft een `.gitkeep` in de repo.
|
|
|
|
**Geen wachtwoord of ander geheim in de repo.** umbrelOS levert `${APP_PASSWORD}` en `${APP_SEED}` aan,
|
|
per installatie afgeleid. Deze repo is publiek, dus een literal is een gepubliceerd wachtwoord. Dat wordt
|
|
pas echt scherp bij Evolu Relay, want daar hoort een Postgres bij.
|
|
|
|
**Mapnaam gelijk aan het `id` in het manifest, en allebei met het store-id `whatsnext` ervoor.**
|
|
|
|
## Electrum Gate
|
|
|
|
### De tests draaien
|
|
|
|
```
|
|
python tests/test_agent_certificates.py
|
|
```
|
|
|
|
```
|
|
python tests/test_server_start_zonder_certificaat.py
|
|
```
|
|
|
|
Deze twee gaan over Electrum Gate en noemen `tools/electrum-gate/` en die app-map bij naam. Er is een
|
|
derde die over de **store**
|
|
gaat en zijn apps zelf vindt, dus over beide:
|
|
|
|
```
|
|
python tests/test_appstore_vorm.py
|
|
```
|
|
|
|
Geen testrunner en geen afhankelijkheden: het zijn losse scripts die 0 teruggeven als alles goed is. Ze
|
|
horen samen met de tests van Evolu Relay hieronder bij "de suite" en draaien voor een commit; de uitvoer
|
|
eindigt bij elk met een regel "N goed, M fout". Er is geen watch-modus; de suite kost minder dan een
|
|
seconde. Noem hier geen aantal: dat klopte tot 30-08-2026 al niet meer.
|
|
|
|
Er is ook een test over de **pagina's** van beide apps, en die is geen Python:
|
|
|
|
```
|
|
node tests/test_paginas_parsen.mjs
|
|
```
|
|
|
|
Hij toetst dat de JavaScript in elke statuspagina parseert. Een pagina kan op twee plekken staan: als
|
|
`index.html.template` in de app-map (dan toetst hij ook dat het script heel blijft ná de invulling door
|
|
umbreld) of als `index.html` in `tools/<app>/` (dan toetst hij dat er geen accolade-variabele meer in
|
|
staat). Sinds september 2026 staan beide pagina's op de tweede plek. Dat is de enige klasse paginafouten
|
|
die niet op het apparaat gevonden hoeft te worden; alles wat de pagina *toont* blijft handwerk in een
|
|
browser.
|
|
|
|
**Wil je een pagina bekijken, gebruik `tools/voorbeeldpagina.mjs`.** Een statuspagina is niet zomaar te
|
|
openen: de gegevens komen van een agent die alleen in de app bestaat, en in een `*.template` staan
|
|
bovendien accolade-variabelen. Dat script vindt de pagina op een van de twee plekken, vult in wat er in te
|
|
vullen is en zet het resultaat in `voorbeeld/` (gitignored). Geen bewijs, wel het gereedschap dat op
|
|
30-08-2026 twee echte opmaakfouten vond voordat ze uitgerold waren.
|
|
|
|
`test_appstore_vorm.py` toetst **expres niet** dat images op een digest gepind zijn. Dat is wél de regel,
|
|
maar geen van de twee apps haalt hem volledig, en een suite die altijd rood staat wordt niet gelezen. Hij
|
|
drukt de pinstatus wel af. Wat hij wél toetst: dat de tag van een eigen image gelijk is aan `VERSION` in
|
|
het bijbehorende `tools/<app>/build.sh`.
|
|
|
|
Twee dingen om te weten voordat je een groene uitslag vertrouwt:
|
|
|
|
- **sommige toetsen slaan zichzelf over en melden dat.** De vergelijking met `ssl` heeft de
|
|
certificaatwinkel van het besturingssysteem nodig, en de subjectAltName-toets heeft netwerk nodig. Staat
|
|
er `OVERGESLAGEN` in de uitvoer, dan is dat deel níet bewezen. Lees de uitvoer dus, tel niet alleen de
|
|
exitcode;
|
|
- **de suite dekt de agent, niet de pagina.** Alles in `index.html` is handwerk in een browser.
|
|
|
|
### Architectuurregels
|
|
|
|
**De app-code zit in een eigen image, gebouwd uit `tools/electrum-gate/`** (sinds 0.1.0, 07-09-2026). De
|
|
agent, de nginx-configuratie, het stream-blok, de pagina en het startscript staan daar; in de app-map
|
|
staan alleen nog de compose, het manifest, het icoon en `data/`. Eén image voor beide containers: de
|
|
compose start hem als `server` met het standaardcommando en als `agent` met `python3 /app/agent.py`.
|
|
Gevolg dat je moet kennen voordat je iets belooft: **een wijziging in `tools/electrum-gate/` is pas
|
|
uitgerold als de image gebouwd, geduwd en met zijn digest in de compose gezet is**, en dat kan alleen de
|
|
gebruiker, op de Umbrel. Verhoog je `VERSION` in `build.sh`, dan verhoog je ook `version` in het manifest
|
|
en de tag in de compose (twee keer); `test_appstore_vorm.py` houdt tag en `VERSION` gelijk.
|
|
|
|
**Geen Docker-socket.** De agent kan nginx daarom niet zelf herladen; hij zet een vlagbestand neer en de
|
|
nginx-container herlaadt zichzelf. Als je denkt de socket nodig te hebben, is er bijna zeker een
|
|
vlagbestand-oplossing.
|
|
|
|
**De pagina start altijd, ook zonder certificaat.** Het TLS-blok staat daarom niet in `nginx.conf` maar
|
|
in `stream.conf`, en `entrypoint.sh` zet dat pas in `/var/lib/gate/tls/` als de agent een `cert.conf`
|
|
geschreven heeft; `nginx.conf` haalt die map met een jokerteken op. Zet het blok niet terug in
|
|
`nginx.conf`, hoe netjes dat ook staat: nginx weigert te starten als een `listen ssl` geen certificaat
|
|
heeft, en dan komt de pagina waarop je dat certificaat kiest ook niet omhoog. Dat was de fout in 0.0.3.
|
|
`tests/test_server_start_zonder_certificaat.py` houdt het dicht.
|
|
|
|
**Het backend-adres komt uit de omgeving, in beide containers.** nginx kan in een `proxy_pass` van het
|
|
stream-blok geen omgevingsvariabele lezen, dus `stream.conf` heeft twee plaatshouders zonder dollarteken
|
|
die `entrypoint.sh` bij het starten invult. De pagina haalt de versie en het adres uit `status.json`; tot
|
|
0.0.29 vulde umbreld die rechtstreeks in de pagina in.
|
|
|
|
**Bij twijfel over een certificaat weigert de app.** Meerdere kandidaten zonder keuze van de gebruiker
|
|
levert géén automatische keuze op, ook niet "de nieuwste". Een verkeerd certificaat geeft een verbinding
|
|
die het lijkt te doen en pas bij de wallet stukloopt op naamverificatie, en dat is veel lastiger te vinden
|
|
dan een app die weigert en zegt waarom.
|
|
|
|
### Feiten
|
|
|
|
- App-id en mapnaam `whatsnext-electrum-gate`; image `sc.kamenier-hamer.nl/sysop/electrum-gate`.
|
|
- De TLS-poort is **50022**, niet de conventionele 50002: die bezet Fulcrum op de host.
|
|
- De app leest de certificaatmap van Zoraxy alleen-lezen. Dat is het meest ongebruikelijke aan dit
|
|
pakket; zie het masterplan **Publicatie-Gate** §4.
|
|
|
|
## Evolu Relay
|
|
|
|
### De tests draaien
|
|
|
|
```
|
|
node tests/test_limiter.mjs
|
|
```
|
|
|
|
```
|
|
python tests/test_relay_agent.py
|
|
```
|
|
|
|
De eerste toetst het toegangsbeleid uit
|
|
`tools/evolu-relay/src/policy.js`: wie er op de relay mag schrijven, wat er met een onbekende eigenaar
|
|
gebeurt, en of een geblokkeerde eigenaar er niet alsnog in komt doordat de leerstand aanstaat. Node 24 of
|
|
hoger, geen afhankelijkheden, en het bestand is met opzet `.mjs`: in de repo-root is er geen `package.json`,
|
|
dus een `.js` zou als CommonJS gelezen worden en de import falen.
|
|
|
|
**Het beleid staat los van de relay en dat is de reden dat dit te testen is.** `policy.js` bevat alleen
|
|
pure functies: geen bestanden, geen netwerk, geen klok. Wat wél schijf raakt staat in `store.js`, en de
|
|
relay zelf wordt in `index.js` alleen aangeroepen. Houd die scheiding aan; anders is er van deze test niets
|
|
meer over. **Ook het tijdvenster heeft geen klok**: `isLearningOpen(state, now)` krijgt het tijdstip mee.
|
|
|
|
De tweede toetst de agent: de labels en de resterende tijd van het tijdvenster. Dat zijn de twee dingen die
|
|
de agent zélf uitrekent in plaats van doorgeeft, en daarmee de enige plek in dit bestand waar hij eigen
|
|
logica heeft. De HTTP-laag zit er niet in: die handlers zijn zonder socket niet aan te roepen.
|
|
|
|
### Architectuurregels
|
|
|
|
**De relay-code is niet van ons.** Dit gebruikt `@evolu/nodejs` uit npm; er worden geen wijzigingen aan die
|
|
software gedaan en er wordt niet bovenstrooms bijgedragen. Wat wij toevoegen zijn de twee terugroepfuncties
|
|
`isOwnerAllowed` en `isOwnerWithinQuota`, en dat is het bedoelde uitbreidpunt. **Bouw de relay niet na.**
|
|
|
|
**Het programma en het bouwrecept staan in `tools/evolu-relay/`, nooit in de app-map.** Een `Dockerfile`
|
|
staat niet in de update-whitelist, dus bouwen-in-de-app kost bij elke nieuwe versie een deïnstallatie plus
|
|
herinstallatie, en het zou de installatie minuten laten hangen.
|
|
|
|
**Verhoog je `VERSION` in `build.sh`, dan verhoog je ook `version` in het manifest.** Anders is er een
|
|
nieuwe image en een oude installatie, zonder dat iets dat meldt. Houd die twee gelijk.
|
|
|
|
**De poorten staan omgekeerd ten opzichte van wat je verwacht, en dat is de kern van de compose.** De
|
|
**pagina** hangt achter `app_proxy` mét de inlog van umbrelOS; de **relay** publiceert zijn eigen
|
|
host-poort 3852. Tot 0.0.2 stond het andersom en moest `PROXY_AUTH_ADD` op `"false"`, want een sync-cliënt
|
|
is geen browser met een sessiecookie en zou een inlogpagina krijgen in plaats van de relay. Daardoor was
|
|
een statuspagina op diezelfde poort net zo onbeschermd als de relay zelf. Zet het niet terug zonder het plan
|
|
**Umbrelapp**, `PLAN.md` §4h te lezen.
|
|
|
|
**Eén schrijver per bestand onder `data/relay/`, en dat is geen stijlkwestie.** Twee processen die in
|
|
dezelfde allowlist schrijven is een wedloop die je een keer per jaar treft en dan niet kunt reproduceren.
|
|
De verdeling:
|
|
|
|
| Bestand | Schrijver | Waarom daar |
|
|
|-|-|-|
|
|
| `owners.json` | het relay-proces | daar staat wie er binnen mag, en dat beslist het proces dat de verbindingen aanneemt |
|
|
| `command.json` | de agent | de postbus. De agent legt erin, het relay-proces past toe en ruimt op |
|
|
| `labels.json` | de agent | een label zegt niets over toegang, dus de relay hoeft het niet te weten. Dat het hier staat en niet in `owners.json` is wat labelen meteen laat werken, ook als de relay omgevallen is |
|
|
|
|
**Het tijdvenster voor nieuwe eigenaars loopt in het relay-proces, nooit in de pagina.** Een teller in een
|
|
tabblad dat je sluit, sluit de deur niet. En `decideOwner` kijkt naar `isLearningOpen(state, now)` en niet
|
|
naar het veld `learning`: de lus die een verlopen venster opruimt loopt elke twee seconden, en in dat gat
|
|
zou een onbekende alsnog binnenkomen.
|
|
|
|
**Het beleid is de enige plek waar staat wie er binnen mag, en `isOwnerAllowed` wordt alleen bij de
|
|
WebSocket-upgrade gesteld.** Gevolg om te kennen voordat je iets belooft op de pagina: **blokkeren werkt pas
|
|
bij de volgende verbinding.** Zie `Docs/Referenties/Upstream-evolu-relay.md` §10 en het plan **Umbrelapp**,
|
|
`OPEN.md` punt 10.
|
|
|
|
**Zet geen ontcijfering in deze app.** Het kan technisch, maar niet met een xpub: er is de SLIP-21-node van
|
|
het apparaat voor nodig, en die geeft volledige zeggenschap over de gegevens. De hele grond waarop je een
|
|
relay ergens kunt neerzetten is dat hij de sleutel niet heeft, en het manifest belooft dat letterlijk.
|
|
Onderhoudsfuncties die de inhoud kennen horen in de tool in `HomeGit/Trezor`. Zie §11 van hetzelfde
|
|
naslagdocument en `OPEN.md` punt 11.
|
|
|
|
**Alles zit in één image, sinds 0.6.0 (08-09-2026): de relay, de agent en de pagina.** `tools/evolu-relay/`
|
|
heeft naast `src/` ook `agent.py`, `nginx.conf` en `index.html`; in de app-map staan alleen nog compose,
|
|
manifest, icoon en `data/`. Basis is `node:24-slim` (om `better-sqlite3`, dat geen musl-binaries heeft)
|
|
met nginx en python3 uit apt. Twee gevolgen die je moet kennen: nginx draait als `www-data`, want Debian
|
|
heeft geen gebruiker `nginx`; en de image heeft geen `USER`, dus de compose zet `user: node` op de
|
|
relay-service en de andere twee draaien als root. De versie in de kop van de pagina komt uit `api/status`
|
|
via `RELAY_APP_VERSION`. Verhoog je `VERSION` in `build.sh`, dan ook het manifest en de tag in de compose,
|
|
**drie keer**.
|
|
|
|
**Deze app draait en wordt gebruikt sinds 28-08-2026**, maar dat geldt niet voor elke wijziging: er zit een
|
|
bouwstap tussen de repo en het apparaat. Een wijziging in `tools/evolu-relay/` is pas uitgerold als de
|
|
image gebouwd, geduwd en met zijn digest in de compose gezet is, en dat kan alleen de gebruiker. Meld dus
|
|
per wijziging wat er wél geverifieerd is; "de app werkt" is geen uitspraak over de code van vandaag.
|
|
|
|
## Repo-feiten
|
|
|
|
- Remote: `https://sc.kamenier-hamer.nl/sysop/UmbrelApps.git`, **SHA-1** en **publiek**. Alle drie zijn
|
|
eisen en geen voorkeuren: umbreld kloont met isomorphic-git, en die spreekt alleen smart-HTTP, kan alleen
|
|
SHA-1, en krijgt geen inloggegevens mee.
|
|
- **Store-id `whatsnext`** in `umbrel-app-store.yml`. Elk app-id moet daarmee beginnen en gelijk zijn aan
|
|
de mapnaam.
|
|
- **Aangemaakt op 25-08-2026 zonder historie**, toen Evolu Relay erbij kwam. De voorganger was de repo
|
|
`ElectrumTLS`; die bevatte één app en zijn naam werd onhoudbaar bij een tweede. De historie is bewust
|
|
niet meegenomen. Zolang die oude repo op de Git-server staat, staat het domein van de gebruiker daar nog
|
|
in; zie het plan **Appstore**, `OPEN.md` punt 3.
|
|
- **De URL is de identiteit van de store.** Verandert hij, dan is het voor umbrelOS een andere store en
|
|
moet de gebruiker hem opnieuw toevoegen.
|
|
|
|
## Taal
|
|
|
|
Codecommentaar en documentatie zijn Nederlands. **Zichtbare tekst is Engels**: de pagina, `umbrel-app.yml`,
|
|
de meldingen die de agent naar de pagina stuurt, en de root-`README.md`, want dat is wat een bezoeker van
|
|
een publieke app store als eerste ziet. Die grens loopt precies langs "ziet de gebruiker dit": een reden
|
|
uit `choose()` belandt op het dashboard en is dus Engels, een commentaarregel erboven niet.
|