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:
Harmen
2026-09-07 20:20:42 +02:00
co-authored by Claude Fable 5.1
parent 7b3804df89
commit 973b24a23f
25 changed files with 981 additions and 410 deletions
+35 -5
View File
@@ -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()) {
const pad = join(REPO, app, 'index.html.template');
if (!existsSync(pad)) {
const pagina = vindPagina(app);
if (pagina === null) {
// Geen pagina is geen fout: een app hoeft er geen te hebben.
continue;
}
const bron = readFileSync(pad, 'utf8');
const bron = readFileSync(pagina.pad, 'utf8');
const blokken = scriptBlokken(bron);
toets(`${app}: de pagina heeft een scriptblok`, blokken.length > 0);
@@ -117,10 +138,19 @@ for (const app of appMappen()) {
const rauw = parseert(blok);
toets(`${naam} parseert zoals hij in de repo staat`, rauw === null, rauw);
const ingevuld = parseert(vulIn(blok));
toets(`${naam} parseert ook nadat umbreld hem invult`, ingevuld === null, ingevuld);
if (pagina.template) {
const ingevuld = parseert(vulIn(blok));
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
// tweeduizend regels echt maakt. Een dialog of een header die niet gesloten is,
// laat de rest van de pagina in dat element hangen.