diff --git a/CLAUDE.md b/CLAUDE.md index 712d7a0..951c7fe 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ gelden, dan per app wat alleen daar geldt. | App | Map | Toestand | |-|-|-| | **Electrum Gate** | `whatsnext-electrum-gate/` | draait op de Umbrel, wordt gebruikt | -| **Evolu Relay** | nog niet | bestaat uit plannen; zie het plan **Proefopstelling** | +| **Evolu Relay** | `whatsnext-evolu-relay/` | gepakketteerd, nog nooit geïnstalleerd; zie het plan **Umbrelapp** | **Zet geen CLAUDE.md of andere werkbestanden in een app-map.** umbreld kopieert bij installatie de héle app-map naar `~/umbrel/app-data//` met `rsync --archive`, dus alles wat daar staat belandt op het @@ -57,9 +57,20 @@ python tests/test_agent_certificates.py python tests/test_server_start_zonder_certificaat.py ``` -Geen testrunner en geen afhankelijkheden: het zijn losse scripts die 0 teruggeven als alles goed is. Beide -horen bij "de suite", dus beide draaien voor een commit die deze app raakt. De uitvoer eindigt met een -regel "N goed, M fout". Er is geen watch-modus; de suite kost minder dan een seconde. +Deze twee gaan over Electrum Gate en noemen 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. Alle +drie horen bij "de suite" en draaien voor een commit; de uitvoer eindigt met een regel "N goed, M fout". +Er is geen watch-modus; de suite kost minder dan een seconde. + +`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 vandaag, en een suite die altijd rood staat wordt niet gelezen. Hij +drukt de pinstatus wel af. Twee dingen om te weten voordat je een groene uitslag vertrouwt: @@ -96,16 +107,29 @@ dan een app die weigert en zegt waarom. ## Evolu Relay -**Er is nog geen manifest, geen compose en geen code, en die horen er ook nog niet te komen.** Het eerste -dat er moet komen is een antwoord, niet een bestand: zie [Docs/CONTINUE_HERE.md](Docs/CONTINUE_HERE.md). -Speculatieve pakketbestanden zouden geschreven worden op aannames die het plan **Proefopstelling** juist -moet toetsen. - **De relay-code is niet van ons.** Dit pakketteert `trezor/trezor-suite-sync`; er worden geen wijzigingen aan die software gedaan en er wordt niet bovenstrooms bijgedragen. -**Er zijn geen tests, want er is geen bron.** Werkte een sessie uitsluitend aan deze app en aan -documentatie, dan vervalt stap 4 van het sessie-protocol. +**Het bouwrecept staat in `tools/evolu-relay/`, nooit in de app-map.** Trezor publiceert geen image, dus er +is een bouwstap. Die hoort niet in `whatsnext-evolu-relay/`: een `Dockerfile` staat niet in de +update-whitelist, dus bouwen-in-de-app kost bij elke nieuwe versie een deïnstallatie plus herinstallatie. +Wat wij toevoegen aan hun Dockerfile is uitsluitend de **pin** op een commit; bouw hun stappen niet na. + +**Verhoog je de pin 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. + +**De app-proxy staat op `PROXY_AUTH_ADD: "false"` en dat is met opzet.** Trezor Suite is geen browser met +een sessiecookie. Zet het niet "voor de veiligheid" terug: dan krijgt Suite een inlogpagina in plaats van +de relay en werkt de app niet meer. De keerzijde hoort erbij en staat in de compose: wie de poort bereikt, +bereikt de relay. Voeg dus geen pagina toe achter diezelfde poort zonder daar apart over na te denken; zie +het plan **Umbrelapp**, `OPEN.md` punt 2. + +**De quota-manager hoort erbij en is geen restje.** De relay weigert elke eigenaar zonder rij in de +limietentabel, en dit is wat die rijen maakt. Haal hem er niet uit omdat hij bij Trezor bij betaalde +hosting hoort. + +**"Gebouwd" is hier nog verder van "werkend" dan bij Electrum Gate:** er is nog nooit iets van deze app op +een Umbrel gedraaid. Meld dat expliciet in plaats van het te laten meelezen als werkend. ## Repo-feiten diff --git a/Docs/CHANGELOG-evolu-relay.md b/Docs/CHANGELOG-evolu-relay.md new file mode 100644 index 0000000..8261889 --- /dev/null +++ b/Docs/CHANGELOG-evolu-relay.md @@ -0,0 +1,20 @@ +# Versiegeschiedenis - Evolu Relay + +Nieuwste bovenaan. Elke regel hier hoort bij een `version` in +`whatsnext-evolu-relay/umbrel-app.yml`; zonder verhoging van dat nummer rolt umbrelOS een wijziging niet +uit. + +## 0.0.1 - 25-08-2026 + +Eerste versie, nog niet geinstalleerd. + +Drie containers: de relay en de quota-manager uit dezelfde zelfgebouwde image met een ander `command`, en +een Postgres eronder. De quota-manager gaat mee omdat de relay elke eigenaar zonder limietenrij weigert. + +De app-proxy staat op `PROXY_AUTH_ADD: "false"`, want Trezor Suite is geen browser met een sessiecookie. +Dat is het patroon van de eigen nostr-relay-app van Umbrel. Poort 3851. + +De image komt uit `tools/evolu-relay/build.sh`, gepind op commit `c03a204` van +`trezor/trezor-suite-sync`. Trezor publiceert zelf geen image. + +Nog niets van dit alles is op een Umbrel gedraaid. diff --git a/Docs/CONTINUE_HERE.md b/Docs/CONTINUE_HERE.md index e15ccca..ba7ade4 100644 --- a/Docs/CONTINUE_HERE.md +++ b/Docs/CONTINUE_HERE.md @@ -17,14 +17,15 @@ | App | Map | Wat het doet | |-|-|-| | **Electrum Gate** | `whatsnext-electrum-gate/` | TLS-voordeur op je eigen Electrum-server, zodat een wallet van buiten erbij kan zonder Tor. Draait, wordt gebruikt | -| **Evolu Relay** | nog niet | De sync-server achter de labels van Trezor Suite, op je eigen Umbrel. Bestaat nog uit plannen | +| **Evolu Relay** | `whatsnext-evolu-relay/` | De sync-server achter de labels van Trezor Suite, op je eigen Umbrel. Gepakketteerd sinds 25-08-2026, nog nooit geïnstalleerd | ## A - Nu (aanbevolen focus) | Plan | App | Volgende stap | Status | |-|-|-|-| | [Webinterface](Plannen/Actief/005-Webinterface/TAKEN.md) | Gate | 0.0.10 op een telefoon nakijken, en het uploaden op het niet-gelukkige pad proberen met een sleutel die niet bij het certificaat hoort. Daarna open punt 5: laten controleren of de TLS-poort zélf antwoordt | 🔶 | -| [Proefopstelling](Plannen/Actief/007-Proefopstelling/TAKEN.md) | Relay | Controleren of Trezor Suite een eigen sync-server accepteert, en op welk platform. Dat is de goedkoopste weerlegging van het hele project en het kost een minuut in de interface. Fase 1 is verder af: de quota-manager blijkt in de praktijk verplicht en er is geen publieke image, dus die moet zelf gebouwd worden | 🔶 | +| [Umbrelapp](Plannen/Actief/008-Umbrelapp/TAKEN.md) | Relay | De image bouwen op de Umbrel met `tools/evolu-relay/build.sh`. Dat is de enige stap tussen wat er in de repo staat en een installatie die kan slagen; daarna installeren en kijken of de drie containers blijven draaien | 🔶 | +| [Proefopstelling](Plannen/Actief/007-Proefopstelling/TAKEN.md) | Relay | Controleren of Trezor Suite een eigen sync-server accepteert, en op welk platform. Dat is de goedkoopste weerlegging van het hele project en het kost een minuut in de interface. Daarna: hoe registreer je een eigenaar bij de quota-manager, want zonder dat weigert de relay iedereen | 🔶 | ## B - Los oppakbaar (geen blokkade, geen vaste volgorde) @@ -60,8 +61,7 @@ daaronder, dus deze tabel en de mapinhoud kunnen niet uit elkaar lopen. | Plan | App | Afhankelijk van | Waarover het gaat | |-|-|-|-| -| [Umbrelapp.PLAN.md](Plannen/Masterplannen/Umbrelapp.PLAN.md) | Relay | Proefopstelling: het aantal containers, de variabelen en de authenticatievraag | Een tweede app-map in deze store, die je in umbrelOS installeert. De ontwerpvraag die alles bepaalt is of de app achter de inlog van umbrelOS kan staan: Trezor Suite is geen browser met een sessiecookie | -| [Bereikbaarheid.PLAN.md](Plannen/Masterplannen/Bereikbaarheid.PLAN.md) | Relay | Umbrelapp: er valt niets bereikbaar te maken zolang er niets draait | Synchroniseren buiten het thuisnetwerk, zonder een poort op de router open te zetten. Voorstel is Tailscale; of dat volstaat hangt ervan af of Suite TLS eist | +| [Bereikbaarheid.PLAN.md](Plannen/Masterplannen/Bereikbaarheid.PLAN.md) | Relay | Umbrelapp: er valt niets bereikbaar te maken zolang er niets draait | Synchroniseren buiten het thuisnetwerk, zonder een poort op de router open te zetten. Voorstel is Tailscale; of dat volstaat hangt ervan af of Suite TLS eist. De gebruiker maakt een subdomein aan, wat op de reverse-proxy-route wijst | | [Publicatie-Relay.PLAN.md](Plannen/Masterplannen/Publicatie-Relay.PLAN.md) | Relay | Umbrelapp, plus een image die te pinnen valt | Inleveren bij de officiële appstore. Twee dingen kunnen dit blokkeren: een image die niet als multi-arch digest in een registry bestaat, en de eis dat de umbrelOS-inlog aan blijft terwijl de relay een cliënt zonder sessie moet bedienen | | [Publicatie-Gate.PLAN.md](Plannen/Masterplannen/Publicatie-Gate.PLAN.md) | Gate | Appstore: de herstart-controle | **Doel van de gebruiker sinds 20-08-2026:** de app inleveren als standaard-app voor Umbrel. Dat verandert de maatstaf van "hij werkt hier" naar "iemand anders keurt het pakket goed". Het meeste is al goed; wat er nog moet is de images pinnen, het app-id kaal maken en de manifestvelden op orde. Het risico zit niet in die lijst maar in de leesmount op de certificaten van Zoraxy | | [Configuratie.PLAN.md](Plannen/Masterplannen/Configuratie.PLAN.md) | Gate | – | **Grotendeels ingehaald op 19-08-2026** en moet opgeschoond worden voordat promotie nog zin heeft: de agent doet de certificaatbronnen en de keuze al, en het hardgecodeerde domein is uit de compose en uit `nginx.conf.template` verdwenen. Wat er nog in zit is een configuratiebestand voor de poort- en padoverstemmingen, plus de README | @@ -78,5 +78,5 @@ Cross-plan kennis staat in [KNOWLEDGE.md](KNOWLEDGE.md). Naslag staat niet in de | [Vergelijkbare-apps.md](Referenties/Vergelijkbare-apps.md) | Gate | Of dit type app al bestaat voor Umbrel, en waarom niet. De kern voor de PR-tekst bij inlevering: de reverse proxies in de store kunnen een certificaat niet op een TCP-poort zetten | | [Upstream-evolu-relay.md](Referenties/Upstream-evolu-relay.md) | Relay | Wat er over `trezor/trezor-suite-sync` bekend is, met per feit hoe hard het is, en de vier dingen die nog helemaal niet uitgezocht zijn | -Versiegeschiedenis staat per app: [CHANGELOG-electrum-gate.md](CHANGELOG-electrum-gate.md). Evolu Relay -heeft er nog geen, want er is nog geen manifest met een `version`. +Versiegeschiedenis staat per app: [CHANGELOG-electrum-gate.md](CHANGELOG-electrum-gate.md) en +[CHANGELOG-evolu-relay.md](CHANGELOG-evolu-relay.md). diff --git a/Docs/Plannen/Actief/008-Umbrelapp/OPEN.md b/Docs/Plannen/Actief/008-Umbrelapp/OPEN.md new file mode 100644 index 0000000..05f4be6 --- /dev/null +++ b/Docs/Plannen/Actief/008-Umbrelapp/OPEN.md @@ -0,0 +1,58 @@ +# Open punten - Umbrelapp + +> Beslissingen die nog een **eigenaar** of een **moment** nodig hebben. Staat een punt hier zonder allebei, +> dan is dat de eerste fout om op te lossen. Wordt een punt een taak, dan verhuist het naar +> [TAKEN.md](TAKEN.md). +> +> **Nummers blijven staan**, ook als een punt beslist is: er kan elders naar verwezen worden, ook vanuit +> codecommentaar. Beslissen betekent verplaatsen naar de kop hieronder, niet hernummeren. + +## Nog te beslissen + +1. **Waar komt de image te staan zodra lokaal niet meer volstaat?** + Op 25-08-2026 is gekozen voor lokaal bouwen als eerste stap, en dat is een bewust tijdelijke keuze: de + app werkt dan alleen op die ene machine en herbouwen is handwerk. Drie richtingen, en ze zijn allemaal + verdedigbaar: + + - **het ingebouwde containerregister van de eigen Gitea.** Zelfde host als de store, geldig + TLS-certificaat, geen nieuw account. Nog na te kijken: staat het pakketregister aan, en mag er anoniem + uit gehaald worden. Zo ja, dan is dit de netste plek; + - **Docker Hub onder een eigen account.** De standaardweg, en verplicht als het ooit de officiële store + in gaat. Zet wel je naam op een publieke image van software van Trezor; lees dan eerst `LICENSE.md`, + dat door GitHub als "other" geclassificeerd staat; + - **lokaal laten.** Verdedigbaar zolang er één Umbrel is. De prijs is dat een herinstallatie ook een + herbouw is, en dat je dat over een half jaar niet meer weet. + + **Moment:** zodra de installatie uit fase 4 geslaagd is, en niet eerder: pas dan weet je of het pakket + iets waard is · **Eigenaar:** gebruiker + +2. **Komt er een statuspagina?** + Electrum Gate heeft er een en die bleek in de praktijk het nuttigste deel van die app. Hier zou dat + kunnen: draait de relay, hoe groot is de database, wanneer was de laatste synchronisatie, en is er een + eigenaar geregistreerd. Dat laatste is meer dan gemak, want het is precies waar het stil kan misgaan. + + Ertegen: het is een eigen container en een eigen onderhoudslast, en het is geen voorwaarde om te kunnen + synchroniseren. Ervoor, en dat is nieuw sinds de samenvoeging van de repo's: de pagina en de agent van + Electrum Gate staan in dezelfde repo en zijn grotendeels over te nemen. + + Let op één ding als het ervan komt: de app-proxy staat op `PROXY_AUTH_ADD: "false"`, dus een pagina zou + net zo onbeschermd zijn als de relay. Bij Electrum Gate zit de pagina juist wél achter de inlog. Dat + vraagt dan een tweede poort of een smalle whitelist. + **Moment:** pas overwegen als fase 4 geslaagd is · **Eigenaar:** gebruiker + +3. **Wat doen we met de quota-manager als blijkt dat hij iets extern nodig heeft?** + Hij is meegepakketteerd omdat de relay zonder limietenrij niemand toelaat. Als het registreren van een + eigenaar een betaalprovider of een Notion-sleutel vraagt, klopt die keuze niet meer en wordt de tweede + weg uit [PLAN.md](PLAN.md) §4b weer actueel: de rij met de hand in de database zetten. + **Moment:** zodra **Proefopstelling** de registratievraag beantwoordt · **Eigenaar:** volgt uit dat plan + +## Bewust uitgesteld + +4. **`SERVER_ENV` op `prod` of `dev`?** - `prod`, tot het tegendeel gemeten is (25-08-2026). + `.env.sample` zegt dat prod authenticatie aanzet; in de code die gelezen is bepaalt die vlag alleen het + logniveau en staan de autorisatiecontroles onvoorwaardelijk aan. Eén van de twee is achterhaald. + + `prod` is de veilige kant van die onduidelijkheid: als het iets aanzet, willen we dat het aanstaat. Maar + het is een gok tot het gemeten is, en het is met één keer starten te meten. Staat als taak in + **Proefopstelling** fase 2. + **Moment:** bij de eerste geslaagde start · **Eigenaar:** volgt uit Proefopstelling diff --git a/Docs/Plannen/Actief/008-Umbrelapp/PLAN.md b/Docs/Plannen/Actief/008-Umbrelapp/PLAN.md new file mode 100644 index 0000000..47face6 --- /dev/null +++ b/Docs/Plannen/Actief/008-Umbrelapp/PLAN.md @@ -0,0 +1,140 @@ +# 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](TAKEN.md), geschiedenis in [PROGRESS.md](PROGRESS.md), +> onbesliste punten in [OPEN.md](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](OPEN.md) punt 2. + +## 4. Ontwerp + +Alles hieronder rust op [Upstream-evolu-relay.md](../../../Referenties/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](../../../Referenties/Umbrel-appstore-spec.md). + +### 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](OPEN.md) punt 1. + +## 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 `: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. diff --git a/Docs/Plannen/Actief/008-Umbrelapp/PROGRESS.md b/Docs/Plannen/Actief/008-Umbrelapp/PROGRESS.md new file mode 100644 index 0000000..4f13c97 --- /dev/null +++ b/Docs/Plannen/Actief/008-Umbrelapp/PROGRESS.md @@ -0,0 +1,40 @@ +# Voortgang - Umbrelapp + +> Chronologisch sessielog, nieuwste bovenaan. Kort: 3 tot 6 regels per entry. Wat er is gebeurd en waarom, +> niet wat er nog moet: dat staat in [TAKEN.md](TAKEN.md). + +## 25-08-2026 - plan werd actief, en het pakket staat er + +De gebruiker besloot het pakket meteen te maken in plaats van te wachten op de proefopstelling. Dat kon, +omdat fase 1 van **Proefopstelling** die middag de feiten had opgeleverd: één image met meerdere +startscripts, een quota-manager die in de praktijk verplicht is, en geen publieke image. + +**De zwaarste ontwerpvraag is met een precedent beslecht en niet met een gok.** Trezor Suite kan niet achter +de inlog van umbrelOS, en het was onduidelijk of `PROXY_AUTH_ADD: "false"` dan een verantwoorde keuze is of +een omweg. De eigen `nostr-relay`-app van Umbrel doet exact hetzelfde, om precies dezelfde reden, en heeft +ook geen eigen `ports:`. Daarmee is de vorm van de compose niet zelfbedacht. + +**Twee dingen kwamen boven die niet in het ontwerp zaten.** `build/` staat in `.gitignore` als bouwselmap, +dus het bouwrecept zou stilzwijgend buiten de repo zijn gebleven; het staat nu onder `tools/`. Dat kwam pas +bij het stagen aan het licht en niet bij het schrijven, wat precies is waarom `git status` lezen erbij hoort. +En de poort werd 3851 en niet 4000: 4000 is de eigen poort van de relay, maar ook een veelgebruikte poort, +en een botsing op de host merk je pas als de app niet start. Die les komt van 50002 tegen Fulcrum. + +**Er is een derde testbestand**, `tests/test_appstore_vorm.py`, en het gaat over de store en niet over één +app: id gelijk aan mapnaam, store-voorvoegsel, veldvolgorde, `app_proxy` die naar een bestaande service +wijst, en elke gemounte map die in de repo bestaat. Het vindt zijn apps zelf, dus een derde app valt er +automatisch onder. Digests toetst het expres niet: geen van de twee apps haalt die regel vandaag, en een +suite die altijd rood staat wordt niet gelezen. + +Mutatie-getest met drie ingrepen: het app-id laten afwijken van de mapnaam, `APP_HOST` naar een +niet-bestaande service laten wijzen, en de `.gitkeep` weghalen. Alle drie vielen om bij de juiste toets, met +een bruikbare melding, en `git diff` was daarna leeg. + +**Geraakt:** `whatsnext-evolu-relay/` (nieuw), `tools/evolu-relay/build.sh` (nieuw), +`tests/test_appstore_vorm.py` (nieuw), `Docs/CHANGELOG-evolu-relay.md` (nieuw), dit plan, +`Docs/CONTINUE_HERE.md`, `CLAUDE.md`, `README.md`. +**Tests:** 32 goed 0 fout (nieuw), 39 goed 0 fout en 54 goed 0 fout (bestaand), niets overgeslagen. + +**Wat er níet geverifieerd is, en dat is veel:** er is nog niets gebouwd en niets geïnstalleerd. Of het +databaseschema zichzelf aanmaakt, of Trezor Suite dit adres accepteert, en of een eigenaar te registreren is +zonder iets extern: alle drie onbekend. "Gebouwd" is hier nadrukkelijk niet "werkend". diff --git a/Docs/Plannen/Actief/008-Umbrelapp/TAKEN.md b/Docs/Plannen/Actief/008-Umbrelapp/TAKEN.md new file mode 100644 index 0000000..672f0e7 --- /dev/null +++ b/Docs/Plannen/Actief/008-Umbrelapp/TAKEN.md @@ -0,0 +1,77 @@ +# Taken - Umbrelapp + +> **App: Evolu Relay.** Prioriteit: **A** | Afhankelijk van: – +> +> Actief sinds 25-08-2026. Gepromoveerd uit `Masterplannen/` omdat de gebruiker besloot het pakket meteen te +> maken. De afhankelijkheid op **Proefopstelling** is daarmee geen blokkade meer maar een parallelle bron: +> fase 1 daarvan is af en leverde de feiten waarop dit pakket rust. Wat er nog uit moet komen zijn twee +> vragen die pas bij het installeren pijn doen, en die staan hieronder als eigen taak. + +## Volgende stap + +- [ ] **De image bouwen op de Umbrel met `tools/evolu-relay/build.sh`.** Dat is de enige stap tussen wat er + nu in de repo staat en een installatie die kan slagen. Het script haalt de broncode op commit + `c03a204` en bouwt de Dockerfile van Trezor; verwacht minuten, want er zit een `yarn install` en een + `yarn build` in. **Eigenaar: gebruiker** + +## Fase 1 - De app-map + +- [x] **`whatsnext-evolu-relay/` aangemaakt met `umbrel-app.yml` en `docker-compose.yml` (25-08-2026).** + Mapnaam gelijk aan het `id`, met het store-voorvoegsel `whatsnext-`, en de manifestvelden in de + voorgeschreven volgorde +- [x] `data/postgres/.gitkeep` erin, zodat de mount bij de eerste start niet als root wordt aangemaakt +- [x] **`dependencies` weggelaten en niet leeg gezet.** Deze app hangt van geen enkele andere app af; een + leeg veld zou suggereren dat er iets te kiezen valt +- [ ] **Een eigen icoon.** Er staat nu géén `icon` in het manifest, dus de tegel blijft naamloos in de + store. Bewust leeg: een geleend of verkeerd icoon is erger dan geen. **Eigenaar: gebruiker** + +## Fase 2 - De compose + +- [x] **`app_proxy` met `PROXY_AUTH_ADD: "false"`, en geen eigen `ports:`.** Trezor Suite is geen browser + met een sessiecookie. Dit is het patroon van de eigen `nostr-relay`-app van Umbrel, dus een precedent + en geen omweg; zie [PLAN.md](PLAN.md) §4c +- [x] **Relay en quota-manager uit één image met een ander `command`.** Expliciet en niet leunend op de + `CMD` van de Dockerfile, want daar staat `yarn start` met bovenstrooms zelf een twijfel erbij +- [x] **De quota-manager gaat mee.** Niet omdat de relay hem aanroept, maar omdat de relay elke eigenaar + zonder limietenrij weigert en dit is wat die rijen maakt +- [x] **Postgres onder `${APP_DATA_DIR}/data/postgres`**, wachtwoord uit `${APP_PASSWORD}`, `PGDATA` op een + submap, en `postgres:17-alpine` in plaats van `postgres` kaal +- [x] **Healthcheck op de eigen database en gebruiker**, niet op `-d postgres` zoals bovenstrooms: die + database bestaat hier niet, en dan is de controle groen op het verkeerde antwoord +- [ ] **De images pinnen op een digest.** `postgres:17-alpine` kan meteen, met + `docker buildx imagetools inspect`. De eigen image kan pas als hij in een register staat; zie + [OPEN.md](OPEN.md) punt 1. Beide staan als TODO in de compose + +## Fase 3 - Het bouwrecept + +- [x] **`tools/evolu-relay/build.sh` (25-08-2026).** Haalt de broncode op een vastgezette commit en bouwt + de Dockerfile van Trezor. Niet hun bouwstappen nabouwen; wat wij toevoegen is de pin +- [x] **Niet in de app-map gezet, en dat is geen netheid.** Een `Dockerfile` staat niet in de + update-whitelist, dus bouwen-in-de-app zou elke nieuwe versie een herinstallatie kosten. Zie + [PLAN.md](PLAN.md) §4f +- [x] **Onder `tools/` en niet onder `build/`:** dat laatste staat in `.gitignore` als bouwselmap, dus het + recept zou stilzwijgend buiten de repo blijven. Gevonden bij het stagen, niet bij het schrijven +- [x] Het script controleert na het ophalen dat de commit is wat hij verwachtte, en faalt hard als dat niet + zo is. Zonder die regel bouwt het stil de verkeerde toestand + +## Fase 4 - Installeren en verifiëren op de Umbrel + +Niets hiervan is op een laptop te doen. Eigenaar van deze hele fase: **gebruiker**. + +- [ ] De image gebouwd (zie "Volgende stap") +- [ ] De app geïnstalleerd, en alle drie de containers blijven draaien +- [ ] **Gecontroleerd of het databaseschema zichzelf aanmaakt.** Niet uitgezocht, en dit is de eerste + plek waar het misgaat als het antwoord nee is. Kijk in de logs van de relay-container +- [ ] Trezor Suite naar `:3851` laten wijzen en een label synchroniseren. **Dit is de enige + controle die telt**, en hij kan mislukken op de eigenaarsregistratie zonder dat er iets mis is met het + pakket +- [ ] De app gestopt en gestart, en de database heeft het overleefd +- [ ] Gecontroleerd dat het toevoegen van deze app Electrum Gate niet raakt + +## Geblokkeerd / wacht op + +- [ ] **Hoe registreer je een eigenaar bij de quota-manager?** Zonder antwoord kan er niet gesynchroniseerd + worden, ook al draait alles. Wacht op **Proefopstelling** fase 1; begin bij `bruno-collection/` in de + bovenstroomse repo +- [ ] **Accepteert Trezor Suite dit adres, en in welke vorm?** Wacht op **Proefopstelling**, "Volgende + stap". Als het antwoord nee is, is dit pakket zonder waarde diff --git a/Docs/Plannen/Masterplannen/Umbrelapp.PLAN.md b/Docs/Plannen/Masterplannen/Archief/Umbrelapp.PLAN.md similarity index 100% rename from Docs/Plannen/Masterplannen/Umbrelapp.PLAN.md rename to Docs/Plannen/Masterplannen/Archief/Umbrelapp.PLAN.md diff --git a/Docs/README.md b/Docs/README.md index 6f79006..af82d8b 100644 --- a/Docs/README.md +++ b/Docs/README.md @@ -88,14 +88,15 @@ naar deze map. De oude `ARCHITECTURE.md`, `STRUCTURE.md` en `QUICKSTART.md` uit [Referenties/Architectuur-huidig.md](Referenties/Architectuur-huidig.md); ze beschreven grotendeels hetzelfde in drie versies. -**Er zijn tests, maar alleen voor Electrum Gate**, en ze dekken de agent en het manifest. Hoe ze draaien -staat in [../CLAUDE.md](../CLAUDE.md). Voor Evolu Relay is er nog geen bron en dus ook niets te testen; -stap 4 van het sessie-protocol vervalt zolang een sessie alleen aan die app werkt. +**Er zijn drie testbestanden.** Twee gaan over Electrum Gate en noemen die app-map bij naam; het derde, +`tests/test_appstore_vorm.py`, gaat over de **store** en vindt zijn apps zelf, dus een derde app valt daar +automatisch onder. Hoe ze draaien staat in [../CLAUDE.md](../CLAUDE.md). -**Wat sowieso automatisch te controleren is** en de moeite waard blijft, en dat geldt straks voor beide -app-mappen: dat de YAML geldig is, dat het `id` in `umbrel-app.yml` gelijk is aan de mapnaam, en dat elke -`image:` een `@sha256:`-digest heeft. Dat zijn precies de fouten die je op het apparaat pas merkt als de -app niet start. +Dat derde bestand dekt precies de fouten die je op het apparaat pas merkt: `id` gelijk aan de mapnaam, het +store-voorvoegsel, de voorgeschreven veldvolgorde, een `app_proxy` die naar een bestaande service wijst, en +elke gemounte map die in de repo bestaat. Wat het **niet** doet is eisen dat elke `image:` een +`@sha256:`-digest heeft. Dat is wel de regel, maar geen van de twee apps haalt hem vandaag en een suite die +altijd rood staat wordt niet gelezen; het staat als taak in de plannen en de pinstatus wordt afgedrukt. **"Gebouwd" en "werkend" liggen hier verder uit elkaar dan gebruikelijk.** Elk plan heeft daarom een paragraaf Verificatie met de controles die **op de Umbrel zelf** gedaan moeten worden. Meld die uitslag, diff --git a/README.md b/README.md index 6358fd5..5c34b50 100644 --- a/README.md +++ b/README.md @@ -37,16 +37,32 @@ trade-off is written out in [Docs/Referenties/Clients.md](Docs/Referenties/Clien ### Evolu Relay -> **Not packaged yet.** Plans only, no manifest and no compose. +> **Packaged, never installed.** The manifest and compose are here; nothing has run on an Umbrel yet. Trezor Suite syncs labels and account names between devices, and by default that runs through a server -operated by Trezor. That server is open source and called Evolu Relay. The plan is to package it here so -the sync runs through your own machine. Trezor states the data is end to end encrypted client-side, so -self-hosting does not change that guarantee, it only takes Trezor out of the picture. +operated by Trezor. That server is open source and called Evolu Relay. This app runs it on your own +machine. The data is end to end encrypted on the device, so self-hosting does not change that guarantee, +it only changes who holds the encrypted copy. -Two questions decide the shape of the package, and both are open: whether Trezor Suite can point at a -custom sync server at all, and whether the quota manager (part of Trezor's own paid hosting) is required. -See [Docs/Referenties/Upstream-evolu-relay.md](Docs/Referenties/Upstream-evolu-relay.md). +Three containers, two images. + +| Container | What it does | +|-|-| +| `relay` | the sync relay on 4000, reached through the app proxy on 3851 | +| `quota-manager` | same image, different command; registers the storage limit the relay requires | +| `db` (`postgres:17-alpine`) | storage, under `${APP_DATA_DIR}/data/postgres` | + +The app proxy runs with `PROXY_AUTH_ADD: "false"`, because Trezor Suite is not a browser with a session +cookie. That is the same pattern Umbrel's own nostr-relay app uses. The flip side: anything that can reach +port 3851 reaches the relay without signing in. What limits the damage is that the relay refuses any owner +without a storage limit registered in its database. + +**Trezor publishes no image**, so it is built from their Dockerfile, pinned to a commit, by +[tools/evolu-relay/build.sh](tools/evolu-relay/build.sh). Run that before installing. + +Still unverified: whether Trezor Suite accepts this address, whether the database schema creates itself, +and how an owner gets registered. See +[Docs/Referenties/Upstream-evolu-relay.md](Docs/Referenties/Upstream-evolu-relay.md). ## Documentatie @@ -65,13 +81,20 @@ Alles staat in **[Docs/](Docs/README.md)**. Begin bij ## Tests +``` +python tests/test_appstore_vorm.py +``` + +Die gaat over de store en vindt zijn apps zelf: id gelijk aan mapnaam, store-voorvoegsel, veldvolgorde in +het manifest, en een `app_proxy` die naar een bestaande service wijst. Daarnaast twee suites voor Electrum +Gate: + ``` python tests/test_agent_certificates.py ``` Losse scripts, geen afhankelijkheden. Let op de regels met `OVERGESLAGEN`: die toetsen hebben de -certificaatwinkel van het besturingssysteem of netwerk nodig, en zijn dan niet bewezen. Ze dekken Electrum -Gate; voor Evolu Relay is er nog geen bron. +certificaatwinkel van het besturingssysteem of netwerk nodig, en zijn dan niet bewezen. ## Licentie diff --git a/tests/test_appstore_vorm.py b/tests/test_appstore_vorm.py new file mode 100644 index 0000000..ebdd909 --- /dev/null +++ b/tests/test_appstore_vorm.py @@ -0,0 +1,308 @@ +"""Toetst de vorm die umbrelOS van deze app store eist, voor élke app erin. + +Waarom deze test bestaat. De twee bestaande testbestanden gaan over Electrum +Gate; ze noemen die app-map bij naam. Sinds 25-08-2026 zit er een tweede app in +deze repo en zijn er dus regels die niet over één app gaan maar over alle: + +- het `id` in `umbrel-app.yml` moet gelijk zijn aan de mapnaam; +- dat id moet beginnen met het store-id uit `umbrel-app-store.yml`; +- de manifestvelden staan in de volgorde die de packaging-documentatie + voorschrijft; +- er is een `docker-compose.yml`, en de `app_proxy` daarin wijst naar een + service die in datzelfde bestand bestaat. + +Dat zijn precies de fouten die je op het apparaat pas merkt: de app verschijnt +niet in de store, of hij verschijnt en start niet. Er komt geen nette +foutmelding, want de runtime-validatie van het manifest staat in umbreld +uitgecommentarieerd. + +Wat deze toetsen expres NIET doen: eisen dat elke image op een digest gepind is. +Dat is wél de regel, maar op 25-08-2026 haalt geen van de twee apps hem, en een +suite die altijd rood staat wordt niet gelezen. Het staat als taak in de plannen +Umbrelapp en Publicatie-Gate. Wat hier wel gebeurt is de pinstatus afdrukken, zodat +je hem ziet zonder erover te struikelen. + +Deze toetsen vinden hun apps zelf. Komt er een derde app bij, dan valt die +automatisch onder alles hierboven en hoeft hier niets bij. + +Draaien: + + python tests/test_appstore_vorm.py +""" + +import sys + +sys.dont_write_bytecode = True + +import os # noqa: E402 + +HERE = os.path.dirname(os.path.abspath(__file__)) +REPO = os.path.abspath(os.path.join(HERE, os.pardir)) +STORE = os.path.join(REPO, "umbrel-app-store.yml") + +# De volgorde uit de packaging-documentatie. Wat de spec niet noemt (icon, +# backupIgnore, submitter, submission) mag erachter, niet ertussen. +VOORGESCHREVEN = [ + "manifestVersion", "id", "category", "name", "version", "tagline", + "description", "releaseNotes", "developer", "website", "dependencies", + "repo", "support", "port", "gallery", "path", +] + +# Velden die er per se moeten staan, ook al is het schema soepeler: zonder deze +# is de winkelpagina leeg of start de app niet. +VERPLICHT = ["manifestVersion", "id", "category", "name", "version", "tagline", + "description", "port"] + + +class Uitslag: + def __init__(self): + self.goed = 0 + self.fout = [] + + def check(self, naam, gelukt, uitleg=""): + if gelukt: + self.goed += 1 + else: + self.fout.append(naam + ((" - " + uitleg) if uitleg else "")) + + def rapport(self): + print() + print("%d goed, %d fout" % (self.goed, len(self.fout))) + for f in self.fout: + print(" FOUT: " + f) + return 0 if not self.fout else 1 + + +def lees(pad): + with open(pad, "r", encoding="utf-8") as f: + return f.read() + + +def veld(tekst, naam): + """De waarde van een veld op het eerste niveau, of None. + + Geen YAML-lezer: de suite heeft geen afhankelijkheden en dat is een + projectregel. Daarom alleen velden die aan het begin van een regel staan. + """ + for regel in tekst.splitlines(): + if regel.startswith(naam + ":"): + return regel.split(":", 1)[1].strip().strip('"').strip("'") + return None + + +def velden_in_volgorde(tekst): + """De veldnamen op het eerste niveau, in de volgorde waarin ze staan.""" + namen = [] + for regel in tekst.splitlines(): + if not regel or regel[0] in " \t#-": + continue + if ":" not in regel: + continue + namen.append(regel.split(":", 1)[0].strip()) + return namen + + +def app_mappen(): + """Elke map in de repo-root met een umbrel-app.yml erin.""" + gevonden = [] + for naam in sorted(os.listdir(REPO)): + pad = os.path.join(REPO, naam) + if not os.path.isdir(pad): + continue + if os.path.isfile(os.path.join(pad, "umbrel-app.yml")): + gevonden.append(naam) + return gevonden + + +def test_er_is_een_store_met_apps(u, store_id, apps): + u.check("umbrel-app-store.yml heeft een id", bool(store_id)) + u.check("en er is minstens een app-map gevonden", bool(apps), + "geen enkele map met een umbrel-app.yml") + + +def test_id_en_mapnaam(u, store_id, app): + """Mapnaam == id == store-prefix + rest. Alle drie of de app bestaat niet.""" + tekst = lees(os.path.join(REPO, app, "umbrel-app.yml")) + app_id = veld(tekst, "id") + + u.check("%s: het manifest heeft een id" % app, bool(app_id)) + if not app_id: + return + + u.check("%s: id is gelijk aan de mapnaam" % app, app_id == app, + "manifest zegt %r" % app_id) + u.check("%s: id begint met het store-id %r" % (app, store_id), + app_id.startswith(store_id + "-"), + "id is %r" % app_id) + u.check("%s: id is lowercase kebab-case" % app, + app_id == app_id.lower() and " " not in app_id and "_" not in app_id, + "id is %r" % app_id) + + +def test_verplichte_velden(u, app): + tekst = lees(os.path.join(REPO, app, "umbrel-app.yml")) + ontbreekt = [naam for naam in VERPLICHT if veld(tekst, naam) is None] + u.check("%s: alle verplichte manifestvelden staan erin" % app, + not ontbreekt, "ontbreekt: %r" % ontbreekt) + + +def test_manifest_volgorde(u, app): + """De voorgeschreven velden in de voorgeschreven onderlinge orde. + + Niet dat ze er alle zestien zijn: `dependencies` hoort weg te blijven bij een + app zonder afhankelijkheden. Wel dat wat er staat niet door elkaar loopt, want + dat is de eis waar een review op valt. + """ + aanwezig = velden_in_volgorde(lees(os.path.join(REPO, app, "umbrel-app.yml"))) + volgens_spec = [n for n in aanwezig if n in VOORGESCHREVEN] + verwacht = [n for n in VOORGESCHREVEN if n in volgens_spec] + + u.check("%s: de manifestvelden staan in de voorgeschreven volgorde" % app, + volgens_spec == verwacht, + "gevonden %r, verwacht %r" % (volgens_spec, verwacht)) + + # Wat de spec niet noemt hoort erachter, niet ertussen. Anders is de kop niet + # letterlijk goed en moet er bij inlevering geschoven worden. + laatste_spec = -1 + for i, naam in enumerate(aanwezig): + if naam in VOORGESCHREVEN: + laatste_spec = i + extra_ertussen = [n for n in aanwezig[:laatste_spec] if n not in VOORGESCHREVEN] + u.check("%s: velden buiten de spec staan achteraan" % app, + not extra_ertussen, "ertussen: %r" % extra_ertussen) + + +def test_compose_bestaat_en_hangt_samen(u, app): + """De app_proxy moet naar een service wijzen die bestaat. + + Dit is een echte klasse fouten en niet een formaliteit: de hostnaam is + `__1`, dus hij bevat het app-id én de servicenaam. Verandert + een van de twee, dan wijst de proxy naar niets en meldt umbrelOS alleen dat de + server onbereikbaar is. Precies het symptoom dat bij Electrum Gate 0.0.3 een + dag kostte. + """ + pad = os.path.join(REPO, app, "docker-compose.yml") + u.check("%s: er is een docker-compose.yml" % app, os.path.isfile(pad)) + if not os.path.isfile(pad): + return + + tekst = lees(pad) + + # Servicenamen: twee spaties diep onder services:, eindigend op een dubbele + # punt. Geen YAML-lezer, zie veld(). + services = [] + in_services = False + for regel in tekst.splitlines(): + if regel.startswith("services:"): + in_services = True + continue + if in_services and regel and not regel[0].isspace(): + break + if in_services and regel.startswith(" ") and not regel.startswith(" "): + kaal = regel.strip() + if kaal.endswith(":") and not kaal.startswith("#"): + services.append(kaal[:-1]) + + u.check("%s: de compose heeft een app_proxy" % app, + "app_proxy" in services, "services: %r" % services) + + app_host = None + for regel in tekst.splitlines(): + if regel.strip().startswith("APP_HOST:"): + app_host = regel.split(":", 1)[1].strip() + break + + u.check("%s: de app_proxy heeft een APP_HOST" % app, bool(app_host)) + if not app_host: + return + + u.check("%s: APP_HOST begint met het app-id" % app, + app_host.startswith(app + "_"), + "APP_HOST is %r maar de map heet %r" % (app_host, app)) + + # De vorm is __1; haal de servicenaam eruit en kijk of die + # bestaat. + rest = app_host[len(app) + 1:] + u.check("%s: APP_HOST eindigt op _1" % app, rest.endswith("_1"), + "APP_HOST is %r" % app_host) + service = rest[:-2] if rest.endswith("_1") else rest + u.check("%s: APP_HOST wijst naar een service die bestaat" % app, + service in services, + "wijst naar %r, aanwezig: %r" % (service, services)) + + +def test_data_onder_data(u, app): + """Wat de app bewaart staat onder data/, en er is een .gitkeep per map. + + De appstore-eis is dat gebruikersstaat onder ${APP_DATA_DIR}/data/... valt en + dat elke map die bij de eerste start moet bestaan in de repo staat. Een mount + naar een map die er niet is, maakt Docker aan als root, en dan kan de app er + niet in schrijven. + """ + pad = os.path.join(REPO, app, "docker-compose.yml") + if not os.path.isfile(pad): + return + tekst = lees(pad) + + buiten = [] + ontbreekt = [] + for regel in tekst.splitlines(): + kaal = regel.strip() + if not kaal.startswith("- ${APP_DATA_DIR}/"): + continue + pad_in_app = kaal[len("- ${APP_DATA_DIR}/"):].split(":", 1)[0] + # Losse bestanden die uit een *.template komen zijn geen gebruikersstaat. + if "/" not in pad_in_app: + continue + if not pad_in_app.startswith("data/"): + buiten.append(pad_in_app) + continue + op_schijf = os.path.join(REPO, app, pad_in_app) + if not os.path.exists(op_schijf): + ontbreekt.append(pad_in_app) + elif os.path.isdir(op_schijf) and not os.path.isfile( + os.path.join(op_schijf, ".gitkeep")): + ontbreekt.append(pad_in_app + "/.gitkeep") + + u.check("%s: alle mounts met een pad staan onder data/" % app, + not buiten, "erbuiten: %r" % buiten) + u.check("%s: elke gemounte map bestaat in de repo" % app, + not ontbreekt, "ontbreekt: %r" % ontbreekt) + + +def rapporteer_pinstatus(apps): + """Afdrukken, niet toetsen. Zie de uitleg bovenaan dit bestand.""" + print() + print("Pinstatus van de images (informatief, geen toets):") + for app in apps: + pad = os.path.join(REPO, app, "docker-compose.yml") + if not os.path.isfile(pad): + continue + for regel in lees(pad).splitlines(): + kaal = regel.strip() + if not kaal.startswith("image:"): + continue + image = kaal.split(":", 1)[1].strip() + merk = "gepind" if "@sha256:" in image else "NIET GEPIND" + print(" %-24s %-12s %s" % (app, merk, image)) + + +def main(): + u = Uitslag() + store_id = veld(lees(STORE), "id") + apps = app_mappen() + + test_er_is_een_store_met_apps(u, store_id, apps) + for app in apps: + test_id_en_mapnaam(u, store_id, app) + test_verplichte_velden(u, app) + test_manifest_volgorde(u, app) + test_compose_bestaat_en_hangt_samen(u, app) + test_data_onder_data(u, app) + + rapporteer_pinstatus(apps) + return u.rapport() + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/evolu-relay/build.sh b/tools/evolu-relay/build.sh new file mode 100644 index 0000000..7377a42 --- /dev/null +++ b/tools/evolu-relay/build.sh @@ -0,0 +1,71 @@ +#!/bin/sh +# ═══════════════════════════════════════════════════════════════════════════════ +# Bouwt de image voor de app whatsnext-evolu-relay. +# +# Waarom dit bestand bestaat: Trezor publiceert geen image. Hun werkproces duwt +# naar een eigen Amazon ECR en op Docker Hub staat niets, dus er is geen +# `docker pull` die iets oplevert. De broncode is wel publiek, met een Dockerfile +# erin. Dit script is de brug: het haalt de broncode op een vastgezette commit en +# bouwt hún Dockerfile. +# +# Waarom het NIET in de app-map staat: umbreld kopieert de hele app-map naar het +# apparaat, en bij een update wordt alleen een whitelist ververst waar een +# Dockerfile niet in zit. Bouwen tijdens het starten van de app zou de installatie +# bovendien minuten laten hangen. Het recept hoort dus in de repo en het resultaat +# in een register of in de lokale Docker-opslag; de app verwijst alleen naar de tag. +# +# Waarom we hun Dockerfile gebruiken en geen eigen: dan hoeven we hun bouwstappen +# niet na te bouwen en niet bij te houden. Wat wij toevoegen is uitsluitend de pin. +# +# Draaien: sh build.sh +# Vereist: docker en git op de machine waar je bouwt. +# ═══════════════════════════════════════════════════════════════════════════════ + +set -eu + +# ── De pin ──────────────────────────────────────────────────────────────────── +# Dit is het enige wat je aanpast als je een nieuwere versie wil. Verhoog daarna +# ook `version` in whatsnext-evolu-relay/umbrel-app.yml, anders rolt umbrelOS de +# wijziging niet uit. +UPSTREAM_REPO="https://github.com/trezor/trezor-suite-sync.git" +UPSTREAM_COMMIT="c03a2043acec48d4a1bbafca65e94da33dce1edd" + +# De tag waar docker-compose.yml van de app naar verwijst. De korte commit zit +# erin, zodat je op het apparaat kunt zien wat er draait. +IMAGE="whatsnext/evolu-relay:c03a204" + +# ── Bouwen ──────────────────────────────────────────────────────────────────── +WORKDIR="$(mktemp -d)" +cleanup() { rm -rf "$WORKDIR"; } +trap cleanup EXIT INT TERM + +echo "Broncode ophalen op ${UPSTREAM_COMMIT}" + +# Niet `clone --branch`: dat pint op een naam die meebeweegt. Een fetch van één +# commit haalt precies deze toestand op en niets anders. GitHub staat het ophalen +# van een losse commit-sha toe; een Git-server die dat niet doet, geeft hier een +# foutmelding in plaats van stil een andere versie te bouwen. +git init --quiet "$WORKDIR" +git -C "$WORKDIR" remote add origin "$UPSTREAM_REPO" +git -C "$WORKDIR" fetch --quiet --depth 1 origin "$UPSTREAM_COMMIT" +git -C "$WORKDIR" checkout --quiet FETCH_HEAD + +# Controle dat we hebben wat we dachten. Zonder deze regel bouwt het script bij +# een gewijzigde afspraak over sha's stil de verkeerde toestand. +GEVONDEN="$(git -C "$WORKDIR" rev-parse HEAD)" +if [ "$GEVONDEN" != "$UPSTREAM_COMMIT" ]; then + echo "FOUT: opgehaald ${GEVONDEN}, verwacht ${UPSTREAM_COMMIT}" >&2 + exit 1 +fi + +echo "Bouwen als ${IMAGE}" +docker build --tag "$IMAGE" "$WORKDIR" + +echo +echo "Klaar. De app verwijst naar deze tag:" +docker image inspect --format '{{.RepoTags}} {{.Id}}' "$IMAGE" +echo +echo "Wil je hem later uit een register halen in plaats van lokaal, dan is dit" +echo "de plek: tag hem naar dat register, push, en zet de digest uit" +echo "\`docker buildx imagetools inspect\` in docker-compose.yml. Zie" +echo "Docs/Referenties/Images-pinnen.md." diff --git a/whatsnext-evolu-relay/data/postgres/.gitkeep b/whatsnext-evolu-relay/data/postgres/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/whatsnext-evolu-relay/docker-compose.yml b/whatsnext-evolu-relay/docker-compose.yml new file mode 100644 index 0000000..f95a1b4 --- /dev/null +++ b/whatsnext-evolu-relay/docker-compose.yml @@ -0,0 +1,117 @@ +# ═══════════════════════════════════════════════════════════════════════════════ +# Evolu Relay - de sync-server van Trezor Suite op je eigen Umbrel. +# +# Drie containers, twee images. De relay en de quota-manager komen uit dezélfde +# image met een ander `command`: bovenstrooms is het één codebase met meerdere +# startscripts. Dat is nagetrokken in de Dockerfile en de README van Trezor; zie +# Docs/Referenties/Upstream-evolu-relay.md §2. +# ═══════════════════════════════════════════════════════════════════════════════ + +services: + app_proxy: + environment: + APP_HOST: whatsnext-evolu-relay_relay_1 + APP_PORT: 4000 + # Trezor Suite is geen browser met een sessiecookie. Achter de inlog van + # umbrelOS zou het een inlogpagina krijgen in plaats van de relay, en dat + # is niet op te lossen aan onze kant. + # + # Dit is geen omweg: het is het patroon dat de eigen nostr-relay-app van + # Umbrel gebruikt, om precies dezelfde reden. Ook Gitea en Budibase in de + # officiele store doen het zo. + # + # De prijs staat er wel bij: wie deze poort kan bereiken, bereikt de relay + # zonder aanmelding. Wat de schade beperkt is dat de relay zelf elke + # eigenaar weigert die geen limietenrij in de database heeft. Zet deze app + # dus niet zonder meer open naar het internet; zie het masterplan + # Bereikbaarheid. + PROXY_AUTH_ADD: "false" + + relay: + # TODO pinnen. Deze tag komt uit tools/evolu-relay/build.sh en bestaat + # voorlopig alleen in de lokale Docker-opslag van de machine waar gebouwd is. + # Daarom staat er geen @sha256: er is nog geen register. Compose haalt niets + # op zolang de image lokaal bestaat; zodra de tag naar een register wijst, + # hoort de digest erbij. Zie Docs/Referenties/Images-pinnen.md. + image: whatsnext/evolu-relay:c03a204 + restart: on-failure + depends_on: + db: + condition: service_healthy + # Bovenstrooms staat `CMD ["yarn", "start"]` in de Dockerfile, met daarbij een + # eigen commentaar dat het misschien een van de twee specifieke scripts had + # moeten zijn. Daarom hier expliciet, en niet vertrouwen op de standaard. + command: ["yarn", "start-evolu-relay"] + environment: + RELAY_PORT: "4000" + HEALTH_PORT: "4002" + # dev of prod. Volgens .env.sample zet prod authenticatie aan; in de code + # bepaalt die vlag alleen het logniveau en staan de autorisatiecontroles + # onvoorwaardelijk aan. Een van de twee is achterhaald en dat is nog niet + # uitgezocht. Hier staat prod, want dat is de veilige kant van die + # onduidelijkheid: als het iets aanzet, willen we dat het aanstaat. + SERVER_ENV: prod + POSTGRES_GATE_HOST: whatsnext-evolu-relay_db_1 + POSTGRES_GATE_PORT: "5432" + POSTGRES_GATE_USER: suite-sync + POSTGRES_GATE_DB: suite-sync-gate + # Door umbrelOS per installatie afgeleid. Nooit een letterlijke waarde: + # deze repo is publiek. + POSTGRES_GATE_PASSWORD: ${APP_PASSWORD} + POSTGRES_GATE_SSL: "false" + + quota-manager: + image: whatsnext/evolu-relay:c03a204 + restart: on-failure + depends_on: + db: + condition: service_healthy + command: ["yarn", "start-quota-manager"] + # Waarom deze container er is, terwijl hij bij Trezor bij hun betaalde + # hosting hoort: de relay weigert elke eigenaar zonder rij in de + # limietentabel, en dit is wat die rijen aanmaakt. Er is geen HTTP-koppeling + # tussen de twee; ze delen alleen de database. Zie Upstream-evolu-relay.md §3. + environment: + QUOTA_MANAGER_PORT: "4001" + HEALTH_PORT: "4012" + SERVER_ENV: prod + POSTGRES_GATE_HOST: whatsnext-evolu-relay_db_1 + POSTGRES_GATE_PORT: "5432" + POSTGRES_GATE_USER: suite-sync + POSTGRES_GATE_DB: suite-sync-gate + POSTGRES_GATE_PASSWORD: ${APP_PASSWORD} + POSTGRES_GATE_SSL: "false" + + db: + # TODO pinnen op de multi-arch index-digest, met + # `docker buildx imagetools inspect postgres:17-alpine`. Kan alleen op een + # machine met Docker; staat als taak in het plan Umbrelapp. + # + # Niet `postgres` kaal zoals de compose van Trezor doet: dat is `latest` en + # dus niet reproduceerbaar, en een grote-versiesprong van Postgres migreert + # zijn datamap niet vanzelf. Dan start de database niet meer en is de data + # alleen met handwerk terug te halen. + image: postgres:17-alpine + restart: on-failure + environment: + POSTGRES_USER: suite-sync + POSTGRES_DB: suite-sync-gate + POSTGRES_PASSWORD: ${APP_PASSWORD} + # Een submap en niet de wortel van de mount. Postgres weigert een datamap + # die al iets anders bevat, en een mount heeft op sommige bestandssystemen + # een lost+found. + PGDATA: /var/lib/postgresql/data/pgdata + volumes: + # Onder data/ en niet in een naamloos Docker-volume: dit is wat umbrelOS + # bewaart en meeneemt in de back-up. In een volume zou de database een + # herinstallatie van de app niet overleven. + - ${APP_DATA_DIR}/data/postgres:/var/lib/postgresql/data + healthcheck: + # Op de eigen database en gebruiker, niet op `-d postgres` zoals + # bovenstrooms: die database bestaat hier niet en dan is de controle groen + # terwijl het verkeerde antwoord gegeven wordt. + test: ["CMD-SHELL", "pg_isready -U suite-sync -d suite-sync-gate"] + interval: 10s + timeout: 5s + retries: 10 + start_period: 30s diff --git a/whatsnext-evolu-relay/umbrel-app.yml b/whatsnext-evolu-relay/umbrel-app.yml new file mode 100644 index 0000000..b716b62 --- /dev/null +++ b/whatsnext-evolu-relay/umbrel-app.yml @@ -0,0 +1,81 @@ +# De veldvolgorde hieronder is niet vrij: de packaging-documentatie van de +# officiele appstore schrijft hem voor. Zie de tabel in +# Docs/Referenties/Umbrel-appstore-spec.md. +# +# Wat de spec niet noemt staat onderaan dit bestand en niet ertussen. Dan is de +# voorgeschreven kop letterlijk goed en hoeft er bij inlevering alleen iets weg. +# +# `dependencies` is weggelaten en niet leeg gezet: deze app hangt van geen enkele +# andere app af. Een leeg veld zou suggereren dat er iets te kiezen valt. +manifestVersion: 1 +id: whatsnext-evolu-relay +category: bitcoin +name: Evolu Relay +version: "0.0.1" +tagline: Sync your Trezor Suite labels through your own machine +description: >- + Trezor Suite can sync the labels and account names you give your addresses across your + devices. By default that runs through a server operated by Trezor. This app runs that + server on your Umbrel instead, so the sync goes through a machine you own. + + + The data is end to end encrypted on your device before it leaves, so Trezor cannot read + your labels either way. What self-hosting changes is who holds the encrypted copy and who + can see that you are syncing at all. + + + This is Trezor's own relay, not a reimplementation: it is built from the source they publish + as trezor/trezor-suite-sync, pinned to a specific commit. + + + Point Trezor Suite at this Umbrel to use it. Away from home you will need a way in, such as + Tailscale or a reverse proxy with your own domain. + + + Two things to know before you install. The relay has no web interface, so the tile opens the + relay itself rather than a page for you to read. And it only serves an owner that has a + storage limit registered in its database, which is what the included quota manager is for; + if syncing is refused, that registration is the place to look. +releaseNotes: >- + First release. Packages Trezor's Evolu Relay, the quota manager it needs, and a PostgreSQL + database, all on your own machine. + + + Built from source pinned to commit c03a204 of trezor/trezor-suite-sync. There is no image + published by Trezor, so the image is built from their Dockerfile; see tools/evolu-relay in + the app store repository. + + + Not verified yet: whether Trezor Suite accepts this address, and what has to happen before + an owner is allowed to sync. +developer: "WhatsNext?" +website: https://sc.kamenier-hamer.nl/sysop/UmbrelApps +repo: https://sc.kamenier-hamer.nl/sysop/UmbrelApps +support: https://sc.kamenier-hamer.nl/sysop/UmbrelApps/issues +# De poort waarop umbrelOS deze app aanbiedt, en tegelijk de poort waar Trezor +# Suite naartoe wijst: de app-proxy staat met PROXY_AUTH_ADD op "false", dus wat +# hier binnenkomt gaat rechtstreeks naar de relay. Zie docker-compose.yml. +# +# Niet 4000, de eigen poort van de relay: die is een veelgebruikte poort en een +# botsing op de host merk je pas als de app niet start. Dat heeft bij Electrum +# Gate een keer een dag gekost, met 50002 tegen Fulcrum. 3851 sluit aan op de 3850 +# van die app. +port: 3851 +# Leeg laten bij een nieuw pakket; de plaatjes doet het store-team zelf. +gallery: [] +path: "" +submitter: "WhatsNext?" +submission: https://sc.kamenier-hamer.nl/sysop/UmbrelApps + +# ── Hieronder staat wat de voorgeschreven volgorde niet noemt ──────────────── +# +# Er is geen `icon` en dat is zichtbaar: de tegel blijft naamloos in de store. +# Bewust nog niet gevuld, want een verkeerd of geleend icoon is erger dan geen, +# en er is nog geen eigen ontwerp. Staat als taak in het plan Umbrelapp. + +# Wat niet in de back-up hoeft. Paden zijn relatief aan de app-datamap. +# +# Voor deze app is dat vrijwel niets, en dat is expres: de database ís de waarde +# van de app. Electrum Gate gebruikt dit veld wel, maar daar gaat het om een log +# en een statusbestand. Neem die keuze hier niet uit gewoonte over. +backupIgnore: []