Files
UmbrelApps/whatsnext-electrum-gate/docker-compose.yml
T

265 lines
14 KiB
YAML
Raw Normal View History

2026-08-25 16:27:57 +02:00
services:
# umbrelOS genereert deze service zelf; wij vullen alleen in waar hij heen moet
# wijzen. De hostnaam heeft de vorm <app-id>_<compose-service>_1.
app_proxy:
environment:
APP_HOST: whatsnext-electrum-gate_server_1
APP_PORT: 80
# De agent. Hij schrijft status.json, leest de certificaten, bevraagt de
# Electrum-server en neemt de certificaatkeuze aan.
#
# Waarom een tweede container en niet een shell-lus in de server hieronder:
# dit werk is inmiddels een programma. Een certificaatdatum uitlezen, een
# JSON-RPC-verzoek doen, een geschiedenis bijhouden en een keuze valideren zijn
# geen dingen die je met openssl en nc aan elkaar knoopt zonder dat het stil
# verkeerde antwoorden gaat geven. Bijkomend voordeel: de app hangt niet meer
# af van de vraag of die twee gereedschappen in de nginx-image zitten.
agent:
# TODO (fase 4 van het plan Appstore): pinnen op de multi-arch index-digest,
# te bepalen met `docker buildx imagetools inspect python:3-alpine` op de
# Umbrel. Geldt voor beide images in dit bestand.
image: python:3-alpine
restart: on-failure
environment:
# Het adres van de Electrum-server die de gebruiker in umbrelOS gekozen
# heeft. umbrelOS vult dit in op grond van de afhankelijkheid hieronder,
# dus dit werkt met Electrs, Fulcrum en ElectrumX.
GATE_ELECTRUM_HOST: ${APP_ELECTRS_NODE_IP}
GATE_ELECTRUM_PORT: ${APP_ELECTRS_NODE_PORT}
GATE_TLS_PORT: "50022"
# De zelfcontrole: de agent verbindt met de TLS-poort van de server
# hieronder en maakt de handdruk af. Niet localhost: dat is een andere
# container en dus een andere netwerk-namespace.
#
# De volledige containernaam en niet de servicenaam 'server'. Dat stond er
# eerst, en op het apparaat gaf het `[Errno 111] Connection refused`
# (27-08-2026). Die fout is veelzeggend: geweigerd betekent dat de naam wél
# oploste, alleen niet naar ons. 'server' is een naam die meer apps op deze
# machine gebruiken, en op een gedeeld netwerk is het dus een gok wie je
# krijgt. De vorm <app-id>_<service>_1 is uniek per app; het is niet toevallig
# dezelfde vorm die umbrelOS hierboven van APP_HOST verlangt.
GATE_TLS_HOST: whatsnext-electrum-gate_server_1
# Niet elke ronde meten, want elke meting is voor nginx een gewone sessie
# en levert dus een regel in het activiteitenlog op. Vijf minuten is vaak
# genoeg voor een waarde die zelden verandert; na een certificaatwissel
# wordt er sowieso gemeten.
GATE_SELF_CHECK_INTERVAL: "300"
2026-08-25 16:27:57 +02:00
GATE_INTERVAL: "60"
GATE_API_PORT: "8000"
GATE_STATE_DIR: /var/lib/gate
# Bron-naam=map. Een bron die niet gemount is, bestaat niet en wordt
# overgeslagen; dat is de normale toestand voor een reverse proxy die de
# gebruiker niet draait.
GATE_CERT_SOURCES: "Zoraxy=/certs/zoraxy,Own folder=/certs/own"
# De enige beschrijfbare bron, en dus de map waarin een upload van de
# pagina landt. Expliciet en niet afgeleid uit de lijst hierboven: welke
# bron beschrijfbaar is, hoort te staan naast de mount die dat toestaat.
# Leeg zetten schakelt uploaden uit.
GATE_UPLOAD_DIR: /certs/own
PYTHONUNBUFFERED: "1"
volumes:
# Door umbrelOS ingevuld uit agent.py.template bij het starten. Er staat
# geen accolade-variabele in dat bestand, dus de invulling laat de
# Python-code ongemoeid; de agent leest zijn instellingen uit de omgeving
# hierboven. De test in tests/ controleert die aanname.
- ${APP_DATA_DIR}/agent.py:/app/agent.py:ro
# De gedeelde toestand: status.json, cert.conf, de herlaadvlag en de
# gekozen certificaat-id. Beide containers zitten hierin.
#
# Onder data/ en niet naast de templates, want dat is wat andere apps doen:
# electrs mount ${APP_DATA_DIR}/data/electrs, mempool ${APP_DATA_DIR}/data.
# Verplaatst op 20-08-2026 met het oog op publicatie in de officiele store.
- ${APP_DATA_DIR}/data/runtime:/var/lib/gate
# De certificaatbronnen. Zoraxy alleen lezen: dat zijn de certificaten van
# een andere app en die raakt deze app niet aan.
- ${UMBREL_ROOT}/app-data/zoraxy/data/config/conf/certs:/certs/zoraxy:ro
# De eigen map is de enige die beschrijfbaar is, want hier landt een upload
# van de pagina. Alleen de agent; de nginx-container houdt hem alleen-lezen,
# want die hoeft er nooit iets neer te zetten.
- ${APP_DATA_DIR}/data/certs:/certs/own
#
# Nginx Proxy Manager hoort hier als derde bron bij en staat er bewust nog
# niet in. Het pad hieronder is een gok en niet geverifieerd, en een
# bind-mount naar een pad dat niet bestaat laat Docker het aanmaken. Dat zou
# een lege maphierarchie neerzetten in de app-data van een app die
# misschien niet eens geinstalleerd is, en die rommel blijft daar staan.
# Eerst op de Umbrel controleren waar NPM zijn certificaten neerzet, dan
# deze regel aanzetten en de naam toevoegen aan GATE_CERT_SOURCES.
#
# - ${UMBREL_ROOT}/app-data/nginx-proxy-manager/data/letsencrypt/live:/certs/npm:ro
command:
- python
- /app/agent.py
server:
image: nginx:alpine
restart: on-failure
# De agent moet er zijn voordat nginx start, want de proxy_pass naar
# http://agent:8000 wordt bij het starten opgelost en een onbekende naam
# laat nginx afbreken. Op de cert.conf van de agent wordt niet gewacht: de
# pagina komt hoe dan ook omhoog, zie het command-blok hieronder.
depends_on:
- agent
ports:
# De TLS-poort voor Electrum-wallets. Dit is de enige poort die deze app
# zelf publiceert; de web-UI loopt via app_proxy.
#
# 50022 en niet de conventionele 50002, omdat Fulcrum die op de host
# bezet: met Fulcrum als backend zou de container niet starten, en dat is
# precies het omschakelen dat deze app moet ondersteunen. De poort naar
# buiten is toch al een andere, want die staat in de router doorgestuurd.
# Beslist 19-08-2026, open punt 1 van het plan Configuratie.
- "50022:50022"
volumes:
# Beide door umbrelOS ingevuld uit een .template bij het starten. De
# pagina wordt als los bestand gemount en niet als map: de bron staat in
# de app-root, want alleen daar wordt hij bij een update ververst.
- ${APP_DATA_DIR}/nginx.conf:/etc/nginx/nginx.conf:ro
- ${APP_DATA_DIR}/index.html:/usr/share/nginx/html/index.html:ro
# Het app-icoon, voor de kop van de pagina en het tabblad. Geen template,
# dus dit bestand komt alleen bij een installatie mee en niet bij een
# update; voor een plaatje dat vrijwel nooit wijzigt is dat goed genoeg.
# De pagina valt terug op een ingebouwd merkje als de mount er niet is.
- ${APP_DATA_DIR}/icon.png:/usr/share/nginx/html/icon.png:ro
# Het stream-blok, dus de TLS-poort zelf. Het staat los van nginx.conf
# omdat nginx niet start met een 'listen ssl' zonder certificaat, en het
# command-blok hieronder zet het pas in /var/lib/gate/tls/ zodra de agent
# een certificaat gekozen heeft. Tot die tijd draait alleen de pagina.
- ${APP_DATA_DIR}/stream.conf:/etc/nginx/stream.conf:ro
# Dezelfde gedeelde toestand als de agent. nginx leest hier cert.conf en
# status.json, en schrijft de sessielog.
- ${APP_DATA_DIR}/data/runtime:/var/lib/gate
# De certificaten zelf moeten ook hier gemount zijn: de agent bepaalt
# welk pad in cert.conf komt, maar nginx moet het bestand kunnen openen.
- ${UMBREL_ROOT}/app-data/zoraxy/data/config/conf/certs:/certs/zoraxy:ro
- ${APP_DATA_DIR}/data/certs:/certs/own:ro
command:
- /bin/sh
- -c
- |
set -eu
mkdir -p /var/lib/gate
# Het log_format van de stream-sessies wordt hier geschreven en niet in
# nginx.conf.template. Een log_format bestaat uit nginx-variabelen met
# een dollarteken, en de template-invulling van umbreld zou die
# stilzwijgend leegmaken. In dit blok is een dollarteken als $$ te
# ontsnappen, dus hier kan het wel.
#
# Let op wat er NIET in staat: geen client-adres en geen bronpoort. Dat
# is precies het soort gegeven dat deze app van het netwerk af houdt, en
# om te zien dat het werkt is het niet nodig.
cat > /var/lib/gate/stream-log.conf <<'CONF'
log_format gate '$$time_iso8601 $$status $$bytes_received $$bytes_sent $$session_time';
access_log /var/lib/gate/stream.log gate;
CONF
# De TLS-poort aan of uit zetten, naar de toestand van cert.conf.
#
# Hier zat tot 0.0.3 een lus die wachtte tot de agent een certificaat
# gekozen had, en dat was fout: nginx startte dan niet, dus de pagina
# kwam niet omhoog, en de pagina is juist waar je dat certificaat kiest.
# Zonder Zoraxy, of met twee kandidaten, hing de app daarmee vast op een
# keuze die nergens te maken was.
#
# Nu start nginx altijd. Het stream-blok komt erbij zodra cert.conf er
# is, en verdwijnt weer als de agent zijn keuze intrekt. nginx.conf haalt
# deze map op met een jokerteken; die matcht dan niets en dat is geen
# fout.
mkdir -p /var/lib/gate/tls
#
# Deze functie eindigt altijd geslaagd, en dat is met opzet: het script
# draait onder 'set -e', dus een mislukte cp zou de aanroeper afbreken.
# In de lus hieronder is die aanroeper de wachtlus, en die mag om geen
# enkele reden stoppen.
sync_tls() {
if [ -f /var/lib/gate/cert.conf ]; then
# Kopieren en niet linken: nginx opent dit pad als de gebruiker
# nginx, en een symlink naar een read-only mount is nodeloos fragiel.
if cp /etc/nginx/stream.conf /var/lib/gate/tls/stream.conf; then
echo "Certificaat aanwezig, TLS luistert op 50022."
else
echo "stream.conf kon niet worden weggezet; TLS blijft uit."
fi
else
rm -f /var/lib/gate/tls/stream.conf
echo "Nog geen certificaat gekozen; TLS staat uit, de pagina werkt."
fi
}
sync_tls
# De verbindingsteller. nginx stream schrijft zijn logregel pas bij het
# sluiten van een sessie, en een wallet houdt zijn verbinding uren open;
# zonder deze teller lijkt een actieve wallet dus afwezig. Dat was de
# vraag van de gebruiker op 20-08-2026, en het open punt uit §4e van het
# plan Webinterface: hoe die toestand binnen de container af te lezen is.
#
# Antwoord: /proc/net/tcp. Dat geldt per netwerk-namespace, dus dit ziet
# alleen de sockets van deze container, en daarom staat deze teller hier
# en niet in de agent: die zit in een andere namespace en kan er niet bij.
# Kolom 2 is het lokale adres met de poort in hex, kolom 4 de toestand.
# C366 is 50022 en 01 is ESTABLISHED, dus de luisterende socket (0A) en de
# verbindingen naar de backend en naar de pagina vallen er buiten.
#
# Geteld wordt alleen het aantal. Geen adres en geen bronpoort, net als in
# het log_format hierboven.
count_sessions() {
cat /proc/net/tcp /proc/net/tcp6 2>/dev/null \
| awk '$$4 == "01" && $$2 ~ /:C366$$/ { n++ } END { print n+0 }'
}
# Meteen een 0 neerzetten, zodat de pagina "geen verbindingen" kan tonen
# in plaats van "onbekend" in het minuutje voor de eerste ronde.
count_sessions > /var/lib/gate/sessions
# De herlaadlus. De agent kan nginx niet zelf herladen: dat zou de
# Docker-socket vragen en die is er bewust uit. In plaats daarvan zet hij
# een vlagbestand neer en herlaadt nginx zichzelf. Een reload leest het
# nieuwe certificaat in zonder bestaande verbindingen te verbreken.
#
# Deze lus draait op de achtergrond en nginx wordt hieronder met exec het
# hoofdproces. Andersom gaat twee keer mis: de shell blijft dan PID 1 en
# geeft signalen niet door, en als nginx omvalt eindigt het script met
# exitcode 0, waardoor Docker een geslaagde afsluiting ziet en
# 'restart: on-failure' niet ingrijpt.
(
while true; do
sleep 10
# Eerst schrijven, dan mv: de agent leest dit bestand op zijn eigen
# moment en mag geen half bestand zien.
count_sessions > /var/lib/gate/sessions.tmp
mv /var/lib/gate/sessions.tmp /var/lib/gate/sessions
if [ -f /var/lib/gate/reload ]; then
echo "Certificaat gewijzigd, nginx herladen ..."
# Eerst wegzetten, dan herladen. Andersom zou een herlading die
# mislukt de vlag toch opruimen, en dan probeert hij het nooit meer.
mv /var/lib/gate/reload /var/lib/gate/reload.done
# Voor de herlading, niet erna: een reload leest de configuratie
# opnieuw, dus het stream-blok moet er dan al liggen.
sync_tls
# De herlading mag niet met 'set -e' meegaan. Een certificaat dat
# nginx niet aanneemt laat de reload falen, en dan zou deze lus
# verdwijnen: geen enkele latere wijziging wordt dan nog opgepikt,
# zonder dat er iets te zien is.
if ! nginx -s reload; then
echo "Herladen mislukt; nginx houdt de vorige configuratie."
fi
elif [ -f /var/lib/gate/cert.conf ] && [ ! -f /var/lib/gate/tls/stream.conf ]; then
# De vangnetregel. De agent zet de vlag bij elke wijziging, dus
# normaal komt hier niets langs; wel als de vlag verloren gaat of
# als cert.conf van een vorige installatie al klaarlag. Zonder deze
# tak zou TLS dan uit blijven staan tot de volgende wijziging.
echo "Certificaat gevonden zonder herlaadvlag, TLS aanzetten ..."
sync_tls
if ! nginx -s reload; then
echo "Herladen mislukt; nginx houdt de vorige configuratie."
fi
fi
done
) &
exec nginx -g 'daemon off;'