Files
UmbrelApps/Docs/Referenties/Umbrel-appstore-spec.md
T
HarmenandClaude Fable 5.1 973b24a23f Gate 0.1.0: eigen image, het command-blok is een script, de app-map is leeg
Plan Eigenimage, fase 1 tot en met 3. De vier templates verhuizen naar
tools/electrum-gate/ zonder extensie; daarnaast Dockerfile (nginx:1.30-alpine
plus python3), entrypoint.sh (het command-blok van de compose, zonder $$) en
build.sh naar het voorbeeld van Evolu Relay. Een image voor beide containers,
gebouwd op de Umbrel; open punt 2 en 3 daarmee beslist.

Inhoudelijk anders dan alleen verplaatst: het log_format staat in stream.conf
zelf, het backend-adres komt via twee plaatshouders zonder dollarteken uit de
omgeving (ook in de server-service), en de pagina haalt versie en adres uit
status.json via GATE_APP_VERSION.

Tests mee verhuisd en uitgebreid: entrypoint.sh en Dockerfile in plaats van het
command-blok, en de tag in de compose gelijk aan VERSION in build.sh voor elke
eigen image. Mutatie-getest met drie ingrepen.

Nog niet gebouwd: er is hier geen Docker. De tag staat ongepind tot de eerste
push; dat is fase 4 en die is van de gebruiker.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 20:20:42 +02:00

31 KiB

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:

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:

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:

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:

    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:

    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:

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.

Elke app krijgt een Tor hidden service, en dat is de tweede weg naar binnen

Vastgesteld op 25-08-2026 bij de eerste installatie van Evolu Relay, nadat de gebruiker vroeg of die app kwaad kon. umbrelOS start per app een <app-id>-tor_server-1-container en publiceert de app als hidden service. Dat staat niet in de compose van de app en je ziet het dus niet als je die leest. Controleren:

cat ~/umbrel/tor/data/app-<app-id>/hostname

Bestaat dat bestand, dan is er een .onion-adres en is de app vanaf het internet bereikbaar. Uit te zetten per app in de umbrelOS-interface.

Of dat erg is, hangt volledig af van PROXY_AUTH_ADD, en dat maakt het een combinatie die je apart moet beoordelen in plaats van per instelling:

App Inlog op de proxy Wat de hidden service dan blootlegt
Electrum Gate aan (standaard) de inlogpagina van umbrelOS. Het dashboard zelf zit erachter. Zijn TLS-poort 50022 loopt niet via de proxy en zit dus niet op het onion-adres
Evolu Relay uit ("false", want Trezor Suite kan niet inloggen) de relay zelf, zonder enige aanmelding. Alleen beschermd doordat het adres onraadbaar is

De regel die daaruit volgt: zet je PROXY_AUTH_ADD op "false", beoordeel dan meteen de hidden service. Die twee samen maken van "alleen mijn thuisnetwerk" ongemerkt "het internet". Ook bij een app mét inlog is uitzetten verdedigbaar, want dan staat er tenminste geen aanmeldscherm op het internet dat er niet hoefde te staan.

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:

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}:

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:

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.

Elke image moet uit een register komen, en pull_policy helpt niet

Op het apparaat vastgesteld op 25-08-2026, bij de eerste installatiepoging van Evolu Relay met een image die alleen lokaal gebouwd was. De installatie faalde met:

Error: (HTTP code 404) unexpected - pull access denied for whatsnext/evolu-relay,
repository does not exist or may require 'docker login'
    at /opt/umbreld/node_modules/docker-modem/lib/modem.js:382:17

Het beslissende detail is die stacktrace, niet de foutmelding. De pull komt uit docker-modem, de Docker-clientbibliotheek van umbreld zelf, en dus rechtstreeks van de Docker Engine API. Compose komt er niet aan te pas. Dat is ook te zien aan de regels Downloaded 40.5% of app in de journal: umbreld haalt de images op en rapporteert de voortgang zelf.

Twee gevolgen, en het tweede is een valkuil die een halve middag kost:

  1. Elke image: in de compose van een app moet anoniem uit een register te halen zijn. Een tag die alleen in de lokale Docker-opslag van het apparaat staat, werkt niet, ook al zou docker compose up hem daar prima vinden. De installatie stopt vóór het starten.
  2. pull_policy: never verandert daar niets aan, want dat is een sleutel van de Compose-specificatie en umbreld leest de compose niet om te pullen; het leest alleen welke images erin staan. Dit is hier geprobeerd en het faalde identiek.

Wat hier eerder stond, en waarom dat misleidde: in app-script staat compose "${app}" pull bij install, update en post-patch-update, en up --detach --build bij het starten. Dat bestand is de legacy-compat-laag; op umbrelOS 1.x is het niet het pad dat een installatie vanuit de interface loopt. Lees dus niet uit app-script af wat umbreld doet zonder te controleren of die weg ook echt bewandeld wordt.

Dat --build blijft trouwens ook zonder dit verhaal een slecht idee voor een app: een Dockerfile staat niet in de update-whitelist hierboven, dus een nieuwe versie vraagt een deïnstallatie, en de installatie hangt tijdens het bouwen. Een bouwrecept hoort in tools/ in de repo, en het resultaat in een register.

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:

- ${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, mempool/docker-compose.yml, 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. 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 Half, sinds 0.1.0 (07-09-2026). De app heeft een eigen image; de digest komt erbij bij de eerste push en dat kan alleen op de Umbrel. Wat nog ontbreekt is linux/arm64: build.sh bouwt standaard één architectuur, multi-arch zit erin als schakelaar en is nooit geprobeerd
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.

In de officiële store zijn het kale bestandsnamen. Uit electrs/umbrel-app.yml:

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:

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, voorbeeld uit de community-store-template, 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.

Bevestigd op de machine op 27-08-2026. Alles hieronder was tot dan afgeleid uit de bron van umbreld en uit de drie manifesten, en nooit uitgeprobeerd. Na de eerste volledige sync van Fulcrum heeft de gebruiker in de umbrelOS-instellingen omgeschakeld van Electrs naar Fulcrum. Electrum Gate hoefde niet aangepast te worden, en daarna kreeg een wallet van buiten over TLS gewoon data terug. Het aliassen van NODE_IP en NODE_PORT werkt dus zoals hier beschreven. Wat het omschakelen zelf nog liet zien: umbrelOS herstart de afnemende app erbij, en de app kwam terug met dezelfde certificaatkeuze.

Het mechanisme. Een app declareert een afhankelijkheid op een app-id, en een andere app kan zeggen dat hij die rol vervult:

# 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:

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:

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:

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:

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:

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, mempool/umbrel-app.yml, zoraxy/umbrel-app.yml, 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

Opgelost, en op 27-08-2026 in de praktijk bevestigd. Hier stond dat deze app zelf 50002 publiceerde voor stunnel, en dat de app daardoor met Fulcrum erbij niet zou starten. Dat gold voor de oude opstelling. Open punt 1 van het plan Configuratie is op 19-08-2026 beslist op 50022, en de handmatige stunnel-container die 50002 vasthield is op 25-08-2026 weggehaald. Sinds de omschakeling draaien Fulcrum op 50002 en Electrum Gate op 50022 naast elkaar op dezelfde host.

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