Open punt 5 van Webinterface, goedgekeurd en gebouwd. De agent verbindt met de eigen TLS-poort, maakt de handdruk af en vergelijkt het getoonde certificaat byte voor byte met het gekozen bestand. Die vergelijking blijkt waardevoller dan de handdruk. Ze vangt een certificaatwissel die nginx nooit heeft toegepast, en dat is precies het geval waar een controle op vertrouwen blind voor is. Er wordt daarom bewust niet tegen de certificaatwinkel van het besturingssysteem geverifieerd: een zelfondertekend certificaat uploaden is een ondersteunde bron en die opstelling zou dan als kapot gemeld worden. Twee dingen liepen anders dan het plan zei en staan nu rechtgezet in PLAN 4a2, OPEN punt 5 en de changelog. De belofte "luisteren, certificaat en doorverbinding in een keer" klopt voor twee van de drie: na de handdruk wordt er niets verstuurd. Een echt verzoek zou de sessie bytes geven, en sessies met bytes worden nooit uit het activiteitenlog gefilterd, want die kunnen een storing zijn. En dat filteren was de tweede verrassing. Elke meting is voor nginx een gewone sessie en levert dus een logregel op; zonder rem ging het log over onszelf in plaats van over wallets. Er zit nu een rem van vijf minuten op, er wordt niet gemeten vlak na een herlading omdat het vorige certificaat er dan nog staat, en de eigen regels worden weggelaten op grond van het moment. Tests: 22 nieuw, met de nadruk op de niet-gelukkige paden en op de rem, want dat is wat het log bruikbaar houdt. Alle vier de beslissende regels mutatie-getest. De changelog kreeg ook de ontbrekende 0.0.15 erbij. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
258 lines
14 KiB
YAML
258 lines
14 KiB
YAML
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. De servicenaam resolveert binnen het
|
|
# compose-netwerk, en dat is dezelfde weg die nginx andersom gebruikt met
|
|
# zijn proxy_pass naar 'agent'. Niet localhost: dat is een andere container.
|
|
GATE_TLS_HOST: server
|
|
# 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"
|
|
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;'
|