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>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
7b3804df89
commit
973b24a23f
@@ -0,0 +1,7 @@
|
|||||||
|
# De bouwcontext van tools/electrum-gate/build.sh is de repo-root, omdat de
|
||||||
|
# Dockerfile icon.png uit de app-map kopieert. Alles wat de Dockerfile niet
|
||||||
|
# noemt blijft hier buiten, zodat de context klein is en een wijziging in de
|
||||||
|
# documentatie de bouwcache niet ongeldig maakt.
|
||||||
|
*
|
||||||
|
!tools/electrum-gate
|
||||||
|
!whatsnext-electrum-gate/icon.png
|
||||||
+2
-2
@@ -59,8 +59,8 @@ build/
|
|||||||
*.zip
|
*.zip
|
||||||
|
|
||||||
# ── Python ────────────────────────────────────────────────────────────────────
|
# ── Python ────────────────────────────────────────────────────────────────────
|
||||||
# De test importeert agent.py.template als module, en Python legt daar dan
|
# De test importeert tools/electrum-gate/agent.py als module, en Python legt daar
|
||||||
# bytecode naast. De test zet dat zelf uit, maar dit is de vangnetregel: zo'n
|
# dan bytecode naast. De test zet dat zelf uit, maar dit is de vangnetregel: zo'n
|
||||||
# .pyc glipte een keer mee in een commit.
|
# .pyc glipte een keer mee in een commit.
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ gelden, dan per app wat alleen daar geldt.
|
|||||||
|
|
||||||
| App | Map | Toestand |
|
| App | Map | Toestand |
|
||||||
|-|-|-|
|
|-|-|-|
|
||||||
| **Electrum Gate** | `whatsnext-electrum-gate/` | draait op de Umbrel, wordt gebruikt |
|
| **Electrum Gate** | `whatsnext-electrum-gate/` | draait op de Umbrel, wordt gebruikt. Sinds 0.1.0 met een eigen image en dus een bouwstap: zie **Electrum Gate** hieronder |
|
||||||
| **Evolu Relay** | `whatsnext-evolu-relay/` | draait op de Umbrel sinds 28-08-2026 en wordt gebruikt. Let op de bouwstap: zie **Evolu Relay** hieronder |
|
| **Evolu Relay** | `whatsnext-evolu-relay/` | draait op de Umbrel sinds 28-08-2026 en wordt gebruikt. Let op de bouwstap: zie **Evolu Relay** hieronder |
|
||||||
|
|
||||||
**Zet geen CLAUDE.md of andere werkbestanden in een app-map.** umbreld kopieert bij installatie de héle
|
**Zet geen CLAUDE.md of andere werkbestanden in een app-map.** umbreld kopieert bij installatie de héle
|
||||||
@@ -25,12 +25,14 @@ Deze gelden voor élke app hier, want het zijn eigenschappen van umbrelOS. Met b
|
|||||||
`${...}` in zo'n bestand door `envsubst`, ook een variabele die niet bestaat, en die wordt dan leeg. Dat
|
`${...}` in zo'n bestand door `envsubst`, ook een variabele die niet bestaat, en die wordt dan leeg. Dat
|
||||||
sloopt Python-code, en in een nginx-config betekent het geen `$host`, geen `log_format` en geen
|
sloopt Python-code, en in een nginx-config betekent het geen `$host`, geen `log_format` en geen
|
||||||
`access_log` met variabelen. Moet er tóch een dollarteken in een nginx-directive, dan hoort die directive
|
`access_log` met variabelen. Moet er tóch een dollarteken in een nginx-directive, dan hoort die directive
|
||||||
in een bestand dat het `command`-blok van de compose wegschrijft, want daar is `$$` te ontsnappen.
|
in een bestand dat het `command`-blok van de compose wegschrijft (daar is `$$` te ontsnappen), of in een
|
||||||
|
eigen image, waar geen envsubst aan te pas komt. Electrum Gate doet sinds 0.1.0 het tweede; Evolu Relay
|
||||||
|
heeft zijn agent en pagina nog als `*.template`.
|
||||||
|
|
||||||
**Wat bij een update meekomt is een whitelist**: `docker-compose.yml`, `*.template`, `exports.sh`, `torrc`,
|
**Wat bij een update meekomt is een whitelist**: `docker-compose.yml`, `*.template`, `exports.sh`, `torrc`,
|
||||||
`hooks` en `umbrel-app.yml`. Al het andere bereikt een bestaande installatie nooit, zonder foutmelding.
|
`hooks` en `umbrel-app.yml`. Al het andere bereikt een bestaande installatie nooit, zonder foutmelding.
|
||||||
Zet logica die later nog moet kunnen wijzigen dus in de compose of in een `*.template`, en nooit in een
|
Zet logica die later nog moet kunnen wijzigen dus in de compose, in een `*.template` of in een eigen
|
||||||
los bestand of in een submap.
|
image, en nooit in een los bestand of in een submap van de app-map.
|
||||||
|
|
||||||
**Een wijziging zonder verhoging van `version` in `umbrel-app.yml` wordt niet uitgerold.** Geen melding,
|
**Een wijziging zonder verhoging van `version` in `umbrel-app.yml` wordt niet uitgerold.** Geen melding,
|
||||||
geen fout; umbrelOS ziet hetzelfde nummer en doet niets. Dat heeft hier een keer een dag gekost. De
|
geen fout; umbrelOS ziet hetzelfde nummer en doet niets. Dat heeft hier een keer een dag gekost. De
|
||||||
@@ -57,7 +59,8 @@ python tests/test_agent_certificates.py
|
|||||||
python tests/test_server_start_zonder_certificaat.py
|
python tests/test_server_start_zonder_certificaat.py
|
||||||
```
|
```
|
||||||
|
|
||||||
Deze twee gaan over Electrum Gate en noemen die app-map bij naam. Er is een derde die over de **store**
|
Deze twee gaan over Electrum Gate en noemen `tools/electrum-gate/` en die app-map bij naam. Er is een
|
||||||
|
derde die over de **store**
|
||||||
gaat en zijn apps zelf vindt, dus over beide:
|
gaat en zijn apps zelf vindt, dus over beide:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -75,18 +78,22 @@ Er is ook een test over de **pagina's** van beide apps, en die is geen Python:
|
|||||||
node tests/test_paginas_parsen.mjs
|
node tests/test_paginas_parsen.mjs
|
||||||
```
|
```
|
||||||
|
|
||||||
Hij toetst dat de JavaScript in elke `index.html.template` parseert, en dat hij dat ook nog doet ná de
|
Hij toetst dat de JavaScript in elke statuspagina parseert. Een pagina staat op een van twee plekken: als
|
||||||
invulling door umbreld. Dat is de enige klasse paginafouten die niet op het apparaat gevonden hoeft te
|
`index.html.template` in de app-map (Evolu Relay; dan toetst hij ook dat het script heel blijft ná de
|
||||||
worden; alles wat de pagina *toont* blijft handwerk in een browser.
|
invulling door umbreld) of als `index.html` in `tools/<app>/` (Electrum Gate sinds 0.1.0; dan toetst hij
|
||||||
|
dat er geen accolade-variabele meer in staat). Dat is de enige klasse paginafouten die niet op het
|
||||||
|
apparaat gevonden hoeft te worden; alles wat de pagina *toont* blijft handwerk in een browser.
|
||||||
|
|
||||||
**Wil je een pagina bekijken, gebruik `tools/voorbeeldpagina.mjs`.** Een `*.template` is niet te openen: er
|
**Wil je een pagina bekijken, gebruik `tools/voorbeeldpagina.mjs`.** Een statuspagina is niet zomaar te
|
||||||
staan accolade-variabelen in en de gegevens komen van een agent die alleen in de app bestaat. Dat script
|
openen: de gegevens komen van een agent die alleen in de app bestaat, en in een `*.template` staan
|
||||||
vult beide in en zet het resultaat in `voorbeeld/` (gitignored). Geen bewijs, wel het gereedschap dat op
|
bovendien accolade-variabelen. Dat script vindt de pagina op een van de twee plekken, vult in wat er in te
|
||||||
|
vullen is en zet het resultaat in `voorbeeld/` (gitignored). Geen bewijs, wel het gereedschap dat op
|
||||||
30-08-2026 twee echte opmaakfouten vond voordat ze uitgerold waren.
|
30-08-2026 twee echte opmaakfouten vond voordat ze uitgerold waren.
|
||||||
|
|
||||||
`test_appstore_vorm.py` toetst **expres niet** dat images op een digest gepind zijn. Dat is wél de regel,
|
`test_appstore_vorm.py` toetst **expres niet** dat images op een digest gepind zijn. Dat is wél de regel,
|
||||||
maar geen van de twee apps haalt hem vandaag, en een suite die altijd rood staat wordt niet gelezen. Hij
|
maar geen van de twee apps haalt hem volledig, en een suite die altijd rood staat wordt niet gelezen. Hij
|
||||||
drukt de pinstatus wel af.
|
drukt de pinstatus wel af. Wat hij wél toetst: dat de tag van een eigen image gelijk is aan `VERSION` in
|
||||||
|
het bijbehorende `tools/<app>/build.sh`.
|
||||||
|
|
||||||
Twee dingen om te weten voordat je een groene uitslag vertrouwt:
|
Twee dingen om te weten voordat je een groene uitslag vertrouwt:
|
||||||
|
|
||||||
@@ -94,20 +101,34 @@ Twee dingen om te weten voordat je een groene uitslag vertrouwt:
|
|||||||
certificaatwinkel van het besturingssysteem nodig, en de subjectAltName-toets heeft netwerk nodig. Staat
|
certificaatwinkel van het besturingssysteem nodig, en de subjectAltName-toets heeft netwerk nodig. Staat
|
||||||
er `OVERGESLAGEN` in de uitvoer, dan is dat deel níet bewezen. Lees de uitvoer dus, tel niet alleen de
|
er `OVERGESLAGEN` in de uitvoer, dan is dat deel níet bewezen. Lees de uitvoer dus, tel niet alleen de
|
||||||
exitcode;
|
exitcode;
|
||||||
- **de suite dekt de agent, niet de pagina.** Alles in `index.html.template` is handwerk in een browser.
|
- **de suite dekt de agent, niet de pagina.** Alles in `index.html` is handwerk in een browser.
|
||||||
|
|
||||||
### Architectuurregels
|
### Architectuurregels
|
||||||
|
|
||||||
|
**De app-code zit in een eigen image, gebouwd uit `tools/electrum-gate/`** (sinds 0.1.0, 07-09-2026). De
|
||||||
|
agent, de nginx-configuratie, het stream-blok, de pagina en het startscript staan daar; in de app-map
|
||||||
|
staan alleen nog de compose, het manifest, het icoon en `data/`. Eén image voor beide containers: de
|
||||||
|
compose start hem als `server` met het standaardcommando en als `agent` met `python3 /app/agent.py`.
|
||||||
|
Gevolg dat je moet kennen voordat je iets belooft: **een wijziging in `tools/electrum-gate/` is pas
|
||||||
|
uitgerold als de image gebouwd, geduwd en met zijn digest in de compose gezet is**, en dat kan alleen de
|
||||||
|
gebruiker, op de Umbrel. Verhoog je `VERSION` in `build.sh`, dan verhoog je ook `version` in het manifest
|
||||||
|
en de tag in de compose (twee keer); `test_appstore_vorm.py` houdt tag en `VERSION` gelijk.
|
||||||
|
|
||||||
**Geen Docker-socket.** De agent kan nginx daarom niet zelf herladen; hij zet een vlagbestand neer en de
|
**Geen Docker-socket.** De agent kan nginx daarom niet zelf herladen; hij zet een vlagbestand neer en de
|
||||||
nginx-container herlaadt zichzelf. Als je denkt de socket nodig te hebben, is er bijna zeker een
|
nginx-container herlaadt zichzelf. Als je denkt de socket nodig te hebben, is er bijna zeker een
|
||||||
vlagbestand-oplossing.
|
vlagbestand-oplossing.
|
||||||
|
|
||||||
**De pagina start altijd, ook zonder certificaat.** Het TLS-blok staat daarom niet in
|
**De pagina start altijd, ook zonder certificaat.** Het TLS-blok staat daarom niet in `nginx.conf` maar
|
||||||
`nginx.conf.template` maar in `stream.conf.template`, en het `command`-blok van de compose zet dat pas in
|
in `stream.conf`, en `entrypoint.sh` zet dat pas in `/var/lib/gate/tls/` als de agent een `cert.conf`
|
||||||
`/var/lib/gate/tls/` als de agent een `cert.conf` geschreven heeft; `nginx.conf` haalt die map met een
|
geschreven heeft; `nginx.conf` haalt die map met een jokerteken op. Zet het blok niet terug in
|
||||||
jokerteken op. Zet het blok niet terug in `nginx.conf`, hoe netjes dat ook staat: nginx weigert te starten
|
`nginx.conf`, hoe netjes dat ook staat: nginx weigert te starten als een `listen ssl` geen certificaat
|
||||||
als een `listen ssl` geen certificaat heeft, en dan komt de pagina waarop je dat certificaat kiest ook niet
|
heeft, en dan komt de pagina waarop je dat certificaat kiest ook niet omhoog. Dat was de fout in 0.0.3.
|
||||||
omhoog. Dat was de fout in 0.0.3. `tests/test_server_start_zonder_certificaat.py` houdt het dicht.
|
`tests/test_server_start_zonder_certificaat.py` houdt het dicht.
|
||||||
|
|
||||||
|
**Het backend-adres komt uit de omgeving, in beide containers.** nginx kan in een `proxy_pass` van het
|
||||||
|
stream-blok geen omgevingsvariabele lezen, dus `stream.conf` heeft twee plaatshouders zonder dollarteken
|
||||||
|
die `entrypoint.sh` bij het starten invult. De pagina haalt de versie en het adres uit `status.json`; tot
|
||||||
|
0.0.29 vulde umbreld die rechtstreeks in de pagina in.
|
||||||
|
|
||||||
**Bij twijfel over een certificaat weigert de app.** Meerdere kandidaten zonder keuze van de gebruiker
|
**Bij twijfel over een certificaat weigert de app.** Meerdere kandidaten zonder keuze van de gebruiker
|
||||||
levert géén automatische keuze op, ook niet "de nieuwste". Een verkeerd certificaat geeft een verbinding
|
levert géén automatische keuze op, ook niet "de nieuwste". Een verkeerd certificaat geeft een verbinding
|
||||||
@@ -116,7 +137,7 @@ dan een app die weigert en zegt waarom.
|
|||||||
|
|
||||||
### Feiten
|
### Feiten
|
||||||
|
|
||||||
- App-id en mapnaam `whatsnext-electrum-gate`.
|
- App-id en mapnaam `whatsnext-electrum-gate`; image `sc.kamenier-hamer.nl/sysop/electrum-gate`.
|
||||||
- De TLS-poort is **50022**, niet de conventionele 50002: die bezet Fulcrum op de host.
|
- De TLS-poort is **50022**, niet de conventionele 50002: die bezet Fulcrum op de host.
|
||||||
- De app leest de certificaatmap van Zoraxy alleen-lezen. Dat is het meest ongebruikelijke aan dit
|
- De app leest de certificaatmap van Zoraxy alleen-lezen. Dat is het meest ongebruikelijke aan dit
|
||||||
pakket; zie het masterplan **Publicatie-Gate** §4.
|
pakket; zie het masterplan **Publicatie-Gate** §4.
|
||||||
|
|||||||
@@ -8,6 +8,41 @@ Wat er nog moet gebeuren staat **niet** hier maar in de plannen; zie [CONTINUE_H
|
|||||||
Een lijst met geplande features op twee plekken loopt uit elkaar, en dan is geen van beide meer te
|
Een lijst met geplande features op twee plekken loopt uit elkaar, en dan is geen van beide meer te
|
||||||
vertrouwen.
|
vertrouwen.
|
||||||
|
|
||||||
|
## [0.1.0] - 2026-09-07
|
||||||
|
|
||||||
|
**Nog niet gebouwd.** De compose verwijst naar `sc.kamenier-hamer.nl/sysop/electrum-gate:0.1.0` zonder
|
||||||
|
digest; die komt erbij zodra de gebruiker de image op de Umbrel gebouwd en geduwd heeft. Tot die tijd is
|
||||||
|
dit een versie in de repo en niet op het apparaat.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **De app heeft een eigen image**, gebouwd uit `tools/electrum-gate/` met `build.sh`, op basis van
|
||||||
|
`nginx:1.30-alpine` plus `python3`. Eén image voor beide containers; de compose start hem als `server`
|
||||||
|
met het standaardcommando en als `agent` met `python3 /app/agent.py`. Tot nu draaide de app op
|
||||||
|
`python:3-alpine` en `nginx:alpine` met zijn eigen bestanden als `*.template` uit de app-map gemount.
|
||||||
|
Waarom, staat in het plan **Eigenimage**, `PLAN.md` §3; het korte antwoord is dat de programmacode nu
|
||||||
|
niet meer in de app-data staat, en dat het startscript weer leesbaar is.
|
||||||
|
- **Het `command`-blok van de compose is `entrypoint.sh` in de image.** Ruim honderd regels shell in een
|
||||||
|
YAML-string, met elk dollarteken verdubbeld omdat umbreld anders elke `${...}` leegmaakte, is nu een
|
||||||
|
gewoon script. Zelfde gedrag: TLS aan of uit naar de toestand van `cert.conf`, de verbindingsteller uit
|
||||||
|
`/proc/net/tcp`, de herlaadlus met vangnettak, en `exec nginx` als laatste regel.
|
||||||
|
- **Het `log_format` staat in `stream.conf` zelf.** Het stond in een los bestand dat het `command`-blok
|
||||||
|
wegschreef, omdat een dollarteken in een template niet mocht. Die omweg is weg.
|
||||||
|
- **Het backend-adres komt uit de omgeving, in beide containers.** `stream.conf` heeft twee plaatshouders
|
||||||
|
zonder dollarteken die `entrypoint.sh` bij het starten invult; de server-service krijgt daarvoor dezelfde
|
||||||
|
`GATE_ELECTRUM_HOST` en `GATE_ELECTRUM_PORT` als de agent.
|
||||||
|
- **De pagina haalt de versie en het backend-adres uit `status.json`.** umbreld vulde die tot nu
|
||||||
|
rechtstreeks in de pagina in; nu de pagina in de image zit, geeft de compose `APP_VERSION` aan de agent
|
||||||
|
door als `GATE_APP_VERSION`, en zet de agent `version` in de status. Ontbreekt hij, dan toont de kop
|
||||||
|
geen versie in plaats van een verzonnen.
|
||||||
|
- **`icon.png` is updatebaar**, want de image kopieert hem uit de app-map. Het bestand blijft ook daar
|
||||||
|
staan, want het manifest wijst ernaar voor de tegel in de winkel.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- De vier `*.template`-bestanden en hun mounts uit de app-map. Wat er overblijft: `docker-compose.yml`,
|
||||||
|
`umbrel-app.yml`, `icon.png` en `data/`.
|
||||||
|
|
||||||
## [0.0.29] - 2026-09-04
|
## [0.0.29] - 2026-09-04
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
| Plan | App | Volgende stap | Status |
|
| Plan | App | Volgende stap | Status |
|
||||||
|-|-|-|-|
|
|-|-|-|-|
|
||||||
| [Eigenimage](Plannen/Actief/004-Eigenimage/TAKEN.md) | **beide** | **Gepromoveerd op 28-08-2026, want waar het op wachtte is klaar:** de gebruiker wilde eerst Evolu Relay afmaken en dat doel is gehaald. Het register is beslist (Gitea, bij inlevering opnieuw kijken), dus fase 1 kan zo starten: het bouwrecept in `tools/electrum-gate/`, multi-arch, anoniem te halen. Er ligt nu ook een voorbeeld naast: `tools/evolu-relay/` doet precies dit, inclusief de valkuil dat een lokaal gebouwde tag voor umbreld onbereikbaar is. **Sinds 30-08-2026 wegen er twee dingen zwaarder voor dit plan.** De kolom App staat op "beide": §8 is herschreven, want ook Evolu Relay heeft zijn agent en pagina nog op vreemde images staan; alleen de relay zelf is klaar. En dit plan blijkt de poort te zijn voor het pinnen dat **Publicatie-Gate** en **Publicatie-Relay** eisen: die twee images verdwijnen hier, dus eerder pinnen is weggegooid werk. Let op §1: het lost níet het updateprobleem op, want dat bestaat niet meer | ⬜ |
|
| [Eigenimage](Plannen/Actief/004-Eigenimage/TAKEN.md) | **beide** | **Fase 1 tot en met 3 gedaan op 07-09-2026, als Electrum Gate 0.1.0, ongebouwd.** Het recept staat in `tools/electrum-gate/` (Dockerfile, build.sh, entrypoint.sh en de vier voormalige templates), de app-map bevat alleen nog compose, manifest, icoon en `data/`, en de compose is voor het eerst zonder shellblok. Eén image voor beide containers, gebouwd op de Umbrel; open punt 2 en 3 zijn daarmee beslist. **Volgende stap is van de gebruiker en kan alleen op de Umbrel:** `sh tools/electrum-gate/build.sh`, duwen, de digest twee keer in de compose, committen, updaten, en dan de controles van fase 4. Reken op minstens één ronde, want de image is nog nooit gebouwd; de tests toetsen de tekst van het recept en niet het resultaat. **Daarna:** hetzelfde voor de agent en de pagina van Evolu Relay (§8), pas als de weg bij Gate bewezen is. Multi-arch en de anonieme pull horen bij **Publicatie-Gate** | 🔶 |
|
||||||
|
|
||||||
## B - Los oppakbaar (geen blokkade, geen vaste volgorde)
|
## B - Los oppakbaar (geen blokkade, geen vaste volgorde)
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ De keten die eronder ligt, van achter naar voren:
|
|||||||
**De regel: pin alleen een image die er na Eigenimage nog staat.** In de praktijk is dat de image die je
|
**De regel: pin alleen een image die er na Eigenimage nog staat.** In de praktijk is dat de image die je
|
||||||
zelf uitgeeft, en die is er precies één per app.
|
zelf uitgeeft, en die is er precies één per app.
|
||||||
|
|
||||||
|
Stand op 07-09-2026: Electrum Gate heeft die eigen image sinds 0.1.0 en zijn twee vreemde images zijn weg;
|
||||||
|
de pin komt erbij zodra de gebruiker hem gebouwd en geduwd heeft. Bij Evolu Relay staan `python:3-alpine`
|
||||||
|
en `nginx:alpine` er nog voor de agent en de pagina, en daarvoor geldt de regel hierboven onverkort.
|
||||||
|
|
||||||
Twee dingen die dit níet zegt, want anders slaat het de andere kant op door:
|
Twee dingen die dit níet zegt, want anders slaat het de andere kant op door:
|
||||||
|
|
||||||
- **de eigen image pinnen doe je wél meteen**, bij elke verhoging. Dat is geen toekomstig werk maar de
|
- **de eigen image pinnen doe je wél meteen**, bij elke verhoging. Dat is geen toekomstig werk maar de
|
||||||
|
|||||||
@@ -12,17 +12,23 @@
|
|||||||
|
|
||||||
## Nog te beslissen
|
## Nog te beslissen
|
||||||
|
|
||||||
2. **Eén image of twee?** [PLAN.md](PLAN.md) §6 stelt één voor: de agent en nginx blijven twee containers,
|
Niets.
|
||||||
maar ze delen één image. Twee images is netter gescheiden en verdubbelt het bouwwerk en het pinwerk.
|
|
||||||
**Moment:** bij de start van het werk · **Eigenaar:** sessie, met de gebruiker mee
|
|
||||||
|
|
||||||
3. **Waar wordt gebouwd?** Op de Umbrel staat Docker, maar dat is een productiemachine en hij is amd64,
|
|
||||||
terwijl arm64 ook een eis is. Bouwen op de werkplek van de gebruiker vraagt daar Docker. Dit hangt samen
|
|
||||||
met de multi-arch-stap uit §4 van het plan, die nog nooit geprobeerd is.
|
|
||||||
**Moment:** bij de start van het werk · **Eigenaar:** gebruiker
|
|
||||||
|
|
||||||
## Beslist
|
## Beslist
|
||||||
|
|
||||||
|
2. **Eén image of twee?** - **Eén** (07-09-2026, sessie, volgens het voorstel in [PLAN.md](PLAN.md) §6).
|
||||||
|
De agent en nginx blijven twee containers met dezelfde image; de compose geeft de agent een eigen
|
||||||
|
`command`. Basis is `nginx:1.30-alpine` met `python3` uit apk, en niet andersom: de officiële
|
||||||
|
nginx-image heeft de stream-module ingebouwd, bij alpine's eigen nginx is dat een los pakket.
|
||||||
|
**Moment:** afgerond · **Eigenaar:** –
|
||||||
|
|
||||||
|
3. **Waar wordt gebouwd?** - **Op de Umbrel** (07-09-2026), om een eenvoudige reden: het is de enige
|
||||||
|
machine met Docker; de werkplek heeft het niet. Dat het een productiemachine is weegt minder dan dat
|
||||||
|
het de plek is waar Evolu Relay ook al gebouwd wordt. Gevolg: één architectuur, amd64, en dat is voor
|
||||||
|
eigen gebruik genoeg. Multi-arch zit in `build.sh` als schakelaar (`PLATFORMS=...`) en is werk voor
|
||||||
|
**Publicatie-Gate**; het is nog nooit geprobeerd.
|
||||||
|
**Moment:** afgerond · **Eigenaar:** –
|
||||||
|
|
||||||
1. **Blijft het Gitea-register ook bij publicatie de bron?** - **Voor nu Gitea, bij inlevering opnieuw
|
1. **Blijft het Gitea-register ook bij publicatie de bron?** - **Voor nu Gitea, bij inlevering opnieuw
|
||||||
kijken** (27-08-2026, gebruiker).
|
kijken** (27-08-2026, gebruiker).
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,25 @@
|
|||||||
> Chronologisch sessielog, nieuwste bovenaan. Kort: 3 tot 6 regels per entry. Wat er is gebeurd en
|
> Chronologisch sessielog, nieuwste bovenaan. Kort: 3 tot 6 regels per entry. Wat er is gebeurd en
|
||||||
> waarom, niet wat er nog moet: dat staat in [TAKEN.md](TAKEN.md).
|
> waarom, niet wat er nog moet: dat staat in [TAKEN.md](TAKEN.md).
|
||||||
|
|
||||||
|
## 07-09-2026 - fase 1 tot en met 3 in één sessie: het recept staat, de app-map is leeg
|
||||||
|
|
||||||
|
Op verzoek van de gebruiker opgepakt, direct na Relay 0.5.6. De vier templates zijn met `git mv` naar
|
||||||
|
`tools/electrum-gate/` verhuisd en heten daar gewoon `agent.py`, `nginx.conf`, `stream.conf` en
|
||||||
|
`index.html`. Daarnaast nieuw: `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. Open punt 2
|
||||||
|
en 3 zijn beslist: één image, gebouwd op de Umbrel.
|
||||||
|
|
||||||
|
Wat er inhoudelijk veranderde en niet alleen verhuisde: het `log_format` staat nu in `stream.conf` zelf,
|
||||||
|
het backend-adres komt via twee plaatshouders zonder dollarteken uit de omgeving (ook de server-service
|
||||||
|
krijgt ze nu), en de pagina haalt versie en adres uit `status.json`, waarvoor de agent `GATE_APP_VERSION`
|
||||||
|
doorgeeft. De compose is voor het eerst sinds 0.0.3 zonder shellblok.
|
||||||
|
|
||||||
|
Tests: alle zes groen, niets overgeslagen. Drie mutaties (het `exec` voor nginx weg, `ENTRYPOINT []` weg,
|
||||||
|
één tag in de compose verhoogd) lieten precies de bedoelde toetsen omvallen, ook de nieuwe in
|
||||||
|
`test_appstore_vorm.py`. **Niet geverifieerd: de image zelf.** Er is hier geen Docker, dus of `apk add
|
||||||
|
python3` het gedraagt zoals gedacht en of de container start, blijkt pas bij fase 4 op de Umbrel.
|
||||||
|
Electrum Gate staat op 0.1.0 in het manifest, met de tag ongepind tot de eerste push.
|
||||||
|
|
||||||
## 27-08-2026 - het register is beslist, en daarna ging de aandacht naar Evolu Relay
|
## 27-08-2026 - het register is beslist, en daarna ging de aandacht naar Evolu Relay
|
||||||
|
|
||||||
Open punt 1 beantwoord: **voor nu Gitea, bij inlevering opnieuw kijken.** De twee bezwaren die dat punt
|
Open punt 1 beantwoord: **voor nu Gitea, bij inlevering opnieuw kijken.** De twee bezwaren die dat punt
|
||||||
|
|||||||
@@ -18,65 +18,91 @@
|
|||||||
|
|
||||||
## Volgende stap
|
## Volgende stap
|
||||||
|
|
||||||
> **Dit plan ligt stil op verzoek van de gebruiker (27-08-2026): hij gaat eerst verder met Evolu Relay,
|
> **Opgepakt op 07-09-2026 op verzoek van de gebruiker.** Fase 1 tot en met 3 zijn in één sessie gedaan
|
||||||
> zie het plan Umbrelapp.** Dat is een volgordekeuze en geen blokkade; er is aan dit plan nog niets
|
> en gecommit als Electrum Gate 0.1.0, ongebouwd. **Wat nu volgt is van de gebruiker en kan alleen op de
|
||||||
> gebouwd, dus er ligt ook niets half af. Beginnen kan met fase 1, en het antwoord dat daarvoor nodig was
|
> Umbrel**, want daar staat Docker:
|
||||||
> staat er.
|
>
|
||||||
|
> 1. `sh tools/electrum-gate/build.sh`, dan `docker login` en `docker push` zoals het script afdrukt;
|
||||||
|
> 2. de digest uit de push-uitvoer achter de tag in de compose, **twee keer** (agent en server);
|
||||||
|
> 3. committen en pushen, dan de app in umbrelOS updaten;
|
||||||
|
> 4. de vijf controles van fase 4 hieronder.
|
||||||
|
>
|
||||||
|
> Dit is de eerste keer dat deze image gebouwd wordt, dus reken op minstens één ronde: een `apk add` die
|
||||||
|
> anders uitpakt dan gedacht, of een pad dat de Dockerfile wél kent en de container niet. De tests
|
||||||
|
> hierboven toetsen de tekst van het recept, niet het resultaat.
|
||||||
|
|
||||||
|
- [x] **Fase 1 tot en met 3 gedaan (07-09-2026), als 0.1.0.** De vier `*.template`-bestanden staan nu in
|
||||||
|
`tools/electrum-gate/` zonder de extensie, plus `Dockerfile`, `build.sh` en `entrypoint.sh`. De
|
||||||
|
compose gebruikt één image voor beide containers, ongepind tot de eerste push. Tests aangepast en
|
||||||
|
mutatie-getest; details in [PROGRESS.md](PROGRESS.md)
|
||||||
|
- [ ] **Fase 4: bouwen, duwen, pinnen, installeren, nakijken.** Van de gebruiker; zie hierboven
|
||||||
|
|
||||||
- [x] **Open punt 1 beantwoord (27-08-2026): voor nu Gitea, bij inlevering opnieuw kijken.** Daarmee is de
|
- [x] **Open punt 1 beantwoord (27-08-2026): voor nu Gitea, bij inlevering opnieuw kijken.** Daarmee is de
|
||||||
enige vraag weg die vóór het bouwen beantwoord moest zijn. Zie [OPEN.md](OPEN.md)
|
enige vraag weg die vóór het bouwen beantwoord moest zijn. Zie [OPEN.md](OPEN.md)
|
||||||
|
|
||||||
- [ ] **Fase 1 beginnen: het bouwrecept in `tools/electrum-gate/`.** Open punt 2 en 3 horen bij die start
|
|
||||||
en niet ervoor: één image of twee, en op welke machine er gebouwd wordt. Dat laatste is de enige die
|
|
||||||
de gebruiker moet beantwoorden, en pas op het moment dat het recept er is en gedraaid moet worden
|
|
||||||
|
|
||||||
## Fase 1 - Het bouwrecept
|
## Fase 1 - Het bouwrecept
|
||||||
|
|
||||||
- [ ] Beslissen: één image of twee (open punt 2). [PLAN.md](PLAN.md) §6 stelt één voor
|
- [x] Beslissen: één image of twee (open punt 2). **Eén**, zoals [PLAN.md](PLAN.md) §6 voorstelt; beslist
|
||||||
- [ ] Beslissen: waar wordt gebouwd (open punt 3). De Umbrel is amd64 en een productiemachine
|
07-09-2026, zie [OPEN.md](OPEN.md)
|
||||||
- [ ] `tools/electrum-gate/Dockerfile` schrijven: basisimage, `agent.py`, de nginx-configuratie, de pagina,
|
- [x] Beslissen: waar wordt gebouwd (open punt 3). **Op de Umbrel**, want dat is de enige machine met
|
||||||
en een `entrypoint.sh`. **Niet in de app-map**: een `Dockerfile` staat niet in de update-whitelist,
|
Docker; de werkplek heeft het niet. Zie [OPEN.md](OPEN.md)
|
||||||
dus bouwen-in-de-app kost bij elke versie een deïnstallatie
|
- [x] `tools/electrum-gate/Dockerfile`: `nginx:1.30-alpine` plus `python3` uit apk, de vijf bestanden
|
||||||
- [ ] `tools/electrum-gate/build.sh` naar het voorbeeld van `tools/evolu-relay/build.sh`: pin op de
|
erin, `ENTRYPOINT []` zodat het envsubst-script van de nginx-image niet meedraait. De bouwcontext is
|
||||||
basisimage, bouwen, naar het register duwen, en de digest afdrukken die in de compose moet
|
de repo-root, want `icon.png` blijft in de app-map (het manifest wijst ernaar) en wordt van daar
|
||||||
- [ ] **Multi-arch bouwen, en dat is nieuw werk.** `linux/amd64` én `linux/arm64` in de manifest-lijst.
|
gekopieerd; `.dockerignore` in de root houdt de context klein
|
||||||
Vraagt `buildx` met QEMU en is hier nog nooit geprobeerd; zie [PLAN.md](PLAN.md) §4
|
- [x] `tools/electrum-gate/build.sh` naar het voorbeeld van Evolu Relay: `VERSION`, controles vooraf
|
||||||
|
(ontbrekende bestanden, een `${` die niemand meer invult), bouwen, en de vervolgstappen afdrukken
|
||||||
|
- [ ] **Multi-arch bouwen, en dat is nieuw werk.** `build.sh` heeft er een schakelaar voor:
|
||||||
|
`PLATFORMS="linux/amd64,linux/arm64"` bouwt met `buildx` en duwt meteen. Nog nooit geprobeerd, en
|
||||||
|
niet nodig voor eigen gebruik op een amd64-Umbrel. Hoort bij **Publicatie-Gate**; zie
|
||||||
|
[PLAN.md](PLAN.md) §4
|
||||||
- [ ] **Controleren dat de image anoniem te halen is.** umbreld krijgt geen inloggegevens mee. Uitloggen in
|
- [ ] **Controleren dat de image anoniem te halen is.** umbreld krijgt geen inloggegevens mee. Uitloggen in
|
||||||
dezelfde context waarin je inlogde, anders meet je je eigen sessie. Het commando staat onderaan
|
dezelfde context waarin je inlogde, anders meet je je eigen sessie. Het commando drukt `build.sh` af.
|
||||||
`tools/evolu-relay/build.sh`
|
**Eigenaar: gebruiker**, bij fase 4
|
||||||
|
|
||||||
## Fase 2 - Het `command`-blok wordt een script
|
## Fase 2 - Het `command`-blok wordt een script
|
||||||
|
|
||||||
- [ ] Het inline `command`-blok uit `docker-compose.yml` omzetten naar `entrypoint.sh` in de image. Dat is
|
- [x] `tools/electrum-gate/entrypoint.sh`, uit het `command`-blok van de compose. Zelfde gedrag, geen
|
||||||
ruim honderd regels shell in een YAML-string met een eigen ontsnappingsregel voor het dollarteken;
|
`$$` meer, en het `log_format` staat nu gewoon in `stream.conf` in plaats van in een los bestand dat
|
||||||
zie [PLAN.md](PLAN.md) §2. **Dit is de grootste inhoudelijke winst van het hele plan**
|
het script wegschreef. Nieuw erin: het invullen van het backend-adres in het stream-blok (zie fase 3)
|
||||||
- [ ] Let op wat daarbij niet mag sneuvelen: het `log_format` met `$$`, de sessieteller uit
|
- [x] Wat niet mocht sneuvelen en er staat: de sessieteller uit `/proc/net/tcp`, de herlaadlus met zijn
|
||||||
`/proc/net/tcp`, de herlaadlus met zijn vangnettak, en het feit dat nginx met `exec` het hoofdproces
|
vangnettak, en `exec nginx` als laatste regel. Alle drie met een toets in
|
||||||
wordt. Die laatste is geen detail: andersom blijft de shell PID 1, geeft signalen niet door, en ziet
|
`test_server_start_zonder_certificaat.py`, en die zijn gezien te falen
|
||||||
Docker een geslaagde afsluiting als nginx omvalt
|
|
||||||
|
|
||||||
## Fase 3 - De app-map opschonen
|
## Fase 3 - De app-map opschonen
|
||||||
|
|
||||||
- [ ] `agent.py.template`, `nginx.conf.template`, `stream.conf.template` en `index.html.template` uit de
|
- [x] De vier templates uit de app-map, met `git mv` naar `tools/electrum-gate/` zonder `.template`
|
||||||
app-map halen zodra ze in de image zitten
|
- [x] **`index.html` heeft geen template-invulling meer.** `${APP_VERSION}` en het backend-adres komen uit
|
||||||
- [ ] **`index.html` heeft dan geen template-invulling meer.** Vandaag staat er
|
`status.json`: de agent krijgt `GATE_APP_VERSION: ${APP_VERSION}` uit de compose en zet `version` in
|
||||||
`${APP_ELECTRS_NODE_IP}:${APP_ELECTRS_NODE_PORT}` in, ingevuld door umbreld bij het starten. Zit de
|
de status; het adres stond er al als `backend.address`
|
||||||
pagina in de image, dan moet dat adres langs de omgeving naar binnen, bijvoorbeeld doordat de agent
|
- [x] **`stream.conf` heeft geen template-invulling meer.** Twee plaatshouders zonder dollarteken,
|
||||||
het in `status.json` zet en de pagina het daaruit leest. Dat is werk dat nu nog gratis is
|
`__GATE_ELECTRUM_HOST__` en `__GATE_ELECTRUM_PORT__`, die `entrypoint.sh` uit de omgeving invult;
|
||||||
- [ ] De compose houdt zijn `environment`-blok; dat is hoe `${APP_ELECTRS_NODE_IP}` binnenkomt, en die weg
|
de server-service krijgt daarvoor dezelfde twee variabelen als de agent
|
||||||
is op 27-08-2026 bewezen bij de omschakeling naar Fulcrum
|
- [x] De compose houdt zijn `environment`-blok; de mounts van de vijf losse bestanden zijn eruit
|
||||||
- [ ] Wat er in de app-map overblijft: `docker-compose.yml`, `umbrel-app.yml`, `icon.png` en `data/` met
|
- [x] Wat er in de app-map overblijft: `docker-compose.yml`, `umbrel-app.yml`, `icon.png` en `data/` met
|
||||||
zijn twee `.gitkeep`-bestanden
|
zijn twee `.gitkeep`-bestanden. Precies wat [PLAN.md](PLAN.md) §6 zei
|
||||||
|
- [x] De tests mee verhuisd: `test_agent_certificates.py` laadt `tools/electrum-gate/agent.py`;
|
||||||
|
`test_server_start_zonder_certificaat.py` toetst `entrypoint.sh` en de Dockerfile in plaats van het
|
||||||
|
`command`-blok, plus dat de tag in de compose `VERSION` uit `build.sh` is; `test_appstore_vorm.py`
|
||||||
|
leest ook de nginx-config onder `tools/` en toetst tag gelijk aan `VERSION` voor élke eigen image;
|
||||||
|
`test_paginas_parsen.mjs` en `tools/voorbeeldpagina.mjs` vinden een pagina op beide plekken
|
||||||
|
|
||||||
## Fase 4 - Uitrollen en verifiëren
|
## Fase 4 - Uitrollen en verifiëren
|
||||||
|
|
||||||
- [ ] Image gepind als `repo:tag@sha256:<digest>` in de compose, en `version` verhoogd in het manifest.
|
- [ ] Image gepind als `repo:tag@sha256:<digest>` in de compose (twee keer), en `version` verhoogd in het
|
||||||
Zonder die verhoging rolt umbrelOS niets uit
|
manifest. Dat laatste staat al op 0.1.0
|
||||||
- [ ] Op de Umbrel installeren en controleren dat alles het nog doet: de pagina, de certificaatkeuze, de
|
- [ ] Op de Umbrel installeren en controleren dat alles het nog doet: de pagina, de certificaatkeuze, de
|
||||||
TLS-poort, de zelfcontrole en het activiteitenlog
|
TLS-poort, de zelfcontrole en het activiteitenlog
|
||||||
- [ ] **`icon.png` wordt hiermee updatebaar**, want die zit dan in de image. Dat was de enige echte
|
- [ ] **De versie in de kop van de pagina**, want die loopt nu via `status.json` en niet meer via umbreld
|
||||||
uitzondering op "alles komt al aan bij een update"; zie [PLAN.md](PLAN.md) §1
|
- [ ] **`icon.png` is hiermee updatebaar**, want die zit in de image. Dat was de enige echte uitzondering
|
||||||
|
op "alles komt al aan bij een update"; zie [PLAN.md](PLAN.md) §1
|
||||||
|
|
||||||
|
## Daarna - Evolu Relay
|
||||||
|
|
||||||
|
- [ ] **Hetzelfde voor de agent en de pagina van Evolu Relay**, die nog als `*.template` op
|
||||||
|
`python:3-alpine` en `nginx:alpine` draaien; zie [PLAN.md](PLAN.md) §8. Pas na fase 4 hierboven:
|
||||||
|
eerst zien dat de weg werkt voordat de tweede app hem gaat
|
||||||
|
|
||||||
## Geblokkeerd / wacht op
|
## Geblokkeerd / wacht op
|
||||||
|
|
||||||
Niets, behalve open punt 1 hierboven, en dat is een vraag van één regel aan de gebruiker.
|
Fase 4 wacht op de gebruiker: bouwen kan alleen op de Umbrel.
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ Wat daar staat en wat deze app ervan doet:
|
|||||||
|
|
||||||
| Eis | Deze app |
|
| 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` |
|
| 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` |
|
| 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 |
|
| 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 |
|
| `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 |
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ Reach your own Electrum server from outside your network, over TLS. An Electrum
|
|||||||
a wallet on the road wants TLS. This app puts a proxy in between, using the certificate a reverse proxy on
|
a wallet on the road wants TLS. This app puts a proxy in between, using the certificate a reverse proxy on
|
||||||
the same Umbrel already manages.
|
the same Umbrel already manages.
|
||||||
|
|
||||||
Two containers, both on an off-the-shelf image, both configured from a `*.template`.
|
Two containers, one image, built from [tools/electrum-gate/](tools/electrum-gate/) by its `build.sh`
|
||||||
|
(nginx on alpine, plus python3). The app folder holds only the compose file, the manifest, the icon and
|
||||||
|
your data.
|
||||||
|
|
||||||
| Container | What it does |
|
| Container | What it does |
|
||||||
|-|-|
|
|-|-|
|
||||||
| `server` (`nginx:alpine`) | terminates TLS on 50022 and forwards plain to the Electrum server; serves the dashboard on port 80 behind the umbrelOS app proxy |
|
| `server` | terminates TLS on 50022 and forwards plain to the Electrum server; serves the dashboard on port 80 behind the umbrelOS app proxy |
|
||||||
| `agent` (`python:3-alpine`) | writes `status.json` every minute, reads the certificates from the mounted folders, queries the Electrum server, and accepts the certificate choice |
|
| `agent` | same image, different command; writes `status.json` every minute, reads the certificates from the mounted folders, queries the Electrum server, and accepts the certificate choice |
|
||||||
|
|
||||||
The agent cannot reload nginx itself, as that would need the Docker socket and it is deliberately absent.
|
The agent cannot reload nginx itself, as that would need the Docker socket and it is deliberately absent.
|
||||||
It writes `cert.conf` with the chosen paths and drops a flag file; the nginx container reloads itself. A
|
It writes `cert.conf` with the chosen paths and drops a flag file; the nginx container reloads itself. A
|
||||||
|
|||||||
@@ -11,16 +11,18 @@ Draaien:
|
|||||||
|
|
||||||
python tests/test_agent_certificates.py
|
python tests/test_agent_certificates.py
|
||||||
|
|
||||||
De test laadt `agent.py.template` rechtstreeks. Dat kan omdat dat bestand geen
|
De test laadt `tools/electrum-gate/agent.py` rechtstreeks: het bestand dat de
|
||||||
accolade-variabelen bevat en de invulling door umbreld hem dus onveranderd laat;
|
Dockerfile in de image zet. Tot 0.1.0 heette dat `agent.py.template` en stond het
|
||||||
de eerste toets hieronder controleert precies dat.
|
in de app-map; de eerste toets hieronder is uit die tijd en blijft staan, want
|
||||||
|
het script haalt zijn instellingen nog altijd uit de omgeving en niet uit een
|
||||||
|
ingevulde constante.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
# Vóór de imports, want anders is het te laat: Python legt bytecode naast
|
# Vóór de imports, want anders is het te laat: Python legt bytecode naast agent.py
|
||||||
# agent.py.template zodra die geïmporteerd wordt, en die rommel hoort niet in de
|
# zodra die geïmporteerd wordt, en die rommel hoort niet in de repo. Een keer is
|
||||||
# app-map. Een keer is zo'n .pyc meegegaan in een commit.
|
# zo'n .pyc meegegaan in een commit.
|
||||||
sys.dont_write_bytecode = True
|
sys.dont_write_bytecode = True
|
||||||
|
|
||||||
import importlib.machinery # noqa: E402
|
import importlib.machinery # noqa: E402
|
||||||
@@ -33,17 +35,13 @@ import subprocess # noqa: E402
|
|||||||
import tempfile # noqa: E402
|
import tempfile # noqa: E402
|
||||||
|
|
||||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
TEMPLATE = os.path.join(HERE, os.pardir, "whatsnext-electrum-gate", "agent.py.template")
|
AGENT = os.path.join(HERE, os.pardir, "tools", "electrum-gate", "agent.py")
|
||||||
|
|
||||||
|
|
||||||
def load_agent():
|
def load_agent():
|
||||||
"""Laadt agent.py.template als module.
|
"""Laadt agent.py als module, buiten sys.path om."""
|
||||||
|
loader = importlib.machinery.SourceFileLoader("gate_agent", AGENT)
|
||||||
Met een expliciete loader, want importlib kijkt normaal naar de extensie en
|
spec = importlib.util.spec_from_file_location("gate_agent", AGENT, loader=loader)
|
||||||
.template staat daar niet tussen.
|
|
||||||
"""
|
|
||||||
loader = importlib.machinery.SourceFileLoader("gate_agent", TEMPLATE)
|
|
||||||
spec = importlib.util.spec_from_file_location("gate_agent", TEMPLATE, loader=loader)
|
|
||||||
module = importlib.util.module_from_spec(spec)
|
module = importlib.util.module_from_spec(spec)
|
||||||
spec.loader.exec_module(module)
|
spec.loader.exec_module(module)
|
||||||
return module
|
return module
|
||||||
@@ -83,12 +81,18 @@ def nep_certificaat(tijdtag, tijdtekst):
|
|||||||
|
|
||||||
|
|
||||||
def test_template_is_invulbaar_zonder_schade(u):
|
def test_template_is_invulbaar_zonder_schade(u):
|
||||||
"""De aanname waar deze hele test op rust."""
|
"""Een restant uit de template-tijd dat blijft staan.
|
||||||
with open(TEMPLATE, "r", encoding="utf-8") as f:
|
|
||||||
|
Het bestand zit sinds 0.1.0 in de image en gaat door geen envsubst meer. Maar
|
||||||
|
een accolade-variabele in Python-code is ook zonder umbreld een fout, en als
|
||||||
|
iemand dit bestand ooit weer als template in een app-map zet, is dit de toets
|
||||||
|
die het meteen ziet.
|
||||||
|
"""
|
||||||
|
with open(AGENT, "r", encoding="utf-8") as f:
|
||||||
inhoud = f.read()
|
inhoud = f.read()
|
||||||
u.check("template bevat geen accolade-variabelen",
|
u.check("agent.py bevat geen accolade-variabelen",
|
||||||
"${" not in inhoud,
|
"${" not in inhoud,
|
||||||
"umbreld zou die invullen en de Python-code slopen")
|
"een envsubst-stap zou die invullen en de Python-code slopen")
|
||||||
|
|
||||||
|
|
||||||
def test_einddatum_gelijk_aan_ssl(agent, u):
|
def test_einddatum_gelijk_aan_ssl(agent, u):
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ Wat deze toetsen expres NIET doen: eisen dat elke image op een digest gepind is.
|
|||||||
Dat is wél de regel, maar op 25-08-2026 haalt geen van de twee apps hem, en een
|
Dat is wél de regel, maar op 25-08-2026 haalt geen van de twee apps hem, en een
|
||||||
suite die altijd rood staat wordt niet gelezen. Het staat als taak in de plannen
|
suite die altijd rood staat wordt niet gelezen. Het staat als taak in de plannen
|
||||||
Umbrelapp en Publicatie-Gate. Wat hier wel gebeurt is de pinstatus afdrukken, zodat
|
Umbrelapp en Publicatie-Gate. Wat hier wel gebeurt is de pinstatus afdrukken, zodat
|
||||||
je hem ziet zonder erover te struikelen.
|
je hem ziet zonder erover te struikelen. Wat wél getoetst wordt, sinds 07-09-2026:
|
||||||
|
dat de tag van een eigen image gelijk is aan VERSION in het bijbehorende
|
||||||
|
tools/<app>/build.sh, want een tag die nooit gebouwd is faalt pas op het apparaat.
|
||||||
|
|
||||||
Deze toetsen vinden hun apps zelf. Komt er een derde app bij, dan valt die
|
Deze toetsen vinden hun apps zelf. Komt er een derde app bij, dan valt die
|
||||||
automatisch onder alles hierboven en hoeft hier niets bij.
|
automatisch onder alles hierboven en hoeft hier niets bij.
|
||||||
@@ -172,6 +174,61 @@ def test_manifest_volgorde(u, app):
|
|||||||
not extra_ertussen, "ertussen: %r" % extra_ertussen)
|
not extra_ertussen, "ertussen: %r" % extra_ertussen)
|
||||||
|
|
||||||
|
|
||||||
|
def recept_map(app):
|
||||||
|
"""Waar het bouwrecept van een app staat: tools/<app-id zonder store-id>/.
|
||||||
|
|
||||||
|
Dat is de afspraak uit CLAUDE.md: een Dockerfile hoort niet in de app-map,
|
||||||
|
want die staat niet in de update-whitelist. De mapnaam is het app-id zonder
|
||||||
|
het store-voorvoegsel, dus whatsnext-evolu-relay bouwt in tools/evolu-relay/.
|
||||||
|
"""
|
||||||
|
kort = app.split("-", 1)[1] if "-" in app else app
|
||||||
|
return os.path.join(REPO, "tools", kort)
|
||||||
|
|
||||||
|
|
||||||
|
def test_image_tag_is_de_gebouwde(u, app):
|
||||||
|
"""De tag in de compose is VERSION uit build.sh, voor elke eigen image.
|
||||||
|
|
||||||
|
Een tag die in de compose staat maar nooit gebouwd is, faalt pas op het
|
||||||
|
apparaat met "pull access denied". En andersom, een VERSION die verhoogd is
|
||||||
|
zonder de compose mee te nemen, levert een nieuwe image en een oude
|
||||||
|
installatie zonder dat iets dat meldt. Beide staan als regel in CLAUDE.md;
|
||||||
|
dit is de toets erop.
|
||||||
|
|
||||||
|
Alleen images uit het eigen recept: een vreemde image (postgres, nginx) heeft
|
||||||
|
geen build.sh en valt hier buiten.
|
||||||
|
"""
|
||||||
|
build = os.path.join(recept_map(app), "build.sh")
|
||||||
|
if not os.path.isfile(build):
|
||||||
|
return
|
||||||
|
|
||||||
|
versie = None
|
||||||
|
for regel in lees(build).splitlines():
|
||||||
|
if regel.startswith("VERSION="):
|
||||||
|
versie = regel.split("=", 1)[1].strip().strip('"')
|
||||||
|
u.check("%s: build.sh heeft een VERSION" % app, bool(versie))
|
||||||
|
if not versie:
|
||||||
|
return
|
||||||
|
|
||||||
|
kort = os.path.basename(recept_map(app))
|
||||||
|
compose = lees(os.path.join(REPO, app, "docker-compose.yml"))
|
||||||
|
eigen = []
|
||||||
|
for regel in compose.splitlines():
|
||||||
|
kaal = regel.strip()
|
||||||
|
if not kaal.startswith("image:"):
|
||||||
|
continue
|
||||||
|
image = kaal.split(":", 1)[1].strip()
|
||||||
|
repo_en_tag = image.split("@", 1)[0]
|
||||||
|
repo_naam = repo_en_tag.rsplit(":", 1)[0]
|
||||||
|
if repo_naam.endswith("/" + kort):
|
||||||
|
eigen.append(repo_en_tag)
|
||||||
|
|
||||||
|
u.check("%s: de compose gebruikt de eigen image" % app, bool(eigen),
|
||||||
|
"geen image op .../%s gevonden" % kort)
|
||||||
|
verkeerd = [i for i in eigen if not i.endswith(":" + versie)]
|
||||||
|
u.check("%s: elke eigen image heeft tag %s uit build.sh" % (app, versie),
|
||||||
|
not verkeerd, "gevonden: %r" % verkeerd)
|
||||||
|
|
||||||
|
|
||||||
def test_compose_bestaat_en_hangt_samen(u, app):
|
def test_compose_bestaat_en_hangt_samen(u, app):
|
||||||
"""De app_proxy moet naar een service wijzen die bestaat.
|
"""De app_proxy moet naar een service wijzen die bestaat.
|
||||||
|
|
||||||
@@ -285,14 +342,25 @@ def test_containernamen_zijn_volledig(u, app):
|
|||||||
|
|
||||||
De vorm <app-id>_<service>_1 is wél uniek. Deze toets kijkt naar de plekken
|
De vorm <app-id>_<service>_1 is wél uniek. Deze toets kijkt naar de plekken
|
||||||
waar een naam als hostnaam wordt gebruikt: proxy_pass in een nginx-config, en
|
waar een naam als hostnaam wordt gebruikt: proxy_pass in een nginx-config, en
|
||||||
de omgevingsvariabelen die een hostnaam dragen.
|
de omgevingsvariabelen die een hostnaam dragen. Een nginx-config kan in de
|
||||||
|
app-map staan (als *.template) of in het bouwrecept onder tools/ (als hij in
|
||||||
|
een eigen image zit); beide worden gelezen.
|
||||||
"""
|
"""
|
||||||
fout = []
|
fout = []
|
||||||
|
|
||||||
|
bestanden = []
|
||||||
for bestandsnaam in sorted(os.listdir(os.path.join(REPO, app))):
|
for bestandsnaam in sorted(os.listdir(os.path.join(REPO, app))):
|
||||||
if not bestandsnaam.endswith(".template") and bestandsnaam != "docker-compose.yml":
|
if bestandsnaam.endswith(".template") or bestandsnaam == "docker-compose.yml":
|
||||||
continue
|
bestanden.append(os.path.join(REPO, app, bestandsnaam))
|
||||||
tekst = lees(os.path.join(REPO, app, bestandsnaam))
|
recept = recept_map(app)
|
||||||
|
if os.path.isdir(recept):
|
||||||
|
for bestandsnaam in sorted(os.listdir(recept)):
|
||||||
|
if bestandsnaam.endswith(".conf"):
|
||||||
|
bestanden.append(os.path.join(recept, bestandsnaam))
|
||||||
|
|
||||||
|
for pad in bestanden:
|
||||||
|
bestandsnaam = os.path.relpath(pad, REPO).replace(os.sep, "/")
|
||||||
|
tekst = lees(pad)
|
||||||
for regel in tekst.splitlines():
|
for regel in tekst.splitlines():
|
||||||
kaal = regel.strip()
|
kaal = regel.strip()
|
||||||
if kaal.startswith("#"):
|
if kaal.startswith("#"):
|
||||||
@@ -464,6 +532,7 @@ def main():
|
|||||||
test_verplichte_velden(u, app)
|
test_verplichte_velden(u, app)
|
||||||
test_manifest_volgorde(u, app)
|
test_manifest_volgorde(u, app)
|
||||||
test_compose_bestaat_en_hangt_samen(u, app)
|
test_compose_bestaat_en_hangt_samen(u, app)
|
||||||
|
test_image_tag_is_de_gebouwde(u, app)
|
||||||
test_data_onder_data(u, app)
|
test_data_onder_data(u, app)
|
||||||
test_containernamen_zijn_volledig(u, app)
|
test_containernamen_zijn_volledig(u, app)
|
||||||
test_geen_werkbestanden_in_de_app_map(u, app)
|
test_geen_werkbestanden_in_de_app_map(u, app)
|
||||||
|
|||||||
@@ -99,14 +99,35 @@ const parseert = (bron) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waar de pagina van een app staat, en of umbreld hem invult.
|
||||||
|
*
|
||||||
|
* Twee plekken: als *.template in de app-map (dan gaat hij door envsubst), of als
|
||||||
|
* gewoon bestand in het bouwrecept onder tools/<app-id zonder store-id>/ (dan zit
|
||||||
|
* hij in een eigen image en wordt hij door niemand ingevuld). Electrum Gate is
|
||||||
|
* sinds 0.1.0 het tweede geval; zie het plan Eigenimage.
|
||||||
|
*/
|
||||||
|
const vindPagina = (app) => {
|
||||||
|
const template = join(REPO, app, 'index.html.template');
|
||||||
|
if (existsSync(template)) {
|
||||||
|
return { pad: template, template: true };
|
||||||
|
}
|
||||||
|
const kort = app.includes('-') ? app.slice(app.indexOf('-') + 1) : app;
|
||||||
|
const inImage = join(REPO, 'tools', kort, 'index.html');
|
||||||
|
if (existsSync(inImage)) {
|
||||||
|
return { pad: inImage, template: false };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
for (const app of appMappen()) {
|
for (const app of appMappen()) {
|
||||||
const pad = join(REPO, app, 'index.html.template');
|
const pagina = vindPagina(app);
|
||||||
if (!existsSync(pad)) {
|
if (pagina === null) {
|
||||||
// Geen pagina is geen fout: een app hoeft er geen te hebben.
|
// Geen pagina is geen fout: een app hoeft er geen te hebben.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bron = readFileSync(pad, 'utf8');
|
const bron = readFileSync(pagina.pad, 'utf8');
|
||||||
const blokken = scriptBlokken(bron);
|
const blokken = scriptBlokken(bron);
|
||||||
|
|
||||||
toets(`${app}: de pagina heeft een scriptblok`, blokken.length > 0);
|
toets(`${app}: de pagina heeft een scriptblok`, blokken.length > 0);
|
||||||
@@ -117,10 +138,19 @@ for (const app of appMappen()) {
|
|||||||
const rauw = parseert(blok);
|
const rauw = parseert(blok);
|
||||||
toets(`${naam} parseert zoals hij in de repo staat`, rauw === null, rauw);
|
toets(`${naam} parseert zoals hij in de repo staat`, rauw === null, rauw);
|
||||||
|
|
||||||
|
if (pagina.template) {
|
||||||
const ingevuld = parseert(vulIn(blok));
|
const ingevuld = parseert(vulIn(blok));
|
||||||
toets(`${naam} parseert ook nadat umbreld hem invult`, ingevuld === null, ingevuld);
|
toets(`${naam} parseert ook nadat umbreld hem invult`, ingevuld === null, ingevuld);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!pagina.template) {
|
||||||
|
// Een pagina in een image gaat door geen envsubst meer, dus wat er staat is
|
||||||
|
// wat de browser krijgt. Een accolade-variabele is dan een restant uit de
|
||||||
|
// template-tijd die letterlijk op de pagina zou belanden.
|
||||||
|
toets(`${app}: de pagina in de image bevat geen accolade-variabele`, !bron.includes('${'));
|
||||||
|
}
|
||||||
|
|
||||||
// De HTML zelf: geen parser, maar wel de fout die je in een bestand van
|
// De HTML zelf: geen parser, maar wel de fout die je in een bestand van
|
||||||
// tweeduizend regels echt maakt. Een dialog of een header die niet gesloten is,
|
// tweeduizend regels echt maakt. Een dialog of een header die niet gesloten is,
|
||||||
// laat de rest van de pagina in dat element hangen.
|
// laat de rest van de pagina in dat element hangen.
|
||||||
|
|||||||
@@ -9,18 +9,25 @@ niets. De symptomen wezen ergens anders heen: de app_proxy meldde alleen dat de
|
|||||||
server niet te bereiken was.
|
server niet te bereiken was.
|
||||||
|
|
||||||
De reparatie is een verplaatsing, en die is niet aan de code af te lezen: het
|
De reparatie is een verplaatsing, en die is niet aan de code af te lezen: het
|
||||||
stream-blok staat nu in stream.conf.template en wordt door het command-blok van
|
stream-blok staat in stream.conf en wordt door entrypoint.sh pas in
|
||||||
de compose pas in /var/lib/gate/tls/ gezet als cert.conf bestaat. nginx.conf
|
/var/lib/gate/tls/ gezet als cert.conf bestaat. nginx.conf haalt die map op met
|
||||||
haalt die map op met een jokerteken, en een jokerteken dat niets matcht is voor
|
een jokerteken, en een jokerteken dat niets matcht is voor nginx geen fout. Eén
|
||||||
nginx geen fout. Eén iemand die dat blok "netjes" terugzet in nginx.conf en de
|
iemand die dat blok "netjes" terugzet in nginx.conf en de klem is terug, zonder
|
||||||
klem is terug, zonder dat een van de andere toetsen iets merkt.
|
dat een van de andere toetsen iets merkt.
|
||||||
|
|
||||||
|
Sinds 0.1.0 zitten deze bestanden in een eigen image en staan ze in
|
||||||
|
tools/electrum-gate/; tot dan waren het *.template-bestanden in de app-map en
|
||||||
|
was het startscript een command-blok in de compose. De toetsen zijn mee
|
||||||
|
verhuisd en toetsen hetzelfde gedrag, plus wat er bij die verhuizing nieuw
|
||||||
|
kapot kan: de Dockerfile die een bestand niet meeneemt, en de plaatshouders in
|
||||||
|
stream.conf die entrypoint.sh moet invullen.
|
||||||
|
|
||||||
Draaien:
|
Draaien:
|
||||||
|
|
||||||
python tests/test_server_start_zonder_certificaat.py
|
python tests/test_server_start_zonder_certificaat.py
|
||||||
|
|
||||||
Er staan inmiddels een paar toetsen bij die niet over het starten gaan maar wel
|
Er staan een paar toetsen bij die niet over het starten gaan maar wel over
|
||||||
over dezelfde soort fout: een die niets meldt en pas opvalt als iemand het toevallig
|
dezelfde soort fout: een die niets meldt en pas opvalt als iemand het toevallig
|
||||||
ziet. De verbindingsteller die stil nul telt, een mount die weer alleen-lezen wordt,
|
ziet. De verbindingsteller die stil nul telt, een mount die weer alleen-lezen wordt,
|
||||||
en twee taglines die uit elkaar lopen. Ze staan hier en niet in een derde bestand,
|
en twee taglines die uit elkaar lopen. Ze staan hier en niet in een derde bestand,
|
||||||
want de suite heeft geen runner en een bestand dat niemand aanroept toetst niets.
|
want de suite heeft geen runner en een bestand dat niemand aanroept toetst niets.
|
||||||
@@ -31,15 +38,20 @@ import sys
|
|||||||
sys.dont_write_bytecode = True
|
sys.dont_write_bytecode = True
|
||||||
|
|
||||||
import os # noqa: E402
|
import os # noqa: E402
|
||||||
|
import re # noqa: E402
|
||||||
|
|
||||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
APP = os.path.join(HERE, os.pardir, "whatsnext-electrum-gate")
|
APP = os.path.join(HERE, os.pardir, "whatsnext-electrum-gate")
|
||||||
|
RECEPT = os.path.join(HERE, os.pardir, "tools", "electrum-gate")
|
||||||
|
|
||||||
NGINX_CONF = os.path.join(APP, "nginx.conf.template")
|
NGINX_CONF = os.path.join(RECEPT, "nginx.conf")
|
||||||
STREAM_CONF = os.path.join(APP, "stream.conf.template")
|
STREAM_CONF = os.path.join(RECEPT, "stream.conf")
|
||||||
|
ENTRYPOINT = os.path.join(RECEPT, "entrypoint.sh")
|
||||||
|
DOCKERFILE = os.path.join(RECEPT, "Dockerfile")
|
||||||
|
BUILD = os.path.join(RECEPT, "build.sh")
|
||||||
|
PAGINA = os.path.join(RECEPT, "index.html")
|
||||||
COMPOSE = os.path.join(APP, "docker-compose.yml")
|
COMPOSE = os.path.join(APP, "docker-compose.yml")
|
||||||
MANIFEST = os.path.join(APP, "umbrel-app.yml")
|
MANIFEST = os.path.join(APP, "umbrel-app.yml")
|
||||||
PAGINA = os.path.join(APP, "index.html.template")
|
|
||||||
|
|
||||||
|
|
||||||
class Uitslag:
|
class Uitslag:
|
||||||
@@ -67,7 +79,7 @@ def lees(pad):
|
|||||||
|
|
||||||
|
|
||||||
def zonder_commentaar(tekst):
|
def zonder_commentaar(tekst):
|
||||||
"""De regels die nginx daadwerkelijk uitvoert.
|
"""De regels die nginx of de shell daadwerkelijk uitvoert.
|
||||||
|
|
||||||
Zonder dit zou elke toets hieronder afgaan op de uitleg erboven, en die
|
Zonder dit zou elke toets hieronder afgaan op de uitleg erboven, en die
|
||||||
noemt juist de dingen die er niet meer mogen staan.
|
noemt juist de dingen die er niet meer mogen staan.
|
||||||
@@ -104,64 +116,71 @@ def test_pagina_hangt_niet_aan_een_certificaat(u):
|
|||||||
|
|
||||||
|
|
||||||
def test_stream_blok_staat_apart(u):
|
def test_stream_blok_staat_apart(u):
|
||||||
"""Het TLS-deel is compleet, en het is een template."""
|
"""Het TLS-deel is compleet, en het backend-adres komt uit de omgeving."""
|
||||||
u.check("stream.conf.template bestaat", os.path.isfile(STREAM_CONF),
|
u.check("stream.conf bestaat", os.path.isfile(STREAM_CONF))
|
||||||
"anders komt het bij een update niet mee: alleen *.template, "
|
|
||||||
"docker-compose.yml, exports.sh, torrc, hooks en umbrel-app.yml "
|
|
||||||
"worden ververst")
|
|
||||||
if not os.path.isfile(STREAM_CONF):
|
if not os.path.isfile(STREAM_CONF):
|
||||||
return
|
return
|
||||||
|
|
||||||
tekst = lees(STREAM_CONF)
|
regels = zonder_commentaar(lees(STREAM_CONF))
|
||||||
regels = zonder_commentaar(tekst)
|
|
||||||
|
|
||||||
u.check("stream.conf luistert op 50022 met ssl",
|
u.check("stream.conf luistert op 50022 met ssl",
|
||||||
any("listen 50022 ssl" in r for r in regels))
|
any("listen 50022 ssl" in r for r in regels))
|
||||||
u.check("stream.conf includet de cert.conf van de agent",
|
u.check("stream.conf includet de cert.conf van de agent",
|
||||||
any("include /var/lib/gate/cert.conf" in r for r in regels))
|
any("include /var/lib/gate/cert.conf" in r for r in regels))
|
||||||
u.check("stream.conf includet het log_format",
|
u.check("stream.conf heeft zijn log_format zelf",
|
||||||
any("include /var/lib/gate/stream-log.conf" in r for r in regels))
|
any(r.startswith("log_format gate") for r in regels),
|
||||||
u.check("stream.conf verwijst naar de backend uit de omgeving",
|
"tot 0.0.29 schreef het command-blok dat in een los bestand, omdat "
|
||||||
any("proxy_pass ${APP_ELECTRS_NODE_IP}" in r for r in regels))
|
"een dollarteken in een template leeggemaakt werd; dat hoeft niet meer")
|
||||||
|
u.check("en schrijft de sessielog waar de agent hem leest",
|
||||||
|
any(r.startswith("access_log /var/lib/gate/stream.log") for r in regels))
|
||||||
|
|
||||||
# Dezelfde architectuurregel als voor de andere templates: umbreld vult elke
|
# Het adres van de backend staat niet in dit bestand maar komt uit de
|
||||||
# accolade-variabele in, dus een nginx-variabele met een dollarteken wordt
|
# omgeving, en de weg daarheen is een plaatshouder die entrypoint.sh invult.
|
||||||
# hier stil leeggemaakt. Vandaar dat het log_format uit een ander bestand
|
# Geen dollarteken in die plaatshouder: dan kan geen envsubst-stap, van
|
||||||
# komt.
|
# umbreld of van wie dan ook, hem stil leegmaken.
|
||||||
losse_dollars = []
|
proxy = [r for r in regels if r.startswith("proxy_pass")]
|
||||||
for regel in regels:
|
u.check("stream.conf heeft precies één proxy_pass", len(proxy) == 1,
|
||||||
pos = regel.find("$")
|
"gevonden: %r" % proxy)
|
||||||
while pos >= 0:
|
u.check("en die verwijst naar de plaatshouders voor host en poort",
|
||||||
if regel[pos + 1:pos + 2] != "{":
|
proxy and "__GATE_ELECTRUM_HOST__:__GATE_ELECTRUM_PORT__" in proxy[0],
|
||||||
losse_dollars.append(regel)
|
"gevonden: %r" % proxy)
|
||||||
break
|
|
||||||
pos = regel.find("$", pos + 1)
|
u.check("stream.conf bevat geen accolade-variabele meer",
|
||||||
u.check("stream.conf heeft geen nginx-variabelen",
|
"${" not in lees(STREAM_CONF),
|
||||||
not losse_dollars,
|
"die wordt sinds de eigen image door niemand meer ingevuld en zou "
|
||||||
"de template-invulling maakt die leeg: %r" % losse_dollars[:2])
|
"letterlijk in de configuratie belanden")
|
||||||
|
|
||||||
|
|
||||||
def test_compose_wacht_niet_op_de_agent(u):
|
def test_entrypoint_wacht_niet_op_de_agent(u):
|
||||||
"""Het command-blok start nginx altijd, en zet TLS erbij als het kan."""
|
"""entrypoint.sh start nginx altijd, en zet TLS erbij als het kan."""
|
||||||
tekst = lees(COMPOSE)
|
u.check("entrypoint.sh bestaat", os.path.isfile(ENTRYPOINT))
|
||||||
|
if not os.path.isfile(ENTRYPOINT):
|
||||||
|
return
|
||||||
|
|
||||||
|
tekst = lees(ENTRYPOINT)
|
||||||
regels = zonder_commentaar(tekst)
|
regels = zonder_commentaar(tekst)
|
||||||
|
|
||||||
u.check("geen wachtlus op cert.conf",
|
u.check("geen wachtlus op cert.conf",
|
||||||
not any("! -f /var/lib/gate/cert.conf" in r and "while" in r
|
not any("! -f" in r and "cert.conf" in r and "while" in r
|
||||||
for r in regels),
|
for r in regels),
|
||||||
"dat is precies de klem uit 0.0.3: geen certificaat, geen nginx, "
|
"dat is precies de klem uit 0.0.3: geen certificaat, geen nginx, "
|
||||||
"geen pagina, dus geen manier om een certificaat te kiezen")
|
"geen pagina, dus geen manier om een certificaat te kiezen")
|
||||||
|
|
||||||
u.check("stream.conf is in de server gemount",
|
u.check("het stream-blok wordt ingevuld uit de omgeving",
|
||||||
any("stream.conf:/etc/nginx/stream.conf" in r for r in regels),
|
any("__GATE_ELECTRUM_HOST__" in r and "GATE_ELECTRUM_HOST" in r
|
||||||
"anders is er niets om in /var/lib/gate/tls/ te zetten")
|
for r in regels)
|
||||||
|
and any("__GATE_ELECTRUM_PORT__" in r for r in regels),
|
||||||
|
"anders staat de plaatshouder letterlijk in de proxy_pass")
|
||||||
|
|
||||||
|
u.check("en wordt uit stream.conf.in gelezen, zoals de Dockerfile hem neerzet",
|
||||||
|
any("/etc/nginx/stream.conf.in" in r for r in regels))
|
||||||
|
|
||||||
u.check("het stream-blok wordt weggezet als cert.conf bestaat",
|
u.check("het stream-blok wordt weggezet als cert.conf bestaat",
|
||||||
any("cp /etc/nginx/stream.conf /var/lib/gate/tls/" in r
|
any(r.startswith("if cp /etc/nginx/stream.conf") and "/tls/" in r
|
||||||
for r in regels))
|
for r in regels))
|
||||||
|
|
||||||
u.check("en weggehaald als cert.conf er niet is",
|
u.check("en weggehaald als cert.conf er niet is",
|
||||||
any("rm -f /var/lib/gate/tls/stream.conf" in r for r in regels),
|
any(r.startswith("rm -f") and "/tls/stream.conf" in r for r in regels),
|
||||||
"een ingetrokken keuze moet de poort ook echt sluiten")
|
"een ingetrokken keuze moet de poort ook echt sluiten")
|
||||||
|
|
||||||
u.check("een mislukte herlading breekt de wachtlus niet af",
|
u.check("een mislukte herlading breekt de wachtlus niet af",
|
||||||
@@ -169,6 +188,119 @@ def test_compose_wacht_niet_op_de_agent(u):
|
|||||||
"onder 'set -e' zou de lus verdwijnen en daarna pikt niets meer "
|
"onder 'set -e' zou de lus verdwijnen en daarna pikt niets meer "
|
||||||
"een wijziging op")
|
"een wijziging op")
|
||||||
|
|
||||||
|
u.check("nginx wordt met exec het hoofdproces",
|
||||||
|
regels and regels[-1].startswith("exec nginx"),
|
||||||
|
"anders blijft de shell PID 1, geeft signalen niet door, en ziet "
|
||||||
|
"Docker een geslaagde afsluiting als nginx omvalt; laatste regel: %r"
|
||||||
|
% (regels[-1] if regels else None))
|
||||||
|
|
||||||
|
u.check("het script begint met een shebang",
|
||||||
|
tekst.startswith("#!/bin/sh"))
|
||||||
|
|
||||||
|
u.check("geen verdubbelde dollartekens meer",
|
||||||
|
"$$" not in "\n".join(regels),
|
||||||
|
"dat was de ontsnapping voor het command-blok in de compose; in een "
|
||||||
|
"gewoon script is $$ het proces-id")
|
||||||
|
|
||||||
|
|
||||||
|
def test_dockerfile_neemt_alles_mee(u):
|
||||||
|
"""Wat entrypoint.sh en nginx.conf verwachten, zet de Dockerfile ook neer.
|
||||||
|
|
||||||
|
Dit is de nieuwe klasse fouten van de eigen image: een bestand dat in de
|
||||||
|
repo staat maar niet in de image, en dan faalt de container pas op het
|
||||||
|
apparaat. Geen Docker nodig om dat te zien; de paden staan in de tekst.
|
||||||
|
"""
|
||||||
|
u.check("Dockerfile bestaat", os.path.isfile(DOCKERFILE))
|
||||||
|
if not os.path.isfile(DOCKERFILE):
|
||||||
|
return
|
||||||
|
|
||||||
|
regels = zonder_commentaar(lees(DOCKERFILE))
|
||||||
|
copies = [r for r in regels if r.startswith("COPY ")]
|
||||||
|
|
||||||
|
def kopieert(bron, doel):
|
||||||
|
return any(bron in r and r.endswith(" " + doel) for r in copies)
|
||||||
|
|
||||||
|
u.check("nginx.conf gaat naar /etc/nginx/nginx.conf",
|
||||||
|
kopieert("tools/electrum-gate/nginx.conf", "/etc/nginx/nginx.conf"))
|
||||||
|
u.check("stream.conf gaat naar /etc/nginx/stream.conf.in",
|
||||||
|
kopieert("tools/electrum-gate/stream.conf", "/etc/nginx/stream.conf.in"),
|
||||||
|
"entrypoint.sh leest dat pad en vult het in naar stream.conf")
|
||||||
|
u.check("de pagina gaat naar de webroot",
|
||||||
|
kopieert("tools/electrum-gate/index.html", "/usr/share/nginx/html/index.html"))
|
||||||
|
u.check("het icoon komt uit de app-map",
|
||||||
|
kopieert("whatsnext-electrum-gate/icon.png", "/usr/share/nginx/html/icon.png"),
|
||||||
|
"de app-map houdt het origineel omdat het manifest ernaar wijst; "
|
||||||
|
"de image kopieert het, zodat er niet twee exemplaren zijn")
|
||||||
|
u.check("de agent gaat naar /app/agent.py",
|
||||||
|
kopieert("tools/electrum-gate/agent.py", "/app/agent.py"),
|
||||||
|
"de compose start hem op dat pad")
|
||||||
|
u.check("entrypoint.sh gaat mee en wordt uitvoerbaar",
|
||||||
|
kopieert("tools/electrum-gate/entrypoint.sh", "/entrypoint.sh")
|
||||||
|
and any(r.startswith("RUN chmod") and "/entrypoint.sh" in r for r in regels),
|
||||||
|
"git op Windows kent geen uitvoerbit, dus die moet de Dockerfile zetten")
|
||||||
|
u.check("python3 wordt geïnstalleerd",
|
||||||
|
any(r.startswith("RUN apk add") and "python3" in r for r in regels))
|
||||||
|
u.check("de basisimage is nginx op een vaste tag",
|
||||||
|
any(re.match(r"FROM nginx:\d+\.\d+-alpine$", r) for r in regels),
|
||||||
|
"nginx:alpine beweegt mee en is ook mét digest niet toegestaan; "
|
||||||
|
"zie Docs/Referenties/Images-pinnen.md")
|
||||||
|
u.check("het entrypoint van de nginx-image is uitgeschakeld",
|
||||||
|
"ENTRYPOINT []" in regels,
|
||||||
|
"anders draait het envsubst-script van die image mee, en dat is "
|
||||||
|
"precies het soort stille invulling dat dit project vreest")
|
||||||
|
|
||||||
|
# De compose noemt paden in de image; die moeten er zijn.
|
||||||
|
compose = zonder_commentaar(lees(COMPOSE))
|
||||||
|
u.check("de compose start de agent op het pad waar de Dockerfile hem neerzet",
|
||||||
|
any(r == "- /app/agent.py" for r in compose))
|
||||||
|
|
||||||
|
|
||||||
|
def test_compose_en_recept_lopen_gelijk(u):
|
||||||
|
"""Dezelfde image in beide services, met de tag uit build.sh.
|
||||||
|
|
||||||
|
Twee services die elk hun eigen tag hebben lopen uit elkaar bij de eerste
|
||||||
|
verhoging die iemand op één plek doet. En een tag die niet gelijk is aan
|
||||||
|
VERSION in build.sh betekent een image die nooit gebouwd is.
|
||||||
|
"""
|
||||||
|
compose = zonder_commentaar(lees(COMPOSE))
|
||||||
|
images = [r.split(":", 1)[1].strip() for r in compose if r.startswith("image:")]
|
||||||
|
u.check("de compose heeft twee images", len(images) == 2, "gevonden: %r" % images)
|
||||||
|
|
||||||
|
tags = set()
|
||||||
|
for image in images:
|
||||||
|
zonder_digest = image.split("@", 1)[0]
|
||||||
|
tags.add(zonder_digest)
|
||||||
|
u.check("en het is twee keer dezelfde", len(tags) == 1, "gevonden: %r" % sorted(tags))
|
||||||
|
|
||||||
|
versie = None
|
||||||
|
for regel in zonder_commentaar(lees(BUILD)):
|
||||||
|
if regel.startswith("VERSION="):
|
||||||
|
versie = regel.split("=", 1)[1].strip().strip('"')
|
||||||
|
u.check("build.sh heeft een VERSION", bool(versie))
|
||||||
|
if versie and tags:
|
||||||
|
tag = sorted(tags)[0]
|
||||||
|
u.check("de tag in de compose is VERSION uit build.sh",
|
||||||
|
tag.endswith(":" + versie),
|
||||||
|
"compose %r, build.sh %r" % (tag, versie))
|
||||||
|
|
||||||
|
# Wat er niet meer gemount mag worden: de programmabestanden. Zit er weer
|
||||||
|
# een mount van een los bestand uit de app-map in, dan is dat een terugval
|
||||||
|
# naar de template-tijd en bereikt een wijziging het apparaat via twee wegen.
|
||||||
|
losse = [r for r in compose if r.startswith("- ${APP_DATA_DIR}/")
|
||||||
|
and "/" not in r[len("- ${APP_DATA_DIR}/"):].split(":", 1)[0]]
|
||||||
|
u.check("geen losse bestanden uit de app-map meer gemount",
|
||||||
|
not losse, "gevonden: %r" % losse)
|
||||||
|
|
||||||
|
u.check("de server krijgt het backend-adres uit de omgeving",
|
||||||
|
compose.count("GATE_ELECTRUM_HOST: ${APP_ELECTRS_NODE_IP}") == 2
|
||||||
|
and compose.count("GATE_ELECTRUM_PORT: ${APP_ELECTRS_NODE_PORT}") == 2,
|
||||||
|
"agent én server hebben het nodig; entrypoint.sh vult er het "
|
||||||
|
"stream-blok mee in")
|
||||||
|
|
||||||
|
u.check("de agent krijgt de versie van het manifest door",
|
||||||
|
"GATE_APP_VERSION: ${APP_VERSION}" in compose,
|
||||||
|
"anders toont de kop van de pagina geen versie")
|
||||||
|
|
||||||
|
|
||||||
def test_uploadpad(u):
|
def test_uploadpad(u):
|
||||||
"""De drie dingen buiten de agent die uploaden mogelijk maken.
|
"""De drie dingen buiten de agent die uploaden mogelijk maken.
|
||||||
@@ -208,13 +340,12 @@ def test_uploadpad(u):
|
|||||||
def test_verbindingsteller(u):
|
def test_verbindingsteller(u):
|
||||||
"""De teller in de nginx-container, en de poort in hex.
|
"""De teller in de nginx-container, en de poort in hex.
|
||||||
|
|
||||||
Hoort hier omdat het om hetzelfde `command`-blok gaat. Het gevaarlijke deel
|
Het gevaarlijke deel is de hex: de teller zoekt in /proc/net/tcp op de poort
|
||||||
is de hex: de teller zoekt in /proc/net/tcp op de poort in hexadecimale vorm,
|
in hexadecimale vorm, en verandert de TLS-poort ooit, dan telt hij stil nul.
|
||||||
en verandert de TLS-poort ooit, dan telt hij stil nul. Een teller die altijd
|
Een teller die altijd nul zegt, ziet eruit als "geen wallet verbonden" en
|
||||||
nul zegt, ziet eruit als "geen wallet verbonden" en niet als een fout.
|
niet als een fout.
|
||||||
"""
|
"""
|
||||||
tekst = lees(COMPOSE)
|
regels = zonder_commentaar(lees(ENTRYPOINT))
|
||||||
regels = zonder_commentaar(tekst)
|
|
||||||
|
|
||||||
teller = [r for r in regels if "/proc/net/tcp" in r]
|
teller = [r for r in regels if "/proc/net/tcp" in r]
|
||||||
u.check("de teller leest /proc/net/tcp", bool(teller),
|
u.check("de teller leest /proc/net/tcp", bool(teller),
|
||||||
@@ -222,11 +353,12 @@ def test_verbindingsteller(u):
|
|||||||
"staan; de agent zit in een andere netwerk-namespace")
|
"staan; de agent zit in een andere netwerk-namespace")
|
||||||
|
|
||||||
u.check("en schrijft het aantal weg voor de agent",
|
u.check("en schrijft het aantal weg voor de agent",
|
||||||
any("/var/lib/gate/sessions" in r for r in regels))
|
any("/sessions" in r for r in regels))
|
||||||
|
|
||||||
# De poort uit de omgeving van de agent is de waarheid; de teller moet
|
# De poort uit de omgeving van de agent is de waarheid; de teller moet
|
||||||
# dezelfde poort in hex zoeken.
|
# dezelfde poort in hex zoeken.
|
||||||
poorten = [r for r in regels if r.startswith("GATE_TLS_PORT:")]
|
compose = zonder_commentaar(lees(COMPOSE))
|
||||||
|
poorten = [r for r in compose if r.startswith("GATE_TLS_PORT:")]
|
||||||
u.check("GATE_TLS_PORT staat in de compose", len(poorten) == 1,
|
u.check("GATE_TLS_PORT staat in de compose", len(poorten) == 1,
|
||||||
"gevonden: %r" % poorten)
|
"gevonden: %r" % poorten)
|
||||||
if not poorten or not teller:
|
if not poorten or not teller:
|
||||||
@@ -238,9 +370,13 @@ def test_verbindingsteller(u):
|
|||||||
# backslash-vervolg en dan valt de hex op een eigen regel.
|
# backslash-vervolg en dan valt de hex op een eigen regel.
|
||||||
u.check("de teller zoekt de TLS-poort in hex (%d is %s)" % (poort, hexpoort),
|
u.check("de teller zoekt de TLS-poort in hex (%d is %s)" % (poort, hexpoort),
|
||||||
(":" + hexpoort) in " ".join(regels),
|
(":" + hexpoort) in " ".join(regels),
|
||||||
"nergens in het command-blok staat :%s, dus telt de teller nul"
|
"nergens in entrypoint.sh staat :%s, dus telt de teller nul"
|
||||||
% hexpoort)
|
% hexpoort)
|
||||||
|
|
||||||
|
stream = zonder_commentaar(lees(STREAM_CONF))
|
||||||
|
u.check("en stream.conf luistert op diezelfde poort",
|
||||||
|
any(r == "listen %d ssl;" % poort for r in stream))
|
||||||
|
|
||||||
|
|
||||||
def test_data_onder_data(u):
|
def test_data_onder_data(u):
|
||||||
"""Alles wat de app zelf schrijft staat onder `data/`.
|
"""Alles wat de app zelf schrijft staat onder `data/`.
|
||||||
@@ -262,14 +398,9 @@ def test_data_onder_data(u):
|
|||||||
fout = []
|
fout = []
|
||||||
for mount in mounts:
|
for mount in mounts:
|
||||||
host = mount.strip('"').split(":")[0].replace("${APP_DATA_DIR}/", "")
|
host = mount.strip('"').split(":")[0].replace("${APP_DATA_DIR}/", "")
|
||||||
# Een bestand naast de compose mag: dat zijn de ingevulde templates en het
|
|
||||||
# icoon, en die hóren daar omdat de whitelist ze daar verft. Een map die de
|
|
||||||
# app zelf vult, hoort onder data/.
|
|
||||||
if "." in os.path.basename(host):
|
|
||||||
continue
|
|
||||||
if not host.startswith("data/"):
|
if not host.startswith("data/"):
|
||||||
fout.append(host)
|
fout.append(host)
|
||||||
u.check("elke map die de app zelf vult staat onder data/",
|
u.check("elke mount uit de app-map staat onder data/",
|
||||||
not fout, "deze niet: %r" % fout)
|
not fout, "deze niet: %r" % fout)
|
||||||
|
|
||||||
u.check("de gedeelde toestand staat onder data/",
|
u.check("de gedeelde toestand staat onder data/",
|
||||||
@@ -382,17 +513,35 @@ def test_tagline_is_overal_dezelfde(u):
|
|||||||
"de pagina noemt hem niet: %r" % tagline)
|
"de pagina noemt hem niet: %r" % tagline)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pagina_zonder_invulling(u):
|
||||||
|
"""De pagina zit in de image en wordt door niemand meer ingevuld.
|
||||||
|
|
||||||
|
Een ${...} die blijft staan komt letterlijk in de browser terecht. En de twee
|
||||||
|
waarden die umbreld vroeger invulde, de versie en het backend-adres, moeten
|
||||||
|
nu uit status.json komen; anders is de kop leeg en het adres ook.
|
||||||
|
"""
|
||||||
|
tekst = lees(PAGINA)
|
||||||
|
u.check("de pagina bevat geen accolade-variabele", "${" not in tekst)
|
||||||
|
u.check("de kop haalt de versie uit status.json",
|
||||||
|
"s.version" in tekst and 'id="head-version"' in tekst)
|
||||||
|
u.check("het backend-adres komt uit status.json",
|
||||||
|
"backend.address" in tekst)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
u = Uitslag()
|
u = Uitslag()
|
||||||
test_pagina_hangt_niet_aan_een_certificaat(u)
|
test_pagina_hangt_niet_aan_een_certificaat(u)
|
||||||
test_stream_blok_staat_apart(u)
|
test_stream_blok_staat_apart(u)
|
||||||
test_compose_wacht_niet_op_de_agent(u)
|
test_entrypoint_wacht_niet_op_de_agent(u)
|
||||||
|
test_dockerfile_neemt_alles_mee(u)
|
||||||
|
test_compose_en_recept_lopen_gelijk(u)
|
||||||
test_verbindingsteller(u)
|
test_verbindingsteller(u)
|
||||||
test_uploadpad(u)
|
test_uploadpad(u)
|
||||||
test_data_onder_data(u)
|
test_data_onder_data(u)
|
||||||
test_manifest_volgorde(u)
|
test_manifest_volgorde(u)
|
||||||
test_releasenotes_hebben_een_geschiedenis(u)
|
test_releasenotes_hebben_een_geschiedenis(u)
|
||||||
test_tagline_is_overal_dezelfde(u)
|
test_tagline_is_overal_dezelfde(u)
|
||||||
|
test_pagina_zonder_invulling(u)
|
||||||
return u.rapport()
|
return u.rapport()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# De image voor de app whatsnext-electrum-gate.
|
||||||
|
#
|
||||||
|
# Eén image voor twee containers. De compose start hem twee keer: als `server`
|
||||||
|
# met het standaardcommando (entrypoint.sh, dat nginx wordt) en als `agent` met
|
||||||
|
# `python3 /app/agent.py`. Twee images was netter gescheiden geweest, maar het
|
||||||
|
# verdubbelt het bouw- en pinwerk, en nginx plus python3 in één alpine is klein.
|
||||||
|
# Beslist bij de start van het plan Eigenimage, OPEN.md punt 2.
|
||||||
|
#
|
||||||
|
# Tot 0.0.29 stonden deze bestanden als *.template in de app-map en draaiden ze
|
||||||
|
# op python:3-alpine en nginx:alpine. Wat dat kostte staat in het plan
|
||||||
|
# Eigenimage, PLAN.md §3; het kortste antwoord: het command-blok van de compose
|
||||||
|
# was ruim honderd regels shell in een YAML-string met verdubbelde dollartekens,
|
||||||
|
# en dat is nu een gewoon script.
|
||||||
|
#
|
||||||
|
# De bouwcontext is de REPO-ROOT, niet deze map. Dat is omdat icon.png in de
|
||||||
|
# app-map blijft staan (het manifest wijst ernaar voor de tegel in de winkel) en
|
||||||
|
# de pagina hem óók nodig heeft; kopiëren uit de app-map voorkomt een tweede
|
||||||
|
# exemplaar dat stil achterloopt. build.sh geeft die context mee.
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
# nginx en niet python als basis: de officiële nginx-image heeft de stream-module
|
||||||
|
# ingebouwd, en op alpine's eigen nginx-pakket is dat een los pakket. De app draaide
|
||||||
|
# al op nginx:alpine, dus dit houdt nginx precies zoals hij was. De tag is de
|
||||||
|
# stable-reeks (even tweede cijfer); zie Docs/Referenties/Images-pinnen.md.
|
||||||
|
FROM nginx:1.30-alpine
|
||||||
|
|
||||||
|
# De agent gebruikt alleen de standaardbibliotheek, inclusief ssl; dat zit in het
|
||||||
|
# python3-pakket van alpine.
|
||||||
|
RUN apk add --no-cache python3
|
||||||
|
|
||||||
|
COPY tools/electrum-gate/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
# Met .in erachter, want dit is nog niet de configuratie die nginx leest: het
|
||||||
|
# backend-adres komt uit de omgeving en entrypoint.sh vult het bij het starten in.
|
||||||
|
COPY tools/electrum-gate/stream.conf /etc/nginx/stream.conf.in
|
||||||
|
COPY tools/electrum-gate/index.html /usr/share/nginx/html/index.html
|
||||||
|
COPY whatsnext-electrum-gate/icon.png /usr/share/nginx/html/icon.png
|
||||||
|
COPY tools/electrum-gate/agent.py /app/agent.py
|
||||||
|
COPY tools/electrum-gate/entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
# De uitvoerbit hier zetten en niet vertrouwen op wat git meegeeft: de repo wordt
|
||||||
|
# ook op Windows bewerkt en daar bestaat die bit niet.
|
||||||
|
RUN chmod 0755 /entrypoint.sh
|
||||||
|
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
EXPOSE 80 50022
|
||||||
|
|
||||||
|
# De nginx-image heeft een eigen entrypoint dat scripts uit /docker-entrypoint.d
|
||||||
|
# draait. Die doen hier niets nuttigs (envsubst op een templates-map die leeg is)
|
||||||
|
# en één ervan zou, als hij ooit wél iets deed, precies de fout maken waar dit
|
||||||
|
# project al een dag aan kwijt was. Weg ermee: het commando is het commando.
|
||||||
|
ENTRYPOINT []
|
||||||
|
CMD ["/entrypoint.sh"]
|
||||||
@@ -1,17 +1,14 @@
|
|||||||
"""Electrum Gate agent: schrijft status.json en neemt de certificaatkeuze aan.
|
"""Electrum Gate agent: schrijft status.json en neemt de certificaatkeuze aan.
|
||||||
|
|
||||||
Waarom dit bestand een .template is en er toch geen accolade-variabelen in staan.
|
Dit bestand zit in de image (tools/electrum-gate/Dockerfile) als /app/agent.py.
|
||||||
umbreld ververst bij een update alleen een whitelist van bestanden en *.template
|
Tot 0.0.29 was het een *.template in de app-map, en omdat umbreld elke
|
||||||
staat daarin, dus een gewoon script naast de compose zou na een update ongewijzigd
|
accolade-variabele in zo'n bestand invult, haalt dit script zijn instellingen uit
|
||||||
blijven staan zonder dat er iets gemeld wordt. Maar umbreld vult bij elke start
|
de omgeving en niet uit een ingevulde constante. Dat is zo gebleven: de compose
|
||||||
ook elke accolade-variabele in, en die zou hier Python-code slopen. Daarom haalt
|
zet ze in de omgeving neer, en dat is ook de nette weg.
|
||||||
dit script zijn instellingen uit de omgeving, en zet de compose ze daar neer. Het
|
|
||||||
ingevulde agent.py is daardoor byte voor byte gelijk aan dit bestand, en dat is
|
|
||||||
precies wat de test in tests/ erover aanneemt.
|
|
||||||
|
|
||||||
Alleen de standaardbibliotheek. De app draait op een kant-en-klare python-image
|
Alleen de standaardbibliotheek. De image installeert python3 uit alpine en verder
|
||||||
en installeert bij het starten niets; dat was een van de dingen die de vorige
|
niets, en dat hoort zo te blijven: de vorige opzet installeerde bij het starten en
|
||||||
opzet fout deed.
|
dat was een van de dingen die fout gingen.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import calendar
|
import calendar
|
||||||
@@ -47,6 +44,12 @@ ELECTRUM_HOST = os.environ.get("GATE_ELECTRUM_HOST", "")
|
|||||||
ELECTRUM_PORT = int(os.environ.get("GATE_ELECTRUM_PORT", "50001"))
|
ELECTRUM_PORT = int(os.environ.get("GATE_ELECTRUM_PORT", "50001"))
|
||||||
TLS_PORT = int(os.environ.get("GATE_TLS_PORT", "50022"))
|
TLS_PORT = int(os.environ.get("GATE_TLS_PORT", "50022"))
|
||||||
|
|
||||||
|
# De versie uit het manifest, voor de kop van de pagina. Tot 0.0.29 vulde umbreld
|
||||||
|
# die rechtstreeks in de pagina in; nu de pagina in de image zit, loopt het via
|
||||||
|
# status.json. Leeg betekent dat de compose hem niet doorgeeft, en dan toont de
|
||||||
|
# pagina geen versie in plaats van een verzonnen.
|
||||||
|
APP_VERSION = os.environ.get("GATE_APP_VERSION", "")
|
||||||
|
|
||||||
# De zelfcontrole: de agent verbindt met de eigen TLS-poort. Zie het plan
|
# De zelfcontrole: de agent verbindt met de eigen TLS-poort. Zie het plan
|
||||||
# Webinterface, OPEN.md punt 5.
|
# Webinterface, OPEN.md punt 5.
|
||||||
#
|
#
|
||||||
@@ -1069,6 +1072,7 @@ def build_status(events):
|
|||||||
status = {
|
status = {
|
||||||
"generated_at": now,
|
"generated_at": now,
|
||||||
"interval_seconds": INTERVAL,
|
"interval_seconds": INTERVAL,
|
||||||
|
"version": APP_VERSION or None,
|
||||||
# open_connections blijft weg als het niet bekend is. Zie
|
# open_connections blijft weg als het niet bekend is. Zie
|
||||||
# read_open_sessions: nul beweren is hier erger dan zwijgen.
|
# read_open_sessions: nul beweren is hier erger dan zwijgen.
|
||||||
"tls": {"port": TLS_PORT, "self_check": self_check},
|
"tls": {"port": TLS_PORT, "self_check": self_check},
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# Bouwt de image voor de app whatsnext-electrum-gate.
|
||||||
|
#
|
||||||
|
# Eén image voor de twee containers van de app: nginx met de pagina en het
|
||||||
|
# stream-blok, en python3 met de agent. Wat erin gaat staat in de Dockerfile
|
||||||
|
# hiernaast; de bestanden zelf staan in deze map, behalve icon.png, dat in de
|
||||||
|
# app-map blijft omdat het manifest ernaar wijst.
|
||||||
|
#
|
||||||
|
# Waarom dit NIET in de app-map staat: umbreld kopieert de hele app-map naar het
|
||||||
|
# apparaat, en bij een update wordt alleen een whitelist ververst waar een
|
||||||
|
# Dockerfile niet in zit. Bouwen tijdens het starten van de app zou de installatie
|
||||||
|
# bovendien minuten laten hangen. Het recept hoort dus in de repo en het resultaat
|
||||||
|
# in een register; de app verwijst alleen naar de tag.
|
||||||
|
#
|
||||||
|
# Draaien: sh tools/electrum-gate/build.sh
|
||||||
|
# Vereist: docker op de machine waar je bouwt. Op de Umbrel is dat met sudo.
|
||||||
|
#
|
||||||
|
# Multi-arch: PLATFORMS="linux/amd64,linux/arm64" sh tools/electrum-gate/build.sh
|
||||||
|
# Dat vraagt buildx met QEMU en duwt meteen naar het register, want een
|
||||||
|
# multi-arch-manifest bestaat niet lokaal. Nog nooit geprobeerd; zie het plan
|
||||||
|
# Eigenimage, PLAN.md §4. Zonder PLATFORMS bouwt dit één architectuur, die van
|
||||||
|
# de machine waarop je staat, en dat is voor eigen gebruik op een amd64-Umbrel
|
||||||
|
# genoeg.
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# ── De pin ────────────────────────────────────────────────────────────────────
|
||||||
|
# Dit is het etiket op de IMAGE. Verhoog het als de image verandert, dus als er
|
||||||
|
# iets in deze map of in icon.png wijzigt, en zet dan dezelfde waarde achter
|
||||||
|
# `image:` in de compose (twee keer: agent en server delen hem).
|
||||||
|
#
|
||||||
|
# `version` in umbrel-app.yml is een ander nummer en mag hierop vooruitlopen: dat
|
||||||
|
# moet bij élke wijziging aan de app omhoog, ook als alleen de compose verandert,
|
||||||
|
# want anders rolt umbrelOS hem niet uit. Dat heeft hier een keer een dag gekost.
|
||||||
|
# Sinds de eigen image raakt vrijwel elke wijziging de image, dus ze lopen
|
||||||
|
# meestal gelijk; de regel blijft: verhoog je VERSION, dan ook het manifest.
|
||||||
|
VERSION="0.1.0"
|
||||||
|
|
||||||
|
# Het register staat er expres in en dit is geen smaakkwestie: umbreld haalt élke
|
||||||
|
# image op via de Docker Engine API, dus een tag die alleen lokaal bestaat is voor
|
||||||
|
# hem onbereikbaar en de installatie faalt met "pull access denied". Dat is op
|
||||||
|
# 25-08-2026 bij Evolu Relay op het apparaat vastgesteld.
|
||||||
|
IMAGE="sc.kamenier-hamer.nl/sysop/electrum-gate:${VERSION}"
|
||||||
|
|
||||||
|
RECEPT="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||||
|
# De bouwcontext is de repo-root, want icon.png komt uit de app-map. Zie de kop
|
||||||
|
# van de Dockerfile.
|
||||||
|
REPO="$(CDPATH= cd -- "${RECEPT}/../.." && pwd)"
|
||||||
|
|
||||||
|
# ── Controles vooraf ──────────────────────────────────────────────────────────
|
||||||
|
# Beter hier hard falen dan een image bouwen die iets anders bevat dan je denkt.
|
||||||
|
|
||||||
|
for bestand in Dockerfile entrypoint.sh agent.py nginx.conf stream.conf index.html; do
|
||||||
|
if [ ! -f "${RECEPT}/${bestand}" ]; then
|
||||||
|
echo "FOUT: ${bestand} ontbreekt in ${RECEPT}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -f "${REPO}/whatsnext-electrum-gate/icon.png" ]; then
|
||||||
|
echo "FOUT: whatsnext-electrum-gate/icon.png ontbreekt in ${REPO}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Een accolade-variabele hoort hier nergens meer in: deze bestanden gaan niet
|
||||||
|
# meer door envsubst, dus wat er staat is wat er draait. Staat er tóch een, dan
|
||||||
|
# is dat een restant van de template-tijd en zou hij letterlijk in de pagina of
|
||||||
|
# de configuratie terechtkomen.
|
||||||
|
if grep -l '\${' "${RECEPT}/nginx.conf" "${RECEPT}/stream.conf" "${RECEPT}/index.html" 2>/dev/null; then
|
||||||
|
echo "FOUT: de bestanden hierboven bevatten nog een \${...}; die wordt niet meer ingevuld." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Bouwen ────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
PLATFORMS="${PLATFORMS:-}"
|
||||||
|
|
||||||
|
if [ -n "$PLATFORMS" ]; then
|
||||||
|
echo "Bouwen als ${IMAGE} voor ${PLATFORMS}, en meteen duwen"
|
||||||
|
docker buildx build --platform "$PLATFORMS" --tag "$IMAGE" --push \
|
||||||
|
--file "${RECEPT}/Dockerfile" "$REPO"
|
||||||
|
echo
|
||||||
|
echo "Geduwd. De index-digest:"
|
||||||
|
docker buildx imagetools inspect "$IMAGE" | head -3
|
||||||
|
else
|
||||||
|
echo "Bouwen als ${IMAGE}"
|
||||||
|
docker build --tag "$IMAGE" --file "${RECEPT}/Dockerfile" "$REPO"
|
||||||
|
echo
|
||||||
|
echo "Klaar:"
|
||||||
|
docker image inspect --format '{{.RepoTags}} {{.Id}}' "$IMAGE"
|
||||||
|
echo
|
||||||
|
echo "Bouwen is niet genoeg: de app verwijst naar het register, want umbreld kan"
|
||||||
|
echo "niet bij een image die alleen lokaal staat. Nog te doen:"
|
||||||
|
echo
|
||||||
|
echo " docker login sc.kamenier-hamer.nl"
|
||||||
|
echo " docker push ${IMAGE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Zet daarna de digest uit de push-uitvoer achter de tag in"
|
||||||
|
echo "whatsnext-electrum-gate/docker-compose.yml (twee keer, agent en server), en"
|
||||||
|
echo "zet \`version\` in het manifest op ${VERSION}. Zonder die verhoging rolt"
|
||||||
|
echo "umbrelOS het niet uit."
|
||||||
|
echo
|
||||||
|
echo "Controleer dat het anoniem te halen is, want umbreld krijgt geen"
|
||||||
|
echo "inloggegevens mee. Log uit in dezelfde context waarin je inlogde, anders"
|
||||||
|
echo "meet je je eigen sessie:"
|
||||||
|
echo
|
||||||
|
echo " docker logout sc.kamenier-hamer.nl && docker pull ${IMAGE}"
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
# Het startscript van de server-container van Electrum Gate.
|
||||||
|
#
|
||||||
|
# Tot 0.0.29 stond dit als inline command-blok in docker-compose.yml, omdat de
|
||||||
|
# compose in de update-whitelist van umbreld staat en een los script niet. Dat
|
||||||
|
# betekende shell in een YAML-string, met elk dollarteken verdubbeld omdat
|
||||||
|
# umbreld anders elke ${...} leegmaakt. Sinds de eigen image is het een gewoon
|
||||||
|
# bestand; wat het doet is niet veranderd.
|
||||||
|
#
|
||||||
|
# Drie dingen die hier gebeuren en waarom ze hier gebeuren en niet in de agent:
|
||||||
|
#
|
||||||
|
# 1. de TLS-poort aan- of uitzetten naar de toestand van cert.conf. nginx weigert
|
||||||
|
# te starten met een 'listen ssl' zonder certificaat, en de pagina waarop je
|
||||||
|
# dat certificaat kiest moet juist altijd omhoog komen;
|
||||||
|
# 2. de open verbindingen tellen. Dat kan alleen in deze netwerk-namespace;
|
||||||
|
# 3. nginx herladen als de agent een vlag neerzet. De agent kan dat niet zelf,
|
||||||
|
# want dat zou de Docker-socket vragen en die is er bewust uit.
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
STATE=/var/lib/gate
|
||||||
|
mkdir -p "$STATE" "$STATE/tls"
|
||||||
|
|
||||||
|
# ── Het stream-blok invullen ──────────────────────────────────────────────────
|
||||||
|
# Het backend-adres komt uit de omgeving, dezelfde twee variabelen die de agent
|
||||||
|
# krijgt. Tot 0.0.29 vulde umbreld ze in via stream.conf.template; nu de
|
||||||
|
# configuratie in de image zit, gebeurt dat hier. nginx kan in een proxy_pass van
|
||||||
|
# het stream-blok zelf geen omgevingsvariabele lezen.
|
||||||
|
#
|
||||||
|
# Twee plaatshouders zonder dollarteken, zodat het bestand zelf nooit door een
|
||||||
|
# envsubst-stap beschadigd kan worden, ook niet als iemand het ooit weer als
|
||||||
|
# template in de app-map zet.
|
||||||
|
if [ -n "${GATE_ELECTRUM_HOST:-}" ] && [ -n "${GATE_ELECTRUM_PORT:-}" ]; then
|
||||||
|
sed -e "s|__GATE_ELECTRUM_HOST__|${GATE_ELECTRUM_HOST}|g" \
|
||||||
|
-e "s|__GATE_ELECTRUM_PORT__|${GATE_ELECTRUM_PORT}|g" \
|
||||||
|
/etc/nginx/stream.conf.in > /etc/nginx/stream.conf
|
||||||
|
else
|
||||||
|
# Zonder backend is er niets om naar door te sturen. De pagina komt wel omhoog
|
||||||
|
# en de agent meldt daar dat het adres ontbreekt; TLS blijft uit.
|
||||||
|
rm -f /etc/nginx/stream.conf
|
||||||
|
echo "GATE_ELECTRUM_HOST of GATE_ELECTRUM_PORT ontbreekt; TLS blijft uit, de pagina werkt."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── De TLS-poort aan of uit ───────────────────────────────────────────────────
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# 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 de map tls/ op
|
||||||
|
# met een jokerteken; die matcht dan niets en dat is geen fout.
|
||||||
|
#
|
||||||
|
# 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 "$STATE/cert.conf" ] && [ -f /etc/nginx/stream.conf ]; then
|
||||||
|
# Kopiëren en niet linken: nginx opent dit pad als de gebruiker nginx, en een
|
||||||
|
# symlink is hier nodeloos fragiel.
|
||||||
|
if cp /etc/nginx/stream.conf "$STATE/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 "$STATE/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.
|
||||||
|
#
|
||||||
|
# /proc/net/tcp geldt per netwerk-namespace, dus dit ziet alleen de sockets van
|
||||||
|
# deze container, en daarom staat de 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 in stream.conf.
|
||||||
|
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 > "$STATE/sessions"
|
||||||
|
|
||||||
|
# ── De herlaadlus ─────────────────────────────────────────────────────────────
|
||||||
|
# Draait op de achtergrond; 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.
|
||||||
|
reload_nginx() {
|
||||||
|
# Een certificaat dat nginx niet aanneemt laat de reload falen. Dat mag niet
|
||||||
|
# met 'set -e' meegaan, want dan verdwijnt de lus en wordt geen enkele latere
|
||||||
|
# wijziging meer opgepikt, zonder dat er iets te zien is.
|
||||||
|
if ! nginx -s reload; then
|
||||||
|
echo "Herladen mislukt; nginx houdt de vorige configuratie."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
(
|
||||||
|
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 > "$STATE/sessions.tmp"
|
||||||
|
mv "$STATE/sessions.tmp" "$STATE/sessions"
|
||||||
|
|
||||||
|
if [ -f "$STATE/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 "$STATE/reload" "$STATE/reload.done"
|
||||||
|
# Voor de herlading, niet erna: een reload leest de configuratie opnieuw,
|
||||||
|
# dus het stream-blok moet er dan al liggen.
|
||||||
|
sync_tls
|
||||||
|
reload_nginx
|
||||||
|
elif [ -f "$STATE/cert.conf" ] && [ ! -f "$STATE/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
|
||||||
|
reload_nginx
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
) &
|
||||||
|
|
||||||
|
exec nginx -g 'daemon off;'
|
||||||
@@ -2,20 +2,17 @@
|
|||||||
<!--
|
<!--
|
||||||
Statuspagina van Electrum Gate.
|
Statuspagina van Electrum Gate.
|
||||||
|
|
||||||
Dit bestand is een template en dat is bewust. umbreld ververst bij een update
|
Dit bestand zit in de image (tools/electrum-gate/Dockerfile) als
|
||||||
alleen een whitelist, en *.template staat daarin; een pagina onder web/ zou na
|
/usr/share/nginx/html/index.html. Tot 0.0.29 was het een *.template in de
|
||||||
een update ongewijzigd blijven staan zonder dat er iets gemeld wordt.
|
app-map die umbreld bij elke start door envsubst haalde, en daarom staat er in
|
||||||
|
het script hieronder nergens een template-literal maar overal string-optelling.
|
||||||
|
Die beperking is weg, maar de stijl is gebleven: één pagina in twee stijlen is
|
||||||
|
erger dan één stijl die niet meer nodig is.
|
||||||
|
|
||||||
Twee regels die daaruit volgen en die bij het wijzigen tellen:
|
Wat de pagina weet, komt uit status.json, dat de agent wegschrijft. Ook de
|
||||||
|
versie en het backend-adres, die umbreld vroeger rechtstreeks invulde. Weet de
|
||||||
1. gebruik geen accolade-variabelen behalve de umbrel-variabelen die hier
|
pagina iets niet, dan zegt hij dat. Hij verzint niets, want hij wordt juist
|
||||||
werkelijk ingevuld moeten worden. De invulling vervangt elke accolade-vorm
|
geraadpleegd op het moment dat er iets mis lijkt te zijn.
|
||||||
en zou de rest stilzwijgend leegmaken. Daarom staat er in het script
|
|
||||||
hieronder nergens een template-literal maar overal string-optelling;
|
|
||||||
2. wat de pagina niet uit een umbrel-variabele kan halen, komt uit status.json
|
|
||||||
dat de achtergrondlus in docker-compose.yml wegschrijft. Weet de pagina iets
|
|
||||||
niet, dan zegt hij dat. Hij verzint niets, want hij wordt juist geraadpleegd
|
|
||||||
op het moment dat er iets mis lijkt te zijn.
|
|
||||||
-->
|
-->
|
||||||
<!-- data-glow schakelt de accentgloed achter de pagina. "off" is de stand sinds
|
<!-- data-glow schakelt de accentgloed achter de pagina. "off" is de stand sinds
|
||||||
0.0.29 (verzoek van de gebruiker, 04-09-2026); "on" zet hem terug, en dat is
|
0.0.29 (verzoek van de gebruiker, 04-09-2026); "on" zet hem terug, en dat is
|
||||||
@@ -848,7 +845,7 @@ body {
|
|||||||
|
|
||||||
Wijzig je de een, wijzig dan de ander:
|
Wijzig je de een, wijzig dan de ander:
|
||||||
tests/test_server_start_zonder_certificaat.py houdt ze gelijk. -->
|
tests/test_server_start_zonder_certificaat.py houdt ze gelijk. -->
|
||||||
<p class="t-body-sm">Your own node from anywhere, without waiting for Tor <span class="head-version">v${APP_VERSION}</span></p>
|
<p class="t-body-sm">Your own node from anywhere, without waiting for Tor <span class="head-version" id="head-version"></span></p>
|
||||||
</div>
|
</div>
|
||||||
<!-- Het menu rechtsboven. Hier stond een losse knop voor licht en donker; die
|
<!-- Het menu rechtsboven. Hier stond een losse knop voor licht en donker; die
|
||||||
is er op verzoek van de gebruiker (27-08-2026) in gegaan, samen met het
|
is er op verzoek van de gebruiker (27-08-2026) in gegaan, samen met het
|
||||||
@@ -985,7 +982,7 @@ body {
|
|||||||
regels die hier stonden zijn weg: "Chosen in the umbrelOS settings"
|
regels die hier stonden zijn weg: "Chosen in the umbrelOS settings"
|
||||||
was uitleg bij een instelling die je hier toch niet wijzigt, en "Last
|
was uitleg bij een instelling die je hier toch niet wijzigt, en "Last
|
||||||
checked" staat als "as of ... ago" onder de reactietijd. -->
|
checked" staat als "as of ... ago" onder de reactietijd. -->
|
||||||
<div class="stat-sub mono" id="backend-address">${APP_ELECTRS_NODE_IP}:${APP_ELECTRS_NODE_PORT}</div>
|
<div class="stat-sub mono" id="backend-address"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1528,6 +1525,10 @@ body {
|
|||||||
put('backend-name', backend.name);
|
put('backend-name', backend.name);
|
||||||
if (backend.address) { put('backend-address', backend.address); }
|
if (backend.address) { put('backend-address', backend.address); }
|
||||||
|
|
||||||
|
// De versie in de kop. Geen put(): die zet "unknown" neer als het ontbreekt,
|
||||||
|
// en een kop met "vunknown" is erger dan een kop zonder versie.
|
||||||
|
if (s.version) { el('head-version').textContent = 'v' + s.version; }
|
||||||
|
|
||||||
drawSelfCheck(tls.self_check);
|
drawSelfCheck(tls.self_check);
|
||||||
|
|
||||||
// Hier stond een badge met "answered 7 seconds ago". Die is eruit op verzoek
|
// Hier stond een badge met "answered 7 seconds ago". Die is eruit op verzoek
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
# nginx-configuratie voor Electrum Gate.
|
# nginx-configuratie voor Electrum Gate.
|
||||||
#
|
#
|
||||||
# Dit bestand is een template en dat is bewust. umbrelOS ververst bij een update
|
# Dit bestand zit in de image (tools/electrum-gate/Dockerfile) als
|
||||||
# alleen een whitelist van bestanden, en *.template staat daarin; een gewoon
|
# /etc/nginx/nginx.conf. Tot 0.0.29 was het een *.template in de app-map die
|
||||||
# bestand naast de compose zou na een update ongewijzigd blijven staan. Daarnaast
|
# umbreld bij elke start door envsubst haalde, en daarom mocht er toen geen
|
||||||
# vult umbrelOS bij elke start de omgevingsvariabelen hieronder in en schrijft het
|
# nginx-variabele met een dollarteken in staan. Die beperking is weg. Dat de
|
||||||
# resultaat naast dit bestand weg als nginx.conf.
|
# access_log voor http nog steeds uit staat is een keuze en geen restant: de
|
||||||
#
|
# pagina hangt achter de app_proxy en wat die opvraagt is niet interessant.
|
||||||
# Let op bij het wijzigen: gebruik hier geen nginx-variabelen met een dollarteken
|
|
||||||
# (zoals de gebruikelijke in een log_format). De invulling vervangt elke ${...}
|
|
||||||
# en zou die stilzwijgend leegmaken. Daarom staat access_log voor http uit, en
|
|
||||||
# wordt het log_format van de stream-sessies door het command-blok in
|
|
||||||
# docker-compose.yml weggeschreven; dáár is een dollarteken te ontsnappen.
|
|
||||||
|
|
||||||
user nginx;
|
user nginx;
|
||||||
worker_processes auto;
|
worker_processes auto;
|
||||||
@@ -53,9 +48,7 @@ http {
|
|||||||
# heeft. Het ergste wat een geslaagde aanroep doet is een ander, ook
|
# heeft. Het ergste wat een geslaagde aanroep doet is een ander, ook
|
||||||
# bestaand, certificaat kiezen.
|
# bestaand, certificaat kiezen.
|
||||||
#
|
#
|
||||||
# Geen proxy_set_header hier, en dat is geen vergetelheid: die zouden een
|
# Geen proxy_set_header hier: de agent heeft ze niet nodig.
|
||||||
# nginx-variabele vragen, en die haalt de template-invulling weg. De
|
|
||||||
# agent heeft ze niet nodig.
|
|
||||||
#
|
#
|
||||||
# De volledige containernaam en niet de servicenaam 'agent'. Dat stond er
|
# De volledige containernaam en niet de servicenaam 'agent'. Dat stond er
|
||||||
# tot 0.0.23 wel, en het werkte zolang deze app de enige was met een
|
# tot 0.0.23 wel, en het werkte zolang deze app de enige was met een
|
||||||
@@ -83,15 +76,14 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ── De TLS-terminatie ────────────────────────────────────────────────────────
|
# ── De TLS-terminatie ────────────────────────────────────────────────────────
|
||||||
# Die staat niet hier maar in stream.conf.template, en dat is geen ordening maar
|
# Die staat niet hier maar in stream.conf, en dat is geen ordening maar de
|
||||||
# de reparatie van een klem. nginx weigert te starten als een 'listen ... ssl'
|
# reparatie van een klem. nginx weigert te starten als een 'listen ... ssl' geen
|
||||||
# geen certificaat heeft; stond het stream-blok hier, dan kwam met dat blok ook
|
# certificaat heeft; stond het stream-blok hier, dan kwam met dat blok ook de
|
||||||
# de pagina hierboven niet omhoog zolang de agent geen certificaat gekozen had.
|
# pagina hierboven niet omhoog zolang de agent geen certificaat gekozen had. En
|
||||||
# En de pagina is juist de plek waar die keuze gemaakt wordt.
|
# de pagina is juist de plek waar die keuze gemaakt wordt.
|
||||||
#
|
#
|
||||||
# Het command-blok van docker-compose.yml legt stream.conf in de map hieronder
|
# entrypoint.sh legt stream.conf in de map hieronder zodra cert.conf bestaat, en
|
||||||
# zodra cert.conf bestaat, en haalt het weg als dat niet zo is. Een include met
|
# haalt het weg als dat niet zo is. Een include met een jokerteken die nergens op
|
||||||
# een jokerteken die nergens op uitkomt is voor nginx geen fout, dus zonder
|
# uitkomt is voor nginx geen fout, dus zonder certificaat start deze server
|
||||||
# certificaat start deze server zonder poort 50022 en met een pagina die
|
# zonder poort 50022 en met een pagina die vertelt waarom.
|
||||||
# vertelt waarom.
|
|
||||||
include /var/lib/gate/tls/*.conf;
|
include /var/lib/gate/tls/*.conf;
|
||||||
@@ -7,34 +7,38 @@
|
|||||||
# zonder Zoraxy, of met twee kandidaten, zag daardoor helemaal niets en had geen
|
# zonder Zoraxy, of met twee kandidaten, zag daardoor helemaal niets en had geen
|
||||||
# enkele weg vooruit. Zo aangetroffen in 0.0.3.
|
# enkele weg vooruit. Zo aangetroffen in 0.0.3.
|
||||||
#
|
#
|
||||||
# Nu staat het blok hier, en het command-blok van docker-compose.yml zet dit
|
# Nu staat het blok hier, en entrypoint.sh zet dit bestand pas in
|
||||||
# bestand pas in /var/lib/gate/tls/ zodra de agent cert.conf geschreven heeft.
|
# /var/lib/gate/tls/ zodra de agent cert.conf geschreven heeft. nginx.conf haalt
|
||||||
# nginx.conf haalt die map op met een jokerteken, en een jokerteken dat niets
|
# die map op met een jokerteken, en een jokerteken dat niets matcht is voor nginx
|
||||||
# matcht is voor nginx geen fout. Geen certificaat betekent dus: geen
|
# geen fout. Geen certificaat betekent dus: geen stream-blok, geen poort 50022,
|
||||||
# stream-blok, geen poort 50022, maar wel een werkende pagina die vertelt wat
|
# maar wel een werkende pagina die vertelt wat eraan mankeert.
|
||||||
# eraan mankeert.
|
|
||||||
#
|
#
|
||||||
# Dit is een template om dezelfde reden als de andere: bij een update ververst
|
# Dit bestand zit in de image als /etc/nginx/stream.conf.in. De twee plaatshouders
|
||||||
# umbrelOS alleen een whitelist, en *.template staat daarin. Let daarom ook hier
|
# __GATE_ELECTRUM_HOST__ en __GATE_ELECTRUM_PORT__ vult entrypoint.sh bij het
|
||||||
# op het dollarteken: elke ${...} wordt bij het starten ingevuld, dus geen
|
# starten in uit de omgeving; nginx kan in een proxy_pass van het stream-blok zelf
|
||||||
# nginx-variabelen in dit bestand. Het log_format, dat er niet zonder kan, staat
|
# geen omgevingsvariabele lezen. Tot 0.0.29 was dit een *.template in de app-map
|
||||||
# in stream-log.conf en wordt door het command-blok weggeschreven.
|
# en deed umbreld die invulling, en daarom mocht er toen geen nginx-variabele met
|
||||||
|
# een dollarteken in staan. Die beperking is weg: het log_format staat nu gewoon
|
||||||
|
# hier en niet meer in een los bestand dat het command-blok wegschreef.
|
||||||
|
|
||||||
stream {
|
stream {
|
||||||
# Het log_format en de access_log, geschreven door het command-blok in
|
# Let op wat er NIET in staat: geen client-adres en geen bronpoort. Dat is
|
||||||
# docker-compose.yml.
|
# precies het soort gegeven dat deze app van het netwerk af houdt, en om te
|
||||||
include /var/lib/gate/stream-log.conf;
|
# zien dat het werkt is het niet nodig.
|
||||||
|
log_format gate '$time_iso8601 $status $bytes_received $bytes_sent $session_time';
|
||||||
|
access_log /var/lib/gate/stream.log gate;
|
||||||
|
|
||||||
server {
|
server {
|
||||||
# 50022 en niet 50002; de reden staat bij de ports-regel in
|
# 50022 en niet 50002; de reden staat bij de ports-regel in
|
||||||
# docker-compose.yml.
|
# docker-compose.yml. Wijzig je dit, wijzig dan ook de hex-poort in de
|
||||||
|
# verbindingsteller van entrypoint.sh.
|
||||||
listen 50022 ssl;
|
listen 50022 ssl;
|
||||||
|
|
||||||
# ssl_certificate en ssl_certificate_key, geschreven door de agent op
|
# ssl_certificate en ssl_certificate_key, geschreven door de agent op
|
||||||
# grond van wat er in de certificaatmappen staat en wat de gebruiker op
|
# grond van wat er in de certificaatmappen staat en wat de gebruiker op
|
||||||
# het dashboard gekozen heeft. Daarom staat er hier geen domeinnaam meer
|
# het dashboard gekozen heeft. Daarom staat er hier geen domeinnaam meer
|
||||||
# in dit bestand. Dat dit bestand bestaat betekent dat cert.conf er is:
|
# in dit bestand. Dat dit bestand bestaat betekent dat cert.conf er is:
|
||||||
# het command-blok zet het er pas dan neer.
|
# entrypoint.sh zet het er pas dan neer.
|
||||||
#
|
#
|
||||||
# nginx wil de VOLLEDIGE keten in ssl_certificate, dus het bestand van
|
# nginx wil de VOLLEDIGE keten in ssl_certificate, dus het bestand van
|
||||||
# Zoraxy kan er ongewijzigd in. Splitsen in een servercertificaat en een
|
# Zoraxy kan er ongewijzigd in. Splitsen in een servercertificaat en een
|
||||||
@@ -56,6 +60,6 @@ stream {
|
|||||||
# verbinding sneller opvallen dan pas bij het volgende verzoek.
|
# verbinding sneller opvallen dan pas bij het volgende verzoek.
|
||||||
proxy_socket_keepalive on;
|
proxy_socket_keepalive on;
|
||||||
|
|
||||||
proxy_pass ${APP_ELECTRS_NODE_IP}:${APP_ELECTRS_NODE_PORT};
|
proxy_pass __GATE_ELECTRUM_HOST__:__GATE_ELECTRUM_PORT__;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,11 +10,13 @@
|
|||||||
//
|
//
|
||||||
// ── Waarom dit bestaat ───────────────────────────────────────────────────────
|
// ── Waarom dit bestaat ───────────────────────────────────────────────────────
|
||||||
//
|
//
|
||||||
// Een `index.html.template` is niet te openen. Twee dingen staan in de weg, en ze
|
// Een statuspagina is niet zomaar te openen. Twee dingen staan in de weg, en ze
|
||||||
// zijn allebei fundamenteel en niet op te lossen door er anders naar te kijken:
|
// zijn allebei fundamenteel en niet op te lossen door er anders naar te kijken:
|
||||||
//
|
//
|
||||||
// 1. er staan accolade-variabelen in die umbreld invult. Onopgelost staat er
|
// 1. in een `index.html.template` staan accolade-variabelen die umbreld invult.
|
||||||
// letterlijk "v${APP_VERSION}" in de kop;
|
// Onopgelost staat er letterlijk "v${APP_VERSION}" in de kop. Een pagina die
|
||||||
|
// in een eigen image zit heeft dit niet meer, maar haalt de versie dan uit
|
||||||
|
// status.json, dus zonder agent staat er niets;
|
||||||
// 2. de pagina haalt al zijn gegevens bij een agent die alleen in de app bestaat.
|
// 2. de pagina haalt al zijn gegevens bij een agent die alleen in de app bestaat.
|
||||||
// Zonder die agent zie je één foutmelding en verder een pagina vol "unknown":
|
// Zonder die agent zie je één foutmelding en verder een pagina vol "unknown":
|
||||||
// geen lijsten, geen teller, geen knoppen.
|
// geen lijsten, geen teller, geen knoppen.
|
||||||
@@ -44,10 +46,18 @@ const REPO = join(HIER, '..');
|
|||||||
const UIT = join(REPO, 'voorbeeld');
|
const UIT = join(REPO, 'voorbeeld');
|
||||||
|
|
||||||
const app = process.argv[2] || 'whatsnext-evolu-relay';
|
const app = process.argv[2] || 'whatsnext-evolu-relay';
|
||||||
const bron = join(REPO, app, 'index.html.template');
|
|
||||||
|
// De pagina staat op een van twee plekken: als *.template in de app-map, of als
|
||||||
|
// gewoon bestand in het bouwrecept onder tools/ als hij in een eigen image zit.
|
||||||
|
// Electrum Gate is sinds 0.1.0 het tweede geval. Alleen de eerste gaat door
|
||||||
|
// envsubst; voor de tweede is de invulling hieronder een lege stap.
|
||||||
|
const template = join(REPO, app, 'index.html.template');
|
||||||
|
const kort = app.includes('-') ? app.slice(app.indexOf('-') + 1) : app;
|
||||||
|
const inImage = join(REPO, 'tools', kort, 'index.html');
|
||||||
|
const bron = existsSync(template) ? template : inImage;
|
||||||
|
|
||||||
if (!existsSync(bron)) {
|
if (!existsSync(bron)) {
|
||||||
console.error(`FOUT: ${bron} bestaat niet`);
|
console.error(`FOUT: geen pagina gevonden op ${template} of ${inImage}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,21 +6,31 @@ services:
|
|||||||
APP_HOST: whatsnext-electrum-gate_server_1
|
APP_HOST: whatsnext-electrum-gate_server_1
|
||||||
APP_PORT: 80
|
APP_PORT: 80
|
||||||
|
|
||||||
|
# Beide containers draaien dezelfde image, gebouwd door tools/electrum-gate/
|
||||||
|
# build.sh. Wat erin zit staat in de Dockerfile daar: nginx met de pagina en het
|
||||||
|
# stream-blok, en python3 met de agent. Tot 0.0.29 stonden die bestanden hier
|
||||||
|
# als *.template en draaiden ze op python:3-alpine en nginx:alpine; waarom dat
|
||||||
|
# veranderd is staat in het plan Eigenimage, PLAN.md §3.
|
||||||
|
#
|
||||||
|
# De tag hoort gelijk te zijn aan VERSION in build.sh, en er hoort een digest
|
||||||
|
# achter zodra de image geduwd is. Let op de valstrik uit KNOWLEDGE.md: een
|
||||||
|
# oude digest onder een nieuwe tag levert stilzwijgend de oude image, dus laat
|
||||||
|
# de digest er even van af zolang de nieuwe niet geduwd is.
|
||||||
|
|
||||||
# De agent. Hij schrijft status.json, leest de certificaten, bevraagt de
|
# De agent. Hij schrijft status.json, leest de certificaten, bevraagt de
|
||||||
# Electrum-server en neemt de certificaatkeuze aan.
|
# Electrum-server en neemt de certificaatkeuze aan.
|
||||||
#
|
#
|
||||||
# Waarom een tweede container en niet een shell-lus in de server hieronder:
|
# Waarom een tweede container en niet een lus in de server hieronder: dit werk
|
||||||
# dit werk is inmiddels een programma. Een certificaatdatum uitlezen, een
|
# is een programma. Een certificaatdatum uitlezen, een JSON-RPC-verzoek doen,
|
||||||
# JSON-RPC-verzoek doen, een geschiedenis bijhouden en een keuze valideren zijn
|
# een geschiedenis bijhouden en een keuze valideren zijn geen dingen die je met
|
||||||
# geen dingen die je met openssl en nc aan elkaar knoopt zonder dat het stil
|
# openssl en nc aan elkaar knoopt zonder dat het stil verkeerde antwoorden gaat
|
||||||
# verkeerde antwoorden gaat geven. Bijkomend voordeel: de app hangt niet meer
|
# geven.
|
||||||
# af van de vraag of die twee gereedschappen in de nginx-image zitten.
|
|
||||||
agent:
|
agent:
|
||||||
# TODO (fase 4 van het plan Appstore): pinnen op de multi-arch index-digest,
|
image: sc.kamenier-hamer.nl/sysop/electrum-gate:0.1.0
|
||||||
# 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
|
restart: on-failure
|
||||||
|
command:
|
||||||
|
- python3
|
||||||
|
- /app/agent.py
|
||||||
environment:
|
environment:
|
||||||
# Het adres van de Electrum-server die de gebruiker in umbrelOS gekozen
|
# Het adres van de Electrum-server die de gebruiker in umbrelOS gekozen
|
||||||
# heeft. umbrelOS vult dit in op grond van de afhankelijkheid hieronder,
|
# heeft. umbrelOS vult dit in op grond van de afhankelijkheid hieronder,
|
||||||
@@ -28,6 +38,10 @@ services:
|
|||||||
GATE_ELECTRUM_HOST: ${APP_ELECTRS_NODE_IP}
|
GATE_ELECTRUM_HOST: ${APP_ELECTRS_NODE_IP}
|
||||||
GATE_ELECTRUM_PORT: ${APP_ELECTRS_NODE_PORT}
|
GATE_ELECTRUM_PORT: ${APP_ELECTRS_NODE_PORT}
|
||||||
GATE_TLS_PORT: "50022"
|
GATE_TLS_PORT: "50022"
|
||||||
|
# De versie uit het manifest, voor de kop van de pagina. Die stond tot
|
||||||
|
# 0.0.29 rechtstreeks in de pagina, ingevuld door umbreld; nu de pagina in
|
||||||
|
# de image zit, geeft de agent hem door via status.json.
|
||||||
|
GATE_APP_VERSION: ${APP_VERSION}
|
||||||
# De zelfcontrole: de agent verbindt met de TLS-poort van de server
|
# De zelfcontrole: de agent verbindt met de TLS-poort van de server
|
||||||
# hieronder en maakt de handdruk af. Niet localhost: dat is een andere
|
# hieronder en maakt de handdruk af. Niet localhost: dat is een andere
|
||||||
# container en dus een andere netwerk-namespace.
|
# container en dus een andere netwerk-namespace.
|
||||||
@@ -57,19 +71,13 @@ services:
|
|||||||
# bron beschrijfbaar is, hoort te staan naast de mount die dat toestaat.
|
# bron beschrijfbaar is, hoort te staan naast de mount die dat toestaat.
|
||||||
# Leeg zetten schakelt uploaden uit.
|
# Leeg zetten schakelt uploaden uit.
|
||||||
GATE_UPLOAD_DIR: /certs/own
|
GATE_UPLOAD_DIR: /certs/own
|
||||||
PYTHONUNBUFFERED: "1"
|
|
||||||
volumes:
|
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
|
# De gedeelde toestand: status.json, cert.conf, de herlaadvlag en de
|
||||||
# gekozen certificaat-id. Beide containers zitten hierin.
|
# gekozen certificaat-id. Beide containers zitten hierin.
|
||||||
#
|
#
|
||||||
# Onder data/ en niet naast de templates, want dat is wat andere apps doen:
|
# Onder data/, want dat is wat andere apps doen: electrs mount
|
||||||
# electrs mount ${APP_DATA_DIR}/data/electrs, mempool ${APP_DATA_DIR}/data.
|
# ${APP_DATA_DIR}/data/electrs, mempool ${APP_DATA_DIR}/data. Verplaatst op
|
||||||
# Verplaatst op 20-08-2026 met het oog op publicatie in de officiele store.
|
# 20-08-2026 met het oog op publicatie in de officiele store.
|
||||||
- ${APP_DATA_DIR}/data/runtime:/var/lib/gate
|
- ${APP_DATA_DIR}/data/runtime:/var/lib/gate
|
||||||
# De certificaatbronnen. Zoraxy alleen lezen: dat zijn de certificaten van
|
# De certificaatbronnen. Zoraxy alleen lezen: dat zijn de certificaten van
|
||||||
# een andere app en die raakt deze app niet aan.
|
# een andere app en die raakt deze app niet aan.
|
||||||
@@ -88,17 +96,18 @@ services:
|
|||||||
# deze regel aanzetten en de naam toevoegen aan GATE_CERT_SOURCES.
|
# deze regel aanzetten en de naam toevoegen aan GATE_CERT_SOURCES.
|
||||||
#
|
#
|
||||||
# - ${UMBREL_ROOT}/app-data/nginx-proxy-manager/data/letsencrypt/live:/certs/npm:ro
|
# - ${UMBREL_ROOT}/app-data/nginx-proxy-manager/data/letsencrypt/live:/certs/npm:ro
|
||||||
command:
|
|
||||||
- python
|
|
||||||
- /app/agent.py
|
|
||||||
|
|
||||||
|
# nginx: de pagina op poort 80 achter de app_proxy, en de TLS-poort 50022 zodra
|
||||||
|
# de agent een certificaat gekozen heeft. Het startscript is entrypoint.sh in de
|
||||||
|
# image; tot 0.0.29 stond dat hier als command-blok van ruim honderd regels, met
|
||||||
|
# elk dollarteken verdubbeld. Wat het doet staat daar uitgelegd.
|
||||||
server:
|
server:
|
||||||
image: nginx:alpine
|
image: sc.kamenier-hamer.nl/sysop/electrum-gate:0.1.0
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
# De agent moet er zijn voordat nginx start, want de proxy_pass naar
|
# De agent moet er zijn voordat nginx start, want de proxy_pass naar de agent
|
||||||
# http://agent:8000 wordt bij het starten opgelost en een onbekende naam
|
# wordt bij het starten opgelost en een onbekende naam laat nginx afbreken.
|
||||||
# laat nginx afbreken. Op de cert.conf van de agent wordt niet gewacht: de
|
# Op de cert.conf van de agent wordt niet gewacht: de pagina komt hoe dan ook
|
||||||
# pagina komt hoe dan ook omhoog, zie het command-blok hieronder.
|
# omhoog, zie entrypoint.sh.
|
||||||
depends_on:
|
depends_on:
|
||||||
- agent
|
- agent
|
||||||
ports:
|
ports:
|
||||||
@@ -111,22 +120,13 @@ services:
|
|||||||
# buiten is toch al een andere, want die staat in de router doorgestuurd.
|
# buiten is toch al een andere, want die staat in de router doorgestuurd.
|
||||||
# Beslist 19-08-2026, open punt 1 van het plan Configuratie.
|
# Beslist 19-08-2026, open punt 1 van het plan Configuratie.
|
||||||
- "50022:50022"
|
- "50022:50022"
|
||||||
|
environment:
|
||||||
|
# Dezelfde twee als bij de agent. entrypoint.sh vult ze in het stream-blok
|
||||||
|
# in, want nginx kan in een proxy_pass geen omgevingsvariabele lezen. Tot
|
||||||
|
# 0.0.29 deed umbreld dat via stream.conf.template.
|
||||||
|
GATE_ELECTRUM_HOST: ${APP_ELECTRS_NODE_IP}
|
||||||
|
GATE_ELECTRUM_PORT: ${APP_ELECTRS_NODE_PORT}
|
||||||
volumes:
|
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
|
# Dezelfde gedeelde toestand als de agent. nginx leest hier cert.conf en
|
||||||
# status.json, en schrijft de sessielog.
|
# status.json, en schrijft de sessielog.
|
||||||
- ${APP_DATA_DIR}/data/runtime:/var/lib/gate
|
- ${APP_DATA_DIR}/data/runtime:/var/lib/gate
|
||||||
@@ -134,131 +134,3 @@ services:
|
|||||||
# welk pad in cert.conf komt, maar nginx moet het bestand kunnen openen.
|
# 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
|
- ${UMBREL_ROOT}/app-data/zoraxy/data/config/conf/certs:/certs/zoraxy:ro
|
||||||
- ${APP_DATA_DIR}/data/certs:/certs/own: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;'
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ manifestVersion: 1
|
|||||||
id: whatsnext-electrum-gate
|
id: whatsnext-electrum-gate
|
||||||
category: bitcoin
|
category: bitcoin
|
||||||
name: Electrum Gate
|
name: Electrum Gate
|
||||||
version: "0.0.29"
|
version: "0.1.0"
|
||||||
tagline: Your own node from anywhere, without waiting for Tor
|
tagline: Your own node from anywhere, without waiting for Tor
|
||||||
description: >-
|
description: >-
|
||||||
The privacy win is already yours: you run the Electrum server. A public one gets asked for
|
The privacy win is already yours: you run the Electrum server. A public one gets asked for
|
||||||
@@ -56,6 +56,16 @@ description: >-
|
|||||||
# staan. In 0.0.9 stond er daardoor drie keer "Earlier releases" en twee keer
|
# staan. In 0.0.9 stond er daardoor drie keer "Earlier releases" en twee keer
|
||||||
# dezelfde 0.0.4-regel. De toets let er nu op.
|
# dezelfde 0.0.4-regel. De toets let er nu op.
|
||||||
releaseNotes: >-
|
releaseNotes: >-
|
||||||
|
The app now ships as its own image instead of mounting its program files into two stock images. Nothing
|
||||||
|
changes in what it does; what changes is where the code lives. The app data folder now holds only your
|
||||||
|
data: the certificate you chose, the one you uploaded, and the status the page reads. The startup logic
|
||||||
|
that used to be a hundred lines of shell inside the compose file is a plain script inside the image, and
|
||||||
|
the app icon updates along with everything else, which it could not before.
|
||||||
|
|
||||||
|
|
||||||
|
Everything in 0.0.29:
|
||||||
|
|
||||||
|
|
||||||
The glow behind the page is gone. It arrived in 0.0.25 and it did what it was meant to do, but a status
|
The glow behind the page is gone. It arrived in 0.0.25 and it did what it was meant to do, but a status
|
||||||
page is something you glance at, and a coloured wash over the whole window is a lot of decoration for a
|
page is something you glance at, and a coloured wash over the whole window is a lot of decoration for a
|
||||||
glance. The panels keep their edge, their shadow and their top light, so they still read as panels; only
|
glance. The panels keep their edge, their shadow and their top light, so they still read as panels; only
|
||||||
|
|||||||
Reference in New Issue
Block a user