Eén app store, twee apps
umbrelOS leest per store één repo, dus twee apps in twee repo's kan niet. Deze repo is de store en bevat vanaf nu Electrum Gate en het werk aan Evolu Relay. Opgezet als verse repo op verzoek van de gebruiker: de historie van ElectrumTLS en van EvoluRelay komt niet mee. Dat heeft één gevolg dat verder gaat dan opruimen. In de historie van ElectrumTLS staat het domein van de gebruiker en het certificaatpad, van vóór de opschoning van 19-08. Die komt hier niet in. Zolang die repo op de Git-server blijft staan verandert dat niets, dus het weghalen ervan is het laatste stuk van open punt 3 van het plan Appstore, en geen bijzaak. De store zelf hoefde niet te veranderen: store-id whatsnext, en dus blijft het app-id whatsnext-electrum-gate. Dat hangt aan het store-id en niet aan de URL, dus voor umbrelOS is dit dezelfde app in een andere store. Dat de store op 19-08 naar de maker genoemd werd in plaats van naar deze ene app, betaalt zich hier uit. Wat de documentatie betreft is dit één wortel voor beide apps, en dat was de reden om samen te voegen en niet de prijs ervan: de appstore-spec, het pinnen van images en de werkwijze golden al voor allebei en stonden in twee repo's naast elkaar. De kruisverwijzing die daarvoor nodig was (Referenties/Umbrel-appstore.md in de oude EvoluRelay-repo) is verdwenen; wat daarin stond over de plekken waar de relay een ander geval is, staat nu als ontwerp in het masterplan Umbrelapp §4. Botsende namen kregen een achtervoegsel met de app, en alleen die: Publicatie werd Publicatie-Gate en Publicatie-Relay, CHANGELOG.md werd CHANGELOG-electrum-gate.md. Proefopstelling kreeg 007, tussen de twee bestaande nummers, zodat de bovenkant van de reeks op tier-orde blijft staan. CONTINUE_HERE.md heeft een kolom App, maar de tiers lopen over beide apps heen: er is één volgorde van werken. Electrum Gate gaat naar 0.0.15, want website, repo, support, submission en icon wijzen nu naar UmbrelApps en zonder versieverhoging rolt dat niet uit. De release notes leggen aan de gebruiker uit dat hij de store opnieuw moet toevoegen. Of een geïnstalleerde app een wisseling van store-URL overleeft is nog steeds niet uitgezocht; dat blijkt bij het omzetten. Twee dingen in de plannen van Electrum Gate waren door deze verhuizing niet meer waar en zijn bijgewerkt: de taak "de repo hernoemen" in fase 7 is afgevinkt, en de repo-vorm in PLAN.md §4a toonde nog de store-id electrumtls, die al sinds fase 7 achterhaald was. Tests: 39 goed 0 fout en 54 goed 0 fout, niets overgeslagen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
# De huidige opzet: hoe het vandaag in elkaar zit
|
||||
|
||||
Naslag. Wat er ís, zodat een latere sessie het niet hoeft te reconstrueren. Wat er mis mee is en wat eraan
|
||||
gebeurt staat in de plannen.
|
||||
|
||||
**Let op het onderscheid dat dit document maakt**, want het is op 19-08-2026 het belangrijkste feit over
|
||||
deze app: er is verschil tussen wat er in de repo staat en wat er op de Umbrel draait. Dat verschil is
|
||||
groot, en het per ongeluk gelijkstellen leidt tot conclusies die niet kloppen.
|
||||
|
||||
| | In de repo | Draait op de Umbrel |
|
||||
|-|-|-|
|
||||
| Versie | 0.0.3 | 2.0.1, en die app wordt gedeïnstalleerd |
|
||||
| App-id | `whatsnext-electrum-gate` | `electrumtls-electrum-tls` |
|
||||
| TLS-poort | 50022 | 50002 |
|
||||
| Containers | `server` plus `agent` | alleen `server` |
|
||||
| Certificaat | door de agent gekozen uit de gevonden mappen | vast pad naar `sync.kamenier-hamer.nl` |
|
||||
| Dashboard | leest `status.json` | verzonnen waarden, hardgecodeerd domein |
|
||||
|
||||
Alles in de kolom "in de repo" is **ongetest buiten de unittests**. De vorige versie draait en een wallet
|
||||
verbindt er over TLS mee; dat is bewezen. De nieuwe niet.
|
||||
|
||||
## 1. Wat het doet
|
||||
|
||||
Een Electrum-server spreekt onversleuteld TCP. Een wallet buiten het netwerk wil TLS. Deze app zet daar
|
||||
nginx met de `stream`-module tussen: die accepteert TLS, termineert het, en praat plat door naar de
|
||||
Electrum-server die de gebruiker in umbrelOS gekozen heeft.
|
||||
|
||||
```
|
||||
Electrum-wallet ──TLS 50022──▶ nginx stream ──plat──▶ Electrs, Fulcrum of ElectrumX
|
||||
▲
|
||||
cert.conf, geschreven door de agent
|
||||
```
|
||||
|
||||
Het certificaat komt van een reverse proxy die op dezelfde Umbrel al Let's Encrypt-certificaten beheert.
|
||||
Deze app leest die mappen alleen; hij vraagt zelf niets aan en vernieuwt niets.
|
||||
|
||||
## 2. De twee containers
|
||||
|
||||
**`server`** (`nginx:alpine`) doet twee dingen. In het `http`-blok serveert hij het dashboard op poort 80,
|
||||
achter de `app_proxy` van umbrelOS, plus `status.json` en een doorstuur naar de API van de agent. In het
|
||||
`stream`-blok termineert hij TLS op 50022. Zijn `command` schrijft bij het starten het `log_format` voor de
|
||||
sessielog weg, wacht tot de agent een certificaat gekozen heeft, en draait daarna een lus die nginx
|
||||
herlaadt zodra de agent daarom vraagt.
|
||||
|
||||
**`agent`** (`python:3-alpine`) draait `agent.py` en doet elke minuut een ronde: de certificaatmappen
|
||||
scannen, de keuze toepassen, de Electrum-server bevragen voor blokhoogte en reactietijd, de sessielog van
|
||||
nginx uitlezen, en `status.json` schrijven. Daarnaast luistert hij op poort 8000 voor de certificaatkeuze
|
||||
van het dashboard.
|
||||
|
||||
**Waarom de agent nginx niet zelf herlaadt.** Dat zou de Docker-socket vragen, en die is er bewust uit
|
||||
gehaald: hij geeft root-toegang tot de host. In plaats daarvan schrijft de agent `cert.conf` plus een
|
||||
vlagbestand, en herlaadt de nginx-container zichzelf. Een reload verbreekt bestaande wallet-verbindingen
|
||||
niet; een containerherstart wel.
|
||||
|
||||
## 3. De gedeelde map
|
||||
|
||||
Beide containers hebben `${APP_DATA_DIR}/runtime` gemount op `/var/lib/gate`. Dat is het enige raakvlak
|
||||
tussen de twee, en het is met opzet een map met bestanden en geen protocol.
|
||||
|
||||
| Bestand | Wie schrijft | Waarvoor |
|
||||
|-|-|-|
|
||||
| `status.json` | agent | alles wat het dashboard toont |
|
||||
| `cert.conf` | agent | de `ssl_certificate`-regels die nginx includeert |
|
||||
| `reload` | agent | vraagt nginx om een herlading; wordt `reload.done` |
|
||||
| `stream-log.conf` | nginx | het `log_format`, hier omdat een dollarteken niet in een template kan |
|
||||
| `stream.log` | nginx | een regel per afgesloten sessie, zonder client-adres |
|
||||
| `config/selected-cert` | agent | de keuze van de gebruiker, overleeft een herstart |
|
||||
|
||||
## 4. Wat er nog hardgecodeerd is
|
||||
|
||||
Sinds 19-08-2026 bijna niets meer in de app zelf. De domeinnaam en het certificaatpad zijn uit
|
||||
`docker-compose.yml` en `nginx.conf.template` verdwenen: de agent bepaalt ze.
|
||||
|
||||
Wat er nog staat, en waar:
|
||||
|
||||
- **de mounts van de certificaatbronnen** in `docker-compose.yml`, want een pad instellen naar iets wat
|
||||
niet gemount is levert een map op die de container niet ziet. Nginx Proxy Manager staat er
|
||||
uitgecommentarieerd bij, omdat het pad niet geverifieerd is;
|
||||
- **de poortnummers** 50022 en 3850;
|
||||
- **de vertaaltabel van backend-IP naar naam** in de agent, met `unknown` als terugval;
|
||||
- **de repo-URL's** in `umbrel-app.yml`. Die horen daar; ze wijzen naar de repo.
|
||||
|
||||
Het plan **Configuratie** haalt de eerste twee naar een configuratiebestand.
|
||||
|
||||
## 5. Hoe het geïnstalleerd wordt
|
||||
|
||||
Als community app store: de gebruiker plakt de repo-URL in umbrelOS en installeert de app. umbreld kloont
|
||||
anoniem met isomorphic-git, dus de repo moet publiek en SHA-1 zijn. Bij installatie wordt de hele app-map
|
||||
gekopieerd; **bij een update alleen een whitelist**, en dat stuurt het hele ontwerp. Zie
|
||||
[Umbrel-appstore-spec.md](Umbrel-appstore-spec.md).
|
||||
|
||||
## 6. Wat er goed aan is, en dat is het onthouden waard
|
||||
|
||||
- **geen Docker-socket**, dus geen root-toegang tot de host;
|
||||
- **geen eigen image**, dus geen bouwstap, geen registry en geen multi-arch-gedoe. Beide containers
|
||||
draaien op een officiële image met configuratie uit een template;
|
||||
- **niets wordt bij het starten geïnstalleerd.** De vorige opzet deed `apk add stunnel` bij élke start;
|
||||
- **de app vraagt geen certificaten aan.** ACME blijft bij de reverse proxy, en dat is de reden dat deze
|
||||
app zo klein kan blijven;
|
||||
- **een certificaatvernieuwing is een reload**, dus wallet-verbindingen blijven staan.
|
||||
@@ -0,0 +1,98 @@
|
||||
# Wie kan deze app gebruiken
|
||||
|
||||
Naslag. Welke wallets en programma's naar een eigen Electrum-server kunnen wijzen, in welke vorm ze het
|
||||
adres willen, en wanneer TLS daarbij werkelijk iets toevoegt. Uitgezocht op 18-08-2026.
|
||||
|
||||
Dit stuurt drie dingen: de beschrijving in de app store, de verbindingsregels op het dashboard (zie het
|
||||
plan **Webinterface**), en de vraag of TLS voor een bepaalde client eigenlijk wel het juiste antwoord is.
|
||||
|
||||
## 1. Waarom TLS, en niet gewoon Tor
|
||||
|
||||
**De privacywinst zit in de eigen server, niet in het transport.** Verbindt een wallet met een publieke
|
||||
Electrum-server, dan vraagt hij daar de geschiedenis van al zijn adressen op, en daarmee weet die server
|
||||
welke adressen en welk saldo bij één gebruiker horen. Dat is de grootste weggever in het dagelijks gebruik
|
||||
van een wallet, en die verdwijnt zodra je je eigen server draait. Dat geldt ongeacht of je over Tor of
|
||||
over TLS verbindt, en het is dus de eigenlijke reden dat deze opzet bestaat.
|
||||
|
||||
Wat daarná overblijft is een afweging over metadata, en die is kleiner. **Bijna elke client hieronder kan
|
||||
ook over Tor.** Een onion-adres is versleuteld, vraagt geen certificaat, geen open poort in de router en geen
|
||||
domeinnaam. Voor privacy is het zelfs beter.
|
||||
|
||||
TLS is dus geen vervanging van Tor maar een andere afweging, en hij wint op deze punten:
|
||||
|
||||
- **Snelheid.** Een Tor-verbinding voegt honderden milliseconden per verzoek toe, en dat merk je bij het
|
||||
synchroniseren van een wallet met veel adressen.
|
||||
- **Betrouwbaarheid op mobiel.** Tor op een telefoon is gevoelig voor slaapstanden en wisselende
|
||||
netwerken; een gewone TLS-verbinding niet.
|
||||
- **Netwerken die Tor blokkeren.** Bedrijfsnetwerken en sommige providers.
|
||||
- **Geen handmatige verificatie.** Bij een publiek vertrouwd certificaat controleert de client de naam
|
||||
zelf. Bij een zelfondertekend certificaat moet de gebruiker een vingerafdruk overtypen, en dat is
|
||||
precies de stap die mensen overslaan.
|
||||
|
||||
Waar TLS **niet** in voorziet: het verbergt niet dat er verkeer is, en je domeinnaam is publiek zichtbaar
|
||||
in het certificaattransparantielogboek. Wie dat wil vermijden, hoort Tor te gebruiken.
|
||||
|
||||
## 2. De clients
|
||||
|
||||
| Client | Kan naar eigen Electrum-server | Vorm van het adres | Opmerking |
|
||||
|-|-|-|-|
|
||||
| **Trezor Suite** (desktop en mobiel) | ja | `host:poort:protocol`, met `s` voor SSL | Instellingen → Netwerken → tandwiel bij Bitcoin. Zie §3 |
|
||||
| **Sparrow Wallet** | ja | host en poort apart, met een keuze voor SSL | De keuze voor gevorderden op de desktop; kan ook rechtstreeks naar Bitcoin Core |
|
||||
| **Electrum** (desktop en Android) | ja | `host:poort:s` | De referentie-implementatie van het protocol |
|
||||
| **BlueWallet** | ja | host en poort, in de instellingen | Mobiel; hier weegt het snelheidsargument tegenover Tor het zwaarst |
|
||||
| **Nunchuk** | ja | host en poort | Gericht op multisig, op alle platforms |
|
||||
| **BitBoxApp** | ja | via de geavanceerde instellingen | Begeleidende app bij de BitBox-hardwarewallet |
|
||||
| **Blockstream** | ja | host en poort, met een Tor-schakelaar | Heeft een expliciete optie voor een eigen Electrum-server. Heette Blockstream Green; de app is omgedoopt, gemeld door de gebruiker op 20-08-2026 |
|
||||
|
||||
**Niet van toepassing**, en het opschrijven waard zodat de vraag niet terugkomt: **Wasabi** en
|
||||
**Samourai** gebruiken hun eigen achterkant en spreken geen Electrum-protocol. **Specter Desktop** praat
|
||||
rechtstreeks met Bitcoin Core via RPC. Hardwarewallets als **Coldcard**, **Keystone** en **Passport**
|
||||
verbinden nooit zelf, maar via een van de programma's hierboven.
|
||||
|
||||
**Binnen je eigen Umbrel is deze app niet nodig.** Apps als mempool en btc-rpc-explorer praten
|
||||
rechtstreeks met Electrs over het interne netwerk. Deze app is uitsluitend voor clients **buiten** je
|
||||
netwerk.
|
||||
|
||||
## 3. Trezor Suite, want die is het meest specifiek
|
||||
|
||||
De vorm is `host:poort:protocol`, waarbij de laatste letter het protocol kiest:
|
||||
|
||||
```
|
||||
sync.kamenier-hamer.nl:50002:s TLS, dus via deze app
|
||||
192.168.1.100:50001:t plat TCP, alleen binnen het eigen netwerk
|
||||
```
|
||||
|
||||
`s` is SSL/TLS, `t` is onversleuteld TCP. Een `.onion`-adres kan ook, want Suite heeft Tor ingebouwd.
|
||||
|
||||
**De tegenspraak in Trezors documentatie is opgelost, en niet in het voordeel van de documentatie.** Er
|
||||
stond hier dat een eigen achterkant alleen in de desktopversie kan, met de aantekening dat elders in
|
||||
diezelfde documentatie een keuzelijst voor mobiel beschreven staat. **De gebruiker heeft het op 20-08-2026
|
||||
op iOS nagekeken: de instelling zit er, dus de mobiele app kan het wel.** Android is niet nagekeken en
|
||||
zal vermoedelijk hetzelfde doen; er staat daarom "also in the mobile app" op het dashboard en niet
|
||||
"iOS en Android".
|
||||
|
||||
Dit is het tweede geval in dit project waarin de documentatie van een leverancier het aflegt tegen één
|
||||
keer kijken. Waard om te onthouden bij de andere regels in de tabel hierboven: die komen uit
|
||||
documentatie, niet uit een test.
|
||||
|
||||
## 4. Wat dit betekent voor het dashboard
|
||||
|
||||
De verbindingsregel is per client net anders, en juist die kleine verschillen kosten mensen tijd. Het
|
||||
dashboard kan ze kant-en-klaar tonen met een kopieerknop, in plaats van alleen het domein en de poort:
|
||||
|
||||
- Trezor Suite: `sync.kamenier-hamer.nl:50002:s`
|
||||
- Electrum: `sync.kamenier-hamer.nl:50002:s`
|
||||
- Sparrow, BlueWallet, Nunchuk, Green: host en poort apart, met SSL aangevinkt
|
||||
|
||||
Dat is goedkoop om te bouwen, want de waarden staan na het plan **Configuratie** toch al in de
|
||||
configuratie.
|
||||
|
||||
## 5. Bronnen
|
||||
|
||||
Geraadpleegd op 18-08-2026:
|
||||
|
||||
- Trezor Knowledge Base, "Connect Trezor Suite to your own node" en "Custom backend in Trezor Suite"
|
||||
- Trezor, "Self-hosted full node via Electrum server in Trezor Suite App"
|
||||
- Documentatie van Sparrow Wallet
|
||||
- Start9 en RaspiBolt, overzichten van wallets die naar een eigen node kunnen wijzen
|
||||
- Coldcard, "Compatible Wallets and Tools"
|
||||
@@ -0,0 +1,98 @@
|
||||
# Images pinnen: de commando's
|
||||
|
||||
Naslag, geen plan. Wanneer dit gedaan wordt en waarom staat in het masterplan **Publicatie**; hier staan
|
||||
alleen de commando's en wat je uit de uitvoer moet halen.
|
||||
|
||||
Alles hieronder draait **op de Umbrel**, want daar staat Docker. Lukt een commando niet, zet er dan `sudo`
|
||||
voor.
|
||||
|
||||
## Waarom het niet één commando is
|
||||
|
||||
De eis is `repo:versie@sha256:<digest>`, en daar zitten drie dingen in die elk apart mis kunnen gaan:
|
||||
|
||||
- **de tag moet specifiek zijn.** `python:3-alpine` beweegt mee met elke nieuwe 3.x en is ook mét digest
|
||||
niet toegestaan. Stap 1 zoekt uit welke versie er nu achter zit;
|
||||
- **de digest moet die van de index zijn**, niet die van één architectuur. Een platform-digest werkt op de
|
||||
machine waar je hem ophaalt en breekt op de andere; een Umbrel Home is arm64 en een zelfbouw meestal
|
||||
amd64;
|
||||
- **beide architecturen moeten erin zitten.** Dat lees je in dezelfde uitvoer af.
|
||||
|
||||
## Stap 1: welke versie zit er achter de bewegende tag
|
||||
|
||||
```bash
|
||||
docker run --rm python:3-alpine python -V
|
||||
```
|
||||
|
||||
```bash
|
||||
docker run --rm nginx:alpine nginx -v
|
||||
```
|
||||
|
||||
De uitvoer noemt bijvoorbeeld `Python 3.13.7` en `nginx version: nginx/1.27.4`. Gebruik daarvan alleen het
|
||||
eerste deel in de tag, dus `python:3.13-alpine` en `nginx:1.27-alpine`, en niet de derde cijfergroep: een
|
||||
patchversie-tag bestaat niet altijd, en de tweede cijfergroep is specifiek genoeg om niet mee te bewegen
|
||||
met een nieuwe hoofdversie.
|
||||
|
||||
## Stap 2: de index-digest van die tag
|
||||
|
||||
```bash
|
||||
docker buildx imagetools inspect python:3.13-alpine
|
||||
```
|
||||
|
||||
```bash
|
||||
docker buildx imagetools inspect nginx:1.27-alpine
|
||||
```
|
||||
|
||||
Twee dingen uit die uitvoer, en let op welke digest je pakt:
|
||||
|
||||
```
|
||||
Name: docker.io/library/python:3.13-alpine
|
||||
MediaType: application/vnd.oci.image.index.v1+json
|
||||
Digest: sha256:AAAA... <- deze, de bovenste
|
||||
Manifests:
|
||||
Name: docker.io/library/python:3.13-alpine@sha256:BBBB...
|
||||
Platform: linux/amd64 <- deze moet erin staan
|
||||
Name: docker.io/library/python:3.13-alpine@sha256:CCCC...
|
||||
Platform: linux/arm64 <- en deze ook
|
||||
```
|
||||
|
||||
De bovenste `Digest:` is de manifest-lijst en die hoort in de compose. De digests bij `Manifests:` zijn per
|
||||
platform; dat is precies de verwisseling die pas op de andere architectuur opvalt.
|
||||
|
||||
## Stap 2b: als `buildx` er niet is
|
||||
|
||||
```bash
|
||||
docker pull python:3.13-alpine
|
||||
```
|
||||
|
||||
```bash
|
||||
docker image inspect --format '{{index .RepoDigests 0}}' python:3.13-alpine
|
||||
```
|
||||
|
||||
Een `docker pull` van een tag met meerdere architecturen zet de index-digest in `RepoDigests`, dus dit
|
||||
levert dezelfde waarde op als stap 2.
|
||||
|
||||
**Maar het bewijst niets over de architecturen, en dat is hier geen theoretisch punt.** De Umbrel van de
|
||||
gebruiker is amd64 (`OS: Linux 6.12.85+deb13-amd64`, uit de nginx-startlog van 20-08-2026). Een `docker pull`
|
||||
daar haalt de amd64-variant en zegt niets over arm64, terwijl arm64 wel een eis is en de helft van de
|
||||
Umbrels erop draait. Gebruik dus stap 2 als het kan, en zie deze terugval als "de digest opzoeken", niet als
|
||||
"de eis controleren".
|
||||
|
||||
Terzijde, uit diezelfde log: achter `nginx:alpine` zat op 20-08-2026 **nginx 1.31.4**. Stap 1 hoeft daarvoor
|
||||
dus niet eens gedraaid te worden zolang de app draait; `docker logs` van de server-container noemt de versie
|
||||
bij elke start.
|
||||
|
||||
## Stap 3: de pin in de repo
|
||||
|
||||
Plak de uitvoer van stap 2 in de sessie. De pin gaat dan in `docker-compose.yml`:
|
||||
|
||||
```yaml
|
||||
image: python:3.13-alpine@sha256:AAAA...
|
||||
```
|
||||
|
||||
Dat is een gewone commit **met een versieverhoging** in `umbrel-app.yml`, want de compose staat in de
|
||||
update-whitelist en wordt dus daadwerkelijk uitgerold.
|
||||
|
||||
## Stap 4: het is onderhoud, geen eenmalige handeling
|
||||
|
||||
Een gepinde image krijgt geen beveiligingsupdates meer tot iemand de pin verhoogt. Deze vier stappen horen
|
||||
daarom bij elke release opnieuw gedaan te worden, niet één keer bij de inlevering.
|
||||
@@ -0,0 +1,528 @@
|
||||
# Umbrel community app store: de spec waar deze repo aan moet voldoen
|
||||
|
||||
Naslag, geen plan. Dit is wat umbrelOS verwacht van een repo die als **community app store** dient, plus
|
||||
het mechanisme waarmee een app van Electrum-backend kan wisselen. Uitgezocht op 18-08-2026 tegen de
|
||||
bronnen die onderaan staan; elk feit hieronder komt uit code of een manifest in die repo's, niet uit een
|
||||
blogpost.
|
||||
|
||||
> **Geldt voor beide apps in deze repo.** Dit document is op 18-08-2026 geschreven toen er één app was, en
|
||||
> op sommige plekken is Electrum Gate nog het voorbeeld. De regels zelf zijn eigenschappen van umbrelOS en
|
||||
> gelden onverkort voor Evolu Relay. De uitzondering is §5, "Wat deze repo nu níet heeft": dat is een
|
||||
> momentopname van 18-08-2026 en gaat alleen over Electrum Gate. Wat er voor de relay ánders ligt, staat in
|
||||
> het masterplan **Umbrelapp** §4.
|
||||
|
||||
## 1. De repo-vorm
|
||||
|
||||
Een community app store is een gewone GitHub-repo met deze vorm:
|
||||
|
||||
```
|
||||
<repo-root>/
|
||||
├── umbrel-app-store.yml # id + name van de store
|
||||
├── <store-id>-<app-id>/ # één map per app
|
||||
│ ├── umbrel-app.yml
|
||||
│ └── docker-compose.yml
|
||||
└── <store-id>-<andere-app>/
|
||||
```
|
||||
|
||||
`umbrel-app-store.yml` heeft precies twee velden:
|
||||
|
||||
```yaml
|
||||
id: whatsnext
|
||||
name: WhatsNext?
|
||||
```
|
||||
|
||||
Het `id` is een **verplichte prefix voor elk app-id in de store**. Een store met `id: whatsnext` die een
|
||||
app `electrum-gate` bevat, heeft dus een map `whatsnext-electrum-gate/` en in het manifest
|
||||
`id: whatsnext-electrum-gate`. Mapnaam en manifest-id moeten gelijk zijn.
|
||||
|
||||
Dat is meteen de reden dat het store-id niet naar één app genoemd moet worden: het zit in het id van
|
||||
elke app die er ooit bij komt, en een app-id wijzigen is voor umbrelOS een andere app. Toen Evolu Relay
|
||||
er op 25-08-2026 bij kwam, hoefde er daarom niets te hernoemen.
|
||||
|
||||
De gebruiker voegt de store toe door de URL van de repo in de umbrelOS-interface te plakken. Er is geen
|
||||
review, geen submissie en geen wachttijd; dat is precies de reden om deze weg te kiezen.
|
||||
|
||||
### De repo hoeft niet op GitHub te staan
|
||||
|
||||
De documentatie van Umbrel praat consequent over GitHub, maar de code doet dat niet. In
|
||||
`app-repository.ts` van umbreld is de enige controle op de URL deze:
|
||||
|
||||
```ts
|
||||
function isValidUrl(url: string) {
|
||||
try {
|
||||
void new URL(url)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Geen hostnaamcontrole, geen `github.com`. Klonen gaat met **isomorphic-git**, een implementatie in
|
||||
JavaScript:
|
||||
|
||||
```ts
|
||||
await git.clone({
|
||||
fs: fse,
|
||||
http,
|
||||
url: this.url,
|
||||
dir: temporaryPath,
|
||||
depth: 1,
|
||||
singleBranch: true,
|
||||
})
|
||||
```
|
||||
|
||||
Een eigen Gitea of Forgejo kan dus de app store zijn. Daar zitten wel drie voorwaarden aan die uit deze
|
||||
aanroep volgen en die niet in de documentatie staan:
|
||||
|
||||
1. **HTTPS, geen SSH.** isomorphic-git spreekt het smart-HTTP-protocol. Een `git@host:pad`-URL werkt niet;
|
||||
het moet `https://host/gebruiker/repo.git` zijn.
|
||||
2. **Anoniem kloonbaar.** Er wordt geen `onAuth` meegegeven, dus umbreld heeft geen manier om
|
||||
inloggegevens aan te bieden. De repo moet publiek leesbaar zijn. Dat is meteen het antwoord op de
|
||||
vraag of een privérepo kan: nee. Lukt het niet, dan meldt umbrelOS `HTTP Error: 401 Unauthorized` bij
|
||||
het toevoegen van de store.
|
||||
|
||||
**Test dit niet met een gewone `git ls-remote`.** Op een machine die ooit naar die repo gepusht heeft,
|
||||
levert een credential-helper de opgeslagen inloggegevens stilzwijgend aan en slaagt de test ten
|
||||
onrechte. Ook met `GIT_TERMINAL_PROMPT=0`, want dat onderdrukt alleen de vráág om een wachtwoord. De
|
||||
test die de toestand van umbreld nabootst:
|
||||
|
||||
```sh
|
||||
GIT_TERMINAL_PROMPT=0 git -c credential.helper= ls-remote https://host/gebruiker/repo.git
|
||||
```
|
||||
|
||||
Slaagt die, dan kan umbreld het ook. Faalt hij met `could not read Username`, dan vraagt de Git-server
|
||||
om een aanmelding. Bij Gitea zijn daar **drie** onafhankelijke oorzaken voor, en ze moeten alle drie
|
||||
goed staan:
|
||||
|
||||
1. **de zichtbaarheid van de repo**, onder Settings;
|
||||
2. **`REQUIRE_SIGNIN_VIEW`** in de serverconfiguratie, die ook publieke repo's achter een aanmelding
|
||||
zet;
|
||||
3. **de zichtbaarheid van het account of de organisatie die eigenaar is**, in te stellen onder Site
|
||||
Administration → User Accounts → Edit → Visibility.
|
||||
|
||||
Die derde is de valstrik, en hij kostte hier de meeste tijd. **Gitea staat niet toe dat een repo
|
||||
zichtbaarder is dan zijn eigenaar.** Staat het account op "limited", dan wordt een repo die je op
|
||||
public zet stilzwijgend teruggezet naar "intern", wat voor een niet-ingelogde bezoeker hetzelfde is
|
||||
als privé. Er komt geen foutmelding; het enige spoor is dat het label na het opslaan op "intern"
|
||||
blijft staan.
|
||||
|
||||
Handig om te weten bij het zoeken: de configuratie van het SynoCommunity-pakket voor Synology heet
|
||||
**`conf.ini`** en niet `app.ini`. Het draaiende proces noemt het echte pad, en dat is sneller dan
|
||||
zoeken:
|
||||
|
||||
```sh
|
||||
ps -ef | grep -i "[g]itea"
|
||||
```
|
||||
3. **Een geldig TLS-certificaat.** Node valideert de keten. Een zelfondertekend certificaat op de
|
||||
Git-server laat het klonen falen.
|
||||
4. **SHA-1 als objectformaat, geen SHA-256.** isomorphic-git berekent object-id's uitsluitend met SHA-1.
|
||||
In `src/utils/shasum.js` staat `import Hash from 'sha.js/sha1.js'` en verder
|
||||
`crypto.subtle.digest('SHA-1', buffer)`; er is geen algoritmeparameter en geen tweede pad. Een repo die
|
||||
met `--object-format=sha256` is aangemaakt, is voor umbreld dus onleesbaar.
|
||||
|
||||
Dit is het opschrijven waard omdat het pas laat zichtbaar wordt. Gitea biedt SHA-256 bij het aanmaken
|
||||
van een repo gewoon als keuze aan, lokaal werkt alles, en de eerste `git push` vanaf een SHA-1-repo
|
||||
faalt met `fatal: the receiving end does not support this repository's hash algorithm`, wat de
|
||||
werkelijke oorzaak niet noemt. Achteraf omzetten kan niet met een instelling: dat is een nieuwe repo
|
||||
plus `git fast-export` naar `git fast-import`. Gevonden op 18-08-2026, bij de eerste push van deze
|
||||
repo.
|
||||
|
||||
Verder is `depth: 1, singleBranch: true` het vermelden waard: alleen de **standaardbranch** wordt
|
||||
opgehaald. De store-inhoud moet daar staan, en een tag of tweede branch doet niets.
|
||||
|
||||
Waar de kloon terechtkomt volgt uit `cleanUrl()`: hostnaam tot de eerste punt, gebruiker en repo uit het
|
||||
pad, plus de eerste acht tekens van de SHA-256 van de URL. Voor
|
||||
`https://sc.kamenier-hamer.nl/sysop/UmbrelApps.git` wordt dat `sysop-umbrelapps-sc-<hash>`. Praktisch
|
||||
gevolg: **de URL is de identiteit van de store.** Verander je hem, dan is het voor umbrelOS een andere
|
||||
store en moet de gebruiker opnieuw toevoegen.
|
||||
|
||||
## 2. Het manifest
|
||||
|
||||
Het schema staat in `packages/umbreld/source/modules/apps/schema.ts` van umbrelOS. Runtime-validatie is
|
||||
daar op dit moment **uitgeschakeld** (`AppManifestSchema.parse` staat uitgecommentarieerd, er wordt een
|
||||
cast gedaan), dus een fout manifest geeft geen nette foutmelding maar vreemd gedrag. Reden te meer om het
|
||||
schema hier op te schrijven.
|
||||
|
||||
Velden die het schema kent, met de type-eis:
|
||||
|
||||
| Veld | Type | Opmerking |
|
||||
|-|-|-|
|
||||
| `manifestVersion` | semver | `1` volstaat; `1.1` is nodig zodra je `hooks/` gebruikt |
|
||||
| `id` | string | gelijk aan de mapnaam, met store-prefix |
|
||||
| `name`, `tagline`, `category`, `version` | string | `version` is vrij tekst, geen semver-eis |
|
||||
| `port` | integer | de **web-UI-poort** die umbrelOS voor de tegel gebruikt, niet je TCP-poort |
|
||||
| `description`, `support` | string | verplicht in het schema |
|
||||
| `website` | URL | moet een geldige URL zijn |
|
||||
| `gallery` | lijst van strings | verplicht in het schema, mag naar externe URL's wijzen |
|
||||
| `icon` | string | optioneel in het schema, maar zonder icoon geen herkenbare tegel |
|
||||
| `dependencies` | lijst van strings | app-id's; zie §4 |
|
||||
| `implements` | lijst van strings | welk app-id deze app kan **vervangen**; zie §4 |
|
||||
| `developer`, `submitter`, `submission`, `repo` | string/URL | optioneel |
|
||||
| `releaseNotes`, `path`, `defaultUsername`, `defaultPassword` | string | optioneel |
|
||||
| `deterministicPassword`, `torOnly`, `optimizedForUmbrelHome`, `disabled` | boolean | optioneel |
|
||||
| `installSize` | integer | in bytes |
|
||||
| `widgets` | lijst | vorm nog niet vastgelegd in het schema |
|
||||
| `backupIgnore` | lijst van strings | paden die niet in de back-up meegaan |
|
||||
| `permissions`, `defaultShell` | | optioneel |
|
||||
|
||||
## 3. De compose
|
||||
|
||||
Twee dingen die de huidige opzet van deze repo níet doet.
|
||||
|
||||
**`app_proxy` in plaats van eigen poorten en netwerken.** umbrelOS genereert zelf een proxy-service; je
|
||||
vult alleen in waar hij heen moet wijzen. De hostnaam is `<app-id>_<compose-service>_1`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app_proxy:
|
||||
environment:
|
||||
# De vorm is: <app-id>_<docker-service-naam>_1
|
||||
APP_HOST: electrumtls-electrum-tls_web_1
|
||||
APP_PORT: 80
|
||||
```
|
||||
|
||||
Een eigen `networks:`-blok op topniveau hoort er niet: de skill in `getumbrel/umbrel-apps` zegt letterlijk
|
||||
dat je `networks: default:` per service alleen gebruikt wanneer je een geteste statische IP of alias nodig
|
||||
hebt. Het `umbrel_main_network` handmatig als extern netwerk aanhaken is de oude 0.5-manier.
|
||||
|
||||
**Images gepind op digest.** De regel uit dezelfde skill: pin elke image als
|
||||
`registry/repo:versie-of-commit@sha256:<digest>`, houd tag en digest samen, en gebruik de **multi-arch
|
||||
index-digest**, niet de architectuurspecifieke. Te controleren met:
|
||||
|
||||
```bash
|
||||
docker buildx imagetools inspect <image>:<tag>
|
||||
```
|
||||
|
||||
Zowel `linux/amd64` als `linux/arm64` moeten erin zitten. Niet toegestaan: `latest`, meebewegende
|
||||
branch-tags, en een digest zonder tag.
|
||||
|
||||
Dit raakt deze app hard: hij draait nu op `alpine:latest` plus een `apk add stunnel` bij élke start. Dat
|
||||
is niet reproduceerbaar, het faalt zonder internet, en het is per definitie niet te pinnen.
|
||||
|
||||
### Hoe bestanden bij de app terechtkomen, en waarom dat bij een update anders gaat
|
||||
|
||||
Dit is niet gedocumenteerd en het is de valkuil met de langste terugverdientijd. Uitgezocht op
|
||||
18-08-2026 in `apps.ts` en het script `legacy-compat/app-script` van umbreld.
|
||||
|
||||
**Bij installatie wordt de hele app-map gekopieerd** naar `${APP_DATA_DIR}`:
|
||||
|
||||
```ts
|
||||
await $`rsync --archive --verbose --exclude ".gitkeep" ${appTemplatePath}/. ${appDataDirectory}`
|
||||
```
|
||||
|
||||
Alles wat in de app-map staat, komt dus mee: scripts, configuratie, een `web/`-map. Een compose die
|
||||
`${APP_DATA_DIR}/entrypoint.sh` mount, werkt daardoor gewoon.
|
||||
|
||||
**Bij een update wordt alleen een whitelist opnieuw gekopieerd.** In `app-script` staat:
|
||||
|
||||
```sh
|
||||
UPDATE_FILES_WHITELIST_PRE="docker-compose.yml *.template exports.sh torrc hooks"
|
||||
UPDATE_FILES_WHITELIST_POST="umbrel-app.yml"
|
||||
```
|
||||
|
||||
Alles daarbuiten wordt bij een update **niet** ververst. Een gewijzigde `entrypoint.sh` of
|
||||
`web/index.html` bereikt een bestaande installatie dus nooit; de gebruiker ziet zijn oude versie en er is
|
||||
geen foutmelding. Alleen een verwijdering en herinstallatie brengt het over.
|
||||
|
||||
**Gevolg voor het ontwerp.** Zet logica die je later nog wilt kunnen wijzigen op een van deze plekken:
|
||||
|
||||
1. **in `docker-compose.yml` zelf**, bijvoorbeeld als een inline `command:`-blok. De compose staat in de
|
||||
whitelist;
|
||||
2. **in de image**, als je er toch een bouwt;
|
||||
3. **in een `*.template`-bestand.** Dit is de nette umbrel-manier en hij doet twee dingen tegelijk: het
|
||||
bestand staat in de whitelist, én `template_app` verwerkt bij elke start elk
|
||||
`${APP_DATA_DIR}/*.template` naar dezelfde naam zonder de extensie, met de omgevingsvariabelen
|
||||
ingevuld. Een `entrypoint.sh.template` wordt dus `entrypoint.sh` mét `${APP_ELECTRS_NODE_IP}` er al in
|
||||
ingevuld.
|
||||
|
||||
Wat je op grond hiervan **niet** moet doen: een los shellscript naast de compose zetten en aannemen dat
|
||||
een `git push` het uitlevert.
|
||||
|
||||
### Wat er in `app-data` staat, en waarom deze app er anders uitziet dan andere
|
||||
|
||||
Nagetrokken op 20-08-2026, nadat de gebruiker opmerkte dat hij bij andere apps geen app-code in de
|
||||
app-map ziet en dat die map daar eerder voor data lijkt te zijn. Dat klopt, en het verschil zit niet in
|
||||
umbrelOS maar in wat een app zelf in zijn map zet.
|
||||
|
||||
**Voor élke app geldt dat de hele app-map naar `app-data` gekopieerd wordt.** Dus ook bij andere apps
|
||||
staan `docker-compose.yml`, `umbrel-app.yml`, `exports.sh` en `hooks/` in
|
||||
`~/umbrel/app-data/<app-id>/`. Wat daar bij hen níet staat is hun programmacode, want die zit in een
|
||||
Docker-image uit een registry. Bij ons staat die er wel: de agent, de nginx-config en de pagina worden uit
|
||||
`app-data` gemount, omdat deze app geen eigen image bouwt.
|
||||
|
||||
**Config-bestanden uit de app-map mounten is een bestaand patroon, ook bij first-party apps.** Uit
|
||||
`electrs/docker-compose.yml`, letterlijk:
|
||||
|
||||
```yaml
|
||||
- ${APP_DATA_DIR}/torrc:/etc/tor/torrc:ro
|
||||
- "${APP_DATA_DIR}/data/electrs:/data"
|
||||
```
|
||||
|
||||
Dat is precies de vorm die deze app ook gebruikt. Het bewijs dat het bedoeld is, staat in de whitelist
|
||||
hieronder: `torrc` en `*.template` staan er met naam in, en `template_app` verwerkt bij elke start
|
||||
`${app_data_dir}/*.template` met **`envsubst`**. Dat laatste is ook de reden achter de architectuurregel in
|
||||
`CLAUDE.md`: `envsubst` vervangt élke `${NAAM}`, ook een die niet bestaat, en die wordt dan leeg.
|
||||
|
||||
**Waar deze app wél van de conventie afwijkt: de data.** Andere apps zetten hun persistente data onder een
|
||||
submap, `${APP_DATA_DIR}/data/...`, zoals in de regels hierboven. Deze app gebruikt
|
||||
`${APP_DATA_DIR}/runtime` en `${APP_DATA_DIR}/certs`, dus naast de code in plaats van eronder. Dat werkt,
|
||||
maar het is niet de vorm die iemand verwacht die andere apps kent.
|
||||
|
||||
Wat de code in `app-data` verder betekent, en dat is de prijs die bewust betaald is:
|
||||
|
||||
- **wijzigen kan alleen via de whitelist.** Vandaar dat hier alles een `*.template` is;
|
||||
- **het gaat mee in de back-up**, want `app-data` is wat umbrelOS bewaart. Voor de code is dat ruis en
|
||||
voor `runtime/` ook; `certs/` hoort er juist wél in. Het manifest heeft een veld `backupIgnore` om dat
|
||||
te sturen, en dat wordt hier nog niet gebruikt;
|
||||
- **er is geen bouwstap en geen registry**, en geen `apk add` bij het starten. Dat was de reden om het zo
|
||||
te doen, en die staat in het plan **Appstore**, fase 4.
|
||||
|
||||
Bronnen: [`electrs/docker-compose.yml`](https://raw.githubusercontent.com/getumbrel/umbrel-apps/master/electrs/docker-compose.yml),
|
||||
[`mempool/docker-compose.yml`](https://raw.githubusercontent.com/getumbrel/umbrel-apps/master/mempool/docker-compose.yml),
|
||||
[`app-script`](https://raw.githubusercontent.com/getumbrel/umbrel/master/packages/umbreld/source/modules/apps/legacy-compat/app-script).
|
||||
|
||||
### De inlevereisen van de officiële appstore
|
||||
|
||||
Opgehaald op 20-08-2026 toen de gebruiker zei dat hij de app uiteindelijk als standaard-app wil
|
||||
publiceren. De eisen staan niet in de README van `umbrel-apps` maar in de skill-documentatie die daar naar
|
||||
verwijst:
|
||||
[`.claude/skills/umbrel-package-app/SKILL.md`](https://raw.githubusercontent.com/getumbrel/umbrel-apps/master/.claude/skills/umbrel-package-app/SKILL.md).
|
||||
Wat daar staat en wat deze app ervan doet:
|
||||
|
||||
| Eis | Deze app |
|
||||
|-|-|
|
||||
| Elke image gepind als `repo:versie@sha256:<digest>`, met `linux/amd64` én `linux/arm64` in de manifest-lijst. Verboden: `latest`, meebewegende branch-tags, een digest zonder tag | **Nog niet.** `python:3-alpine` en `nginx:alpine` staan er kaal in. Dit is de grootste openstaande eis en hij kan alleen op de Umbrel zelf, met `docker buildx imagetools inspect` |
|
||||
| Mapnaam gelijk aan het app-id, lowercase kebab-case | Klopt, maar het id heeft nu het store-voorvoegsel `whatsnext-`. Dat is een eis van een **community** store; officiële apps hebben een kaal id, dus dit wordt `electrum-gate` |
|
||||
| Manifestvelden in een vaste volgorde: `manifestVersion`, `id`, `category`, `name`, `version`, `tagline`, `description`, `releaseNotes`, `developer`, `website`, `dependencies`, `repo`, `support`, `port`, `gallery`, `path`, en daarna de optionele | Klopt sinds 20-08-2026, met een toets erop. Wat de spec niet noemt (`icon`, `backupIgnore`) staat áchter die reeks, dus de kop is letterlijk goed |
|
||||
| `gallery: []` bij een nieuw pakket; het store-team doet de plaatjes | Klopt al. Zie de paragraaf hieronder: de inhoud van dit veld moet bij inlevering leeg zijn, het veld zelf blijft staan |
|
||||
| `icon` weglaten bij inlevering; iconen worden apart gehost | Nu wél gevuld, en dat moet ook zolang dit een eigen store is. Staat daarom als **laatste** regel van het manifest: bij inlevering is dat de enige die weg hoeft |
|
||||
| `app_proxy` met alleen omgevingsvariabelen, geen eigen `ports:` | Klopt. De `ports:` van 50022 staat op de server-service, en dat is normaal voor een niet-web-poort |
|
||||
| Umbrel-inlog aan laten staan; `PROXY_AUTH_WHITELIST` alleen smal en voor paden die geen cookie kunnen sturen | Klopt: er is geen whitelist, dus ook `/api/` zit achter de inlog. Dat is de onderbouwing onder het uploadpad |
|
||||
| Alle gebruikersstaat, config, uploads en geheimen onder `${APP_DATA_DIR}/data/...`, met een `.gitkeep` per map die bij de eerste start moet bestaan | Klopt sinds 0.0.9 |
|
||||
| Niet buiten `${APP_DATA_DIR}` schrijven | Klopt. Wel **lezen** buiten: `${UMBREL_ROOT}/app-data/zoraxy/...` staat alleen-lezen gemount, en dat is het meest ongebruikelijke aan deze app. Reken op een vraag daarover bij de review |
|
||||
|
||||
### Iconen en de drie promo-afbeeldingen
|
||||
|
||||
Uitgezocht op 20-08-2026, nadat de gebruiker opmerkte dat de afbeeldingen bovenaan een app in de store er
|
||||
allemaal hetzelfde uitzien: een achtergrond met een plaatje van de app erop. Dat klopt, en de reden is dat
|
||||
**het store-team ze zelf maakt**. Ze staan dan ook niet in de app-repo maar in
|
||||
[`umbrel-apps-gallery`](https://github.com/getumbrel/umbrel-apps-gallery).
|
||||
|
||||
**In de officiële store zijn het kale bestandsnamen.** Uit `electrs/umbrel-app.yml`:
|
||||
|
||||
```yaml
|
||||
gallery:
|
||||
- 1.jpg
|
||||
- 2.jpg
|
||||
- 3.jpg
|
||||
- 4.jpg
|
||||
```
|
||||
|
||||
Wat er van een inzender gevraagd wordt, uit de inleverdraden: **1440 bij 900 pixels, PNG, drie tot vijf
|
||||
stuks**, of drie tot vijf gewone schermafbeeldingen waarna het team de opmaak doet. Gecombineerd met de
|
||||
regel uit de packaging-documentatie ("set `gallery: []` for new packages") is de praktische route dus:
|
||||
schermafbeeldingen aanleveren, veld leeg laten. Het veld zelf blijft op zijn plek in de volgorde staan;
|
||||
alleen de inhoud is leeg.
|
||||
|
||||
**In een eigen store zijn het absolute URL's**, net als het icoon. Uit het voorbeeld in de
|
||||
community-store-template:
|
||||
|
||||
```yaml
|
||||
icon: https://svgur.com/i/mvA.svg
|
||||
gallery:
|
||||
- https://i.imgur.com/yyVG0Jb.jpeg
|
||||
- https://i.imgur.com/yyVG0Jb.jpeg
|
||||
- https://i.imgur.com/yyVG0Jb.jpeg
|
||||
```
|
||||
|
||||
Voor deze store kan dat dus met dezelfde truc als `icon` nu doet: de bestanden in de repo zetten en er met
|
||||
hun raw-URL naar wijzen.
|
||||
|
||||
**Eén waarschuwing die niet over formaten gaat.** Een schermafbeelding van de certificaatkeuze op deze
|
||||
machine toont veertien hostnamen van de gebruiker, en een van de verbindingsregels toont zijn domein. Voor
|
||||
een publieke winkelpagina horen daar plaatsvervangende namen in. Dat is een andere afweging dan de
|
||||
certificate-transparency-logs die in de app-beschrijving staan: die zijn per certificaat op te zoeken, een
|
||||
winkelpagina zet de hele lijst bij elkaar.
|
||||
|
||||
Bronnen: [`electrs/umbrel-app.yml`](https://raw.githubusercontent.com/getumbrel/umbrel-apps/master/electrs/umbrel-app.yml),
|
||||
[voorbeeld uit de community-store-template](https://raw.githubusercontent.com/getumbrel/umbrel-community-app-store/master/sparkles-hello-world/umbrel-app.yml),
|
||||
[`umbrel-apps-gallery`](https://github.com/getumbrel/umbrel-apps-gallery).
|
||||
|
||||
### Hoe umbrelOS een update ziet
|
||||
|
||||
Via het veld **`version`** in `umbrel-app.yml`. umbreld haalt de repo periodiek op en vergelijkt de
|
||||
versie in de store met die van het geïnstalleerde manifest in `${APP_DATA_DIR}/umbrel-app.yml`. Dat
|
||||
verklaart ook waarom `umbrel-app.yml` als **laatste** wordt gekopieerd bij een update
|
||||
(`UPDATE_FILES_WHITELIST_POST`): het geïnstalleerde manifest is de administratie van wat er staat, en dat
|
||||
mag pas bijgewerkt worden als de rest binnen is.
|
||||
|
||||
**Hoe die vergelijking werkt, voor zover het uitmaakt: `0.0.10` na `0.0.9` levert een update op.** Op
|
||||
20-08-2026 gecontroleerd op de Umbrel van de gebruiker. Dat sluit een tekstvergelijking met groter-dan uit,
|
||||
want daarin is `0.0.10` kleiner dan `0.0.9`. Het is dus een gelijkheids- of semver-vergelijking, en over
|
||||
tweecijferige versiedelen hoef je je geen zorgen te maken. Over een **omlaaggaand** nummer nog steeds wel:
|
||||
dat blijft ongetoetst.
|
||||
|
||||
**Praktische regel die hieruit volgt: een wijziging zonder versieverhoging wordt nooit uitgerold.** Er
|
||||
komt geen melding en geen fout; umbrelOS ziet simpelweg hetzelfde nummer en doet niets. Bij elke
|
||||
functionele wijziging hoort dus een nieuwe `version`, ook bij een kleine reparatie in de compose of in
|
||||
een template.
|
||||
|
||||
### Beschikbare omgevingsvariabelen
|
||||
|
||||
| Variabele | Betekenis |
|
||||
|-|-|
|
||||
| `APP_ID` | app-id uit manifest en mapnaam |
|
||||
| `APP_VERSION` | het `version`-veld |
|
||||
| `APP_DATA_DIR` | `${UMBREL_ROOT}/app-data/<app-id>` |
|
||||
| `APP_MANIFEST_FILE` | pad naar het geïnstalleerde `umbrel-app.yml` |
|
||||
| `UMBREL_ROOT` | de Umbrel-datawortel op de host |
|
||||
| `DEVICE_HOSTNAME` | apparaatnaam zonder `.local` |
|
||||
| `DEVICE_DOMAIN_NAME` | het `.local`-domein van het apparaat |
|
||||
| `APP_DOMAIN` | het lokale `.local`-domein voor deze app |
|
||||
| `APP_PROXY_HOSTNAME`, `APP_PROXY_PORT` | hostnaam en poort van de gegenereerde proxy |
|
||||
| `NETWORK_IP` | basis-IP van het Umbrel-dockernetwerk, met `/16` voor het subnet |
|
||||
| `TOR_PROXY_IP`, `TOR_PROXY_PORT` | de SOCKS-proxy van Umbrel |
|
||||
| `TOR_DATA_DIR` | Tor-datamap op de host |
|
||||
| `APP_HIDDEN_SERVICE` | het onion-adres van de app |
|
||||
| `APP_SEED`, `APP_PASSWORD` | deterministisch per installatie afgeleide geheimen |
|
||||
|
||||
Daarnaast krijgt een app de exports van zijn **afhankelijkheden** (§4).
|
||||
|
||||
## 4. Wisselen tussen Electrs, Fulcrum en ElectrumX
|
||||
|
||||
Dit is het antwoord op de vraag "moet de gebruiker kunnen kiezen tussen Electrs en Fulcrum": umbrelOS
|
||||
1.3 doet dat al, en een app hoeft er niets voor te bouwen.
|
||||
|
||||
**Het mechanisme.** Een app declareert een afhankelijkheid op een app-id, en een andere app kan zeggen
|
||||
dat hij die rol vervult:
|
||||
|
||||
```yaml
|
||||
# fulcrum/umbrel-app.yml en electrumx/umbrel-app.yml
|
||||
implements:
|
||||
- electrs
|
||||
```
|
||||
|
||||
In `AppSettingsSchema` staat `dependencies: z.record(z.string())`: per app wordt bewaard welke
|
||||
implementatie de gebruiker voor welke afhankelijkheid gekozen heeft. umbrelOS laadt vervolgens de
|
||||
`exports.sh` van de **gekozen** app.
|
||||
|
||||
**Waarom dat werkt zonder aanpassing aan de afnemer.** De vervangers aliassen zichzelf naar de
|
||||
Electrs-namen. Uit `fulcrum/exports.sh`:
|
||||
|
||||
```sh
|
||||
export APP_FULCRUM_IP="10.21.22.200"
|
||||
export APP_FULCRUM_NODE_IP="10.21.21.200"
|
||||
export APP_FULCRUM_NODE_PORT="50002"
|
||||
|
||||
for var in IP NODE_IP NODE_PORT; do
|
||||
electrs_var="APP_ELECTRS_${var}"
|
||||
fulcrum_var="APP_FULCRUM_${var}"
|
||||
...
|
||||
export "$electrs_var"="${!electrs_var:=${!fulcrum_var}}"
|
||||
done
|
||||
```
|
||||
|
||||
`electrumx/exports.sh` doet exact hetzelfde met `APP_ELECTRUMX_*`. Ter vergelijking, `electrs/exports.sh`:
|
||||
|
||||
```sh
|
||||
export APP_ELECTRS_IP="10.21.22.4"
|
||||
export APP_ELECTRS_NODE_IP="10.21.21.10"
|
||||
export APP_ELECTRS_NODE_PORT="50001"
|
||||
```
|
||||
|
||||
**Wat dat voor deze app betekent, in twee regels:**
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
- electrs
|
||||
```
|
||||
|
||||
en in de compose `${APP_ELECTRS_NODE_IP}:${APP_ELECTRS_NODE_PORT}` gebruiken in plaats van een
|
||||
hardgecodeerde containernaam. Daarmee werkt de app met alle drie de backends en kiest de gebruiker in de
|
||||
umbrelOS-instellingen.
|
||||
|
||||
**Let op: alleen `IP`, `NODE_IP` en `NODE_PORT` worden gealiast.** Alles wat Electrs-specifiek is, zoals
|
||||
`APP_ELECTRS_RPC_HIDDEN_SERVICE`, bestaat niet bij Fulcrum. Gebruik die dus niet.
|
||||
|
||||
### Het keuzedialoog bij de installatie, en wat er niet mee kan
|
||||
|
||||
Nagetrokken op 20-08-2026 in de bron, nadat de gebruiker meldde dat hij bij sommige apps twee dropdowns
|
||||
krijgt en vroeg of "Zoraxy of Nginx Proxy Manager" ook zo kan. Dat dialoog bestaat, maar het werkt anders
|
||||
dan het lijkt, en het verschil is precies wat de vraag beantwoordt.
|
||||
|
||||
**Eén dropdown per afhankelijkheid, niet per keuze.** In het schema van umbreld staat:
|
||||
|
||||
```ts
|
||||
dependencies: z.array(z.string()).optional(),
|
||||
implements: z.array(z.string()).optional(),
|
||||
```
|
||||
|
||||
Een afhankelijkheid is dus één app-id en kan zelf géén lijst met alternatieven zijn; een geneste lijst
|
||||
bestaat niet in dit schema. Wat er in zo'n dropdown staat, komt van de andere kant: elke app die
|
||||
`implements: [<dat id>]` declareert, verschijnt erin. Twee dropdowns betekent dus twee afhankelijkheden.
|
||||
Het voorbeeld dat de gebruiker zag is `mempool`:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
- bitcoin
|
||||
- electrs
|
||||
```
|
||||
|
||||
Dat geeft twee dropdowns, en de inhoud van de tweede is precies waar §4 hierboven over gaat: Electrs,
|
||||
Fulcrum en ElectrumX declareren allemaal `implements: [electrs]`.
|
||||
|
||||
**Gevolg voor "Zoraxy of NPM": dat kan niet.** Nagekeken in beide manifesten in de officiële appstore:
|
||||
`zoraxy` en `nginx-proxy-manager` declareren **geen** `implements`, dus er is geen gedeelde rol waarop een
|
||||
afhankelijkheid kan wijzen. Er is ook geen weg omheen aan onze kant: `implements` staat in hún manifest en
|
||||
niet in het onze. Wat wél kan is één van de twee hard eisen (`dependencies: [electrs, zoraxy]`), en dat is
|
||||
dan een dropdown met één optie erin.
|
||||
|
||||
Bronnen: [`schema.ts`](https://raw.githubusercontent.com/getumbrel/umbrel/master/packages/umbreld/source/modules/apps/schema.ts),
|
||||
[`mempool/umbrel-app.yml`](https://raw.githubusercontent.com/getumbrel/umbrel-apps/master/mempool/umbrel-app.yml),
|
||||
[`zoraxy/umbrel-app.yml`](https://raw.githubusercontent.com/getumbrel/umbrel-apps/master/zoraxy/umbrel-app.yml),
|
||||
[`nginx-proxy-manager/umbrel-app.yml`](https://raw.githubusercontent.com/getumbrel/umbrel-apps/master/nginx-proxy-manager/umbrel-app.yml).
|
||||
|
||||
### Poortbotsing: 50002
|
||||
|
||||
De poorten die de drie backends op de **host** publiceren:
|
||||
|
||||
| App | Host-poort |
|
||||
|-|-|
|
||||
| Electrs | 50001 |
|
||||
| Fulcrum | 50002 |
|
||||
| ElectrumX | 50001 intern, 50003 als publieke poort |
|
||||
|
||||
Deze app publiceert nu zelf 50002 voor stunnel. Met Fulcrum geïnstalleerd botst dat en start de app niet.
|
||||
De keuze hierover staat als open punt in het plan **Configuratie**.
|
||||
|
||||
## 5. Wat deze repo nu níet heeft
|
||||
|
||||
Puntsgewijs, zodat het plan **Appstore** hier direct op kan leunen:
|
||||
|
||||
1. geen `umbrel-app-store.yml`;
|
||||
2. geen app-submap, alles ligt in de repo-root;
|
||||
3. app-id `electrum-tls` mist de store-prefix;
|
||||
4. geen `app_proxy`-service; wel handmatige host-poorten en een extern `umbrel_main_network`;
|
||||
5. images op `latest`, stunnel via `apk add` bij elke start;
|
||||
6. `dependencies: [electrs]` staat er wel, maar de compose gebruikt `${APP_ELECTRS_IP}` als host terwijl
|
||||
`${APP_ELECTRS_NODE_IP}` bedoeld is: `APP_ELECTRS_IP` is de web-UI-container van Electrs, niet de
|
||||
Electrum-server;
|
||||
7. `icon` en `gallery` wijzen naar de assets van de Electrs-app in `getumbrel/umbrel-apps`, dus naar
|
||||
andermans bestanden en naar een plaatje van een andere app;
|
||||
8. `port: 50002` staat op de TCP-poort terwijl dat veld de web-UI-poort van de tegel is;
|
||||
9. `submitter` en `submission` verwijzen naar `getumbrel/umbrel-apps`, wat voor een community store niet
|
||||
klopt;
|
||||
10. `install.sh` en `uninstall.sh` kopiëren naar `/home/umbrel/umbrel/apps/`, wat onder umbrelOS 1.x door
|
||||
`umbreld` beheerd wordt.
|
||||
|
||||
## 6. Bronnen
|
||||
|
||||
Alles hierboven komt uit deze bestanden, geraadpleegd op 18-08-2026:
|
||||
|
||||
- `getumbrel/umbrel-community-app-store`, README en de voorbeeld-app `sparkles-hello-world/`
|
||||
- `getumbrel/umbrel-apps`, `AGENTS.md` en `.claude/skills/umbrel-package-app/SKILL.md`
|
||||
- `getumbrel/umbrel-apps`, de mappen `electrs/`, `fulcrum/` en `electrumx/`: manifest, compose en
|
||||
`exports.sh`
|
||||
- `getumbrel/umbrel`, `packages/umbreld/source/modules/apps/schema.ts` en `app-repository.ts`
|
||||
- umbrelOS 1.3 release notes over swappable dependencies
|
||||
@@ -0,0 +1,72 @@
|
||||
# Upstream: `trezor/trezor-suite-sync` en Evolu Relay
|
||||
|
||||
Naslag, geen plan. Wat er over de bovenstroomse repo bekend is, plus per feit hoe hard het is. Dat
|
||||
onderscheid staat er expres in: **alles hieronder komt uit het vooronderzoek van 25-08-2026 en is in deze
|
||||
repo nog niet nagetrokken tegen de bron.** Het plan **Proefopstelling** doet precies dat, en de eerste
|
||||
sessie die daar iets van bevestigt of weerlegt, werkt dit document bij met de datum erbij.
|
||||
|
||||
## 1. Wat het is
|
||||
|
||||
Trezor Suite kan labels en accountnamen synchroniseren tussen apparaten. De server die dat doet heet in de
|
||||
interface "custom sync server"; het onderdeel zelf heet **Evolu Relay** en staat in de repo
|
||||
`trezor/trezor-suite-sync`.
|
||||
|
||||
Evolu is een local-first synchronisatielaag: de client houdt zijn eigen kopie bij en de relay is een
|
||||
doorgeefluik voor versleutelde wijzigingen. Dat is ook waarom zelf hosten hier kán zonder dat je iets aan
|
||||
de beveiliging opgeeft: volgens Trezor is de data client-side end-to-end versleuteld, dus de relay ziet
|
||||
niets leesbaars. **Zelf hosten haalt Trezor uit de vergelijking, het verandert de privacygaranties niet.**
|
||||
|
||||
## 2. Wat de repo bevat
|
||||
|
||||
| Onderdeel | Poort | Waarvoor | Hardheid |
|
||||
|-|-|-|-|
|
||||
| `evolu-relay` | 4000 | de eigenlijke sync-relay, dit is wat we nodig hebben | onderzocht 25-08-2026 |
|
||||
| `quota-manager` | 4001 | quota- en betaalserver, noemt een "Payment Server" en een Notion API-spec | onderzocht 25-08-2026 |
|
||||
| Postgres | standaard | opslag achter de relay | onderzocht 25-08-2026 |
|
||||
| `Dockerfile` en `docker-compose.yaml` | | Trezor containeriseert zelf al | onderzocht 25-08-2026 |
|
||||
| `.k8s/` | | Kubernetes-configuratie, voor Umbrel niet relevant | onderzocht 25-08-2026 |
|
||||
|
||||
Laatste release genoemd in het vooronderzoek: **v0.1.8, april 2026**. Dat maakt het een jong en actief
|
||||
project, en dat is een risico dat verder gaat dan een versienummer: de architectuur kan nog schuiven.
|
||||
|
||||
## 3. De vraag die alles bepaalt
|
||||
|
||||
**Is de quota-manager verplicht?** De naam en de beschrijving wijzen op Trezor's eigen gehoste,
|
||||
quota-gebaseerde dienst, en voor privégebruik op één Umbrel is dat niet iets wat je wilt draaien. Maar
|
||||
"bedoeld voor" is geen "optioneel": als de relay bij het starten een verbinding met de quota-manager
|
||||
verwacht, moet hij mee in het pakket.
|
||||
|
||||
Het verschil in uitkomst is groot genoeg om het als eerste te beantwoorden:
|
||||
|
||||
- **niet verplicht** → een pakket van twee containers, relay plus Postgres;
|
||||
- **wel verplicht** → drie containers, plus de vraag wat de quota-manager zelf nodig heeft. Als dat een
|
||||
Notion-sleutel of een betaalprovider is, is dat geen pakketteerprobleem meer maar een blokkade.
|
||||
|
||||
Waar je het antwoord vindt zonder te draaien: `.env.sample`, de compose van Trezor zelf, en de plek in de
|
||||
broncode van de relay waar de quota-manager wordt aangeroepen. Waar je het antwoord bewijst: hem starten
|
||||
zonder.
|
||||
|
||||
## 4. Wat er nog helemaal niet uitgezocht is
|
||||
|
||||
Deze vier staan hier omdat ze het pakket bepalen en omdat het vooronderzoek er niets over zegt. Ze zijn
|
||||
geen taak (dat zijn ze in **Proefopstelling** en **Umbrelapp**), maar een lezer moet niet denken dat de
|
||||
tabel hierboven het hele plaatje is.
|
||||
|
||||
1. **Publiceert Trezor een image in een registry, of is er alleen een Dockerfile?** Dit is de vraag met de
|
||||
grootste gevolgen na de quota-manager. Umbrel wil een image gepind op
|
||||
`repo:versie@sha256:<digest>`, met `linux/amd64` én `linux/arm64` erin. Alleen een Dockerfile betekent
|
||||
dat je zelf bouwt en zelf publiceert, en dan ben je onderhouder van een image geworden.
|
||||
2. **Draait het op arm64?** De helft van de Umbrels is een Raspberry Pi. Een image die alleen amd64 kent,
|
||||
valt daar om, en dat merk je pas op het apparaat.
|
||||
3. **Hoe authenticeert Trezor Suite zich tegen de relay, en over welk protocol praat het?** Dit bepaalt of
|
||||
de app achter de inlog van umbrelOS kan staan. Zie het masterplan **Umbrelapp**, §4b.
|
||||
4. **Accepteert Trezor Suite een `http://`-adres, of eist het TLS?** Daar hangt het masterplan
|
||||
**Bereikbaarheid** aan.
|
||||
|
||||
## 5. Bronnen
|
||||
|
||||
Het vooronderzoek van 25-08-2026 staat ongewijzigd in
|
||||
[Vooronderzoek.PLAN.md](../Plannen/Masterplannen/Archief/Vooronderzoek.PLAN.md); daar is elke regel
|
||||
hierboven vandaan gekomen. De onderliggende bronnen (de repo van Trezor zelf, de release-pagina) zijn nog
|
||||
niet opnieuw geraadpleegd; zet de URL erbij zodra dat gebeurt, zoals de andere referenties in deze map dat
|
||||
doen.
|
||||
@@ -0,0 +1,73 @@
|
||||
# Bestaat dit al voor Umbrel?
|
||||
|
||||
Uitgezocht op 20-08-2026 op verzoek van de gebruiker, met het oog op inlevering in de officiële appstore.
|
||||
De korte versie: **nee, en de reden dat het niet bestaat is opvallend concreet.**
|
||||
|
||||
## Wat er in de officiële store staat en in de buurt komt
|
||||
|
||||
Uit de categorie Networking: **Nginx Proxy Manager** en **OpenResty Manager** ("Expose your services easily
|
||||
and securely"), **Cloudflare Tunnel**, **NetBird**, en verder Pi-hole, AdGuard en dat soort dingen.
|
||||
**Zoraxy** staat er ook (deze app leest zijn certificaatmap). Dat zijn allemaal reverse proxies voor HTTP,
|
||||
of tunnels.
|
||||
|
||||
Geen van die apps doet wat deze app doet: **TLS termineren op een gewone TCP-poort** en het verkeer plat
|
||||
doorzetten naar de Electrum-server.
|
||||
|
||||
## Het beslissende feit
|
||||
|
||||
Nginx Proxy Manager is de populairste van dat rijtje en zit onder de motorkap op precies dezelfde techniek
|
||||
als deze app, namelijk het `stream`-blok van nginx. **Maar zijn beheerinterface kan er geen certificaat op
|
||||
zetten.** Daar staat een openstaand verzoek voor, en de werkwijze die mensen ondertussen gebruiken is met de
|
||||
hand een bestand als `/data/nginx/stream/6.conf` bijwerken, wat de interface bij de volgende wijziging
|
||||
overschrijft.
|
||||
|
||||
De onderliggende nginx kán het wel; dat is precies wat deze app doet, en het is ook waarom de app zo klein
|
||||
kon blijven. Wat hij toevoegt is niet de techniek maar het beheer eromheen: certificaten vinden, weigeren
|
||||
bij twijfel, een keuze onthouden, en herladen bij een vernieuwing zonder verbindingen te verbreken.
|
||||
|
||||
## Dat de vraag bestaat, is ook zichtbaar
|
||||
|
||||
Op het forum van Umbrel staan meerdere draden die letterlijk hierom vragen: "On Umbrel Home, how to enable
|
||||
SSL for Electrs?", "Can't connect to Electrs over Clearnet/HTTPS" en "How can I turn on https?". De
|
||||
antwoorden daarin zijn handwerk: een reverse proxy ervoor zetten, of een VPS met een WireGuard-tunnel.
|
||||
|
||||
## De vergelijking met een tunnel of VPN, en waarom die juist vóór deze app pleit
|
||||
|
||||
Wie dit probleem heeft, kan ook een tunnel of een VPN nemen: **Cloudflare Tunnel**, **NetBird**, Tailscale,
|
||||
WireGuard. Reken erop dat een reviewer daarnaar vraagt.
|
||||
|
||||
Hier stond eerst dat dat een afweging is. **Dat was te vriendelijk voor die alternatieven**, en de gebruiker
|
||||
wees daar op 20-08-2026 op: het vermijden van dat soort producten is bij hem juist de aanleiding voor deze
|
||||
opstelling. Dat is een sterker en preciezer argument, en het is ook feitelijk:
|
||||
|
||||
- **een tunneldienst zit in het pad.** Wie zijn verkeer door zo'n dienst laat lopen, laat het daar
|
||||
termineren; dat is niet een bijwerking maar de manier waarop het werkt;
|
||||
- **een mesh-VPN vraagt een account en een coördinatieserver**, en een client op elk apparaat dat je
|
||||
gebruikt. Op een telefoon betekent dat vaak dat álle verkeer die kant op gaat;
|
||||
- **deze app vraagt geen van beide.** Je eigen certificaat, je eigen domein, je eigen poort, en aan de
|
||||
andere kant een wallet die TLS al spreekt. Dat is precies de lijst in de app: Trezor Suite, Sparrow,
|
||||
BlueWallet, Nunchuk, Blockstream, BitBoxApp;
|
||||
- wat het wél vraagt is één doorgestuurde poort in de router, en dat staat zo in de beschrijving.
|
||||
|
||||
Tor blijft de privacyvriendelijkere weg en staat als zodanig in de beschrijving van de app zelf. Deze app is
|
||||
de andere kant van díe afweging en doet niet alsof hij die niet heeft.
|
||||
|
||||
**In het Engels, voor hergebruik in de winkeltekst en de PR** (staat sinds 0.0.11 ook in `description`):
|
||||
|
||||
> Nothing in the middle. The connection runs from your wallet straight to your own node, encrypted with a
|
||||
> certificate you already own, for a domain you already control. There is no account to create, no tunnel
|
||||
> service that terminates your traffic along the way, and no client to install on every device you use: the
|
||||
> wallets below already speak TLS, they only need an address. What it does ask of you is one forwarded port
|
||||
> on your router.
|
||||
|
||||
## Wat dit betekent voor de inlevering
|
||||
|
||||
De niche is echt en scherp te omschrijven, en dat hoort in de PR-tekst: *de reverse proxies in deze store
|
||||
kunnen certificaten wel op HTTP zetten, maar niet op een TCP-poort, en een Electrum-wallet praat geen HTTP.*
|
||||
|
||||
Bronnen:
|
||||
[Networking-categorie van de appstore](https://apps.umbrel.com/category/networking),
|
||||
[NPM-verzoek om stream-SSL-terminatie](https://github.com/NginxProxyManager/nginx-proxy-manager/issues/2542),
|
||||
[nginx over SSL-terminatie voor TCP](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-tcp/),
|
||||
[forumdraad over SSL voor Electrs](https://community.umbrel.com/t/on-umbrel-home-how-to-enable-ssl-for-electrs/24909),
|
||||
[forumdraad over Electrs via clearnet](https://community.umbrel.com/t/cant-connect-to-electrs-over-clearnet-https/13579).
|
||||
Reference in New Issue
Block a user