De pagina van Evolu Relay op de lijst van de gebruiker

Negen punten uit echt gebruik met 0.4.0, alle negen gedaan. Twee ervan waren
onderzoeksvragen en die staan onderaan.

Evolu Relay 0.5.0
- Een tijdvenster van twee minuten voor nieuwe eigenaars, met een stopknop. De
  teller zit in het relay-proces en niet in de pagina: een teller in een tabblad
  dat je sluit, sluit de deur niet. policy.js kreeg learningUntil, isLearningOpen
  en expireLearning.
- decideOwner kijkt naar isLearningOpen en niet naar het veld learning. De lus die
  een verlopen venster opruimt loopt elke twee seconden, en in dat gat zou een
  onbekende alsnog binnenkomen.
- Zonder STATE_VERSION te verhogen, met een toets die dat verdedigt: een verhoging
  zou de allowlist van de draaiende installatie laten afwijzen en de deur sluiten
  voor eigenaars die er al in stonden.
- Labels op een eigenaar-id, in een eigen labels.json met de agent als enige
  schrijver. Een label zegt niets over toegang, dus de relay hoeft het niet te
  weten; het is daardoor meteen opgeslagen en werkt ook als de relay omligt.
- Geblokkeerde en geweigerde eigenaars in een kader, met een badge die zegt welke
  van de twee het is. De badge staat buiten het hover-blok, anders is dat
  onderscheid onzichtbaar tenzij je over de regel gaat.
- Maatvoering gelijk aan Electrum Gate: 1760px, hetzelfde raster, icoon van 64
  pixels, dezelfde kop, versienummer erachter. Uitleg uit de kaders, knoppen pas
  bij hover, geen voetregel.

Electrum Gate 0.0.24
- Menu-item "About this app", in beide apps.
- De statuswidget zei "Answering" met "answered in 7 ms, from inside the app" en
  zegt nu "Running" met de meting eronder. De nuance dat de controle van container
  naar container loopt is verplaatst naar een eigen kopje in die dialoog, waar er
  ruimte voor is; vier woorden waren te weinig.

Toetsen en gereedschap
- tests/test_relay_agent.py (nieuw, 65 toetsen) en tests/test_paginas_parsen.mjs
  (nieuw). Muteertests gedraaid op de beslissende regels.
- Een dollarteken-toets in test_appstore_vorm.py. Het commentaar in drie bestanden
  beweerde al dat die test bestond; nu is dat waar.
- Een toets dat er geen werkbestanden in een app-map staan. umbreld kopieert de
  hele map naar het apparaat en in de back-up.
- tools/voorbeeldpagina.mjs maakt van een *.template een pagina die je in een
  browser kunt openen. Dat vond meteen twee echte opmaakfouten.

De twee onderzoeksvragen
- Een geweigerde eigenaar komt niet in de database: isOwnerAllowed zit in de
  WebSocket-upgrade, dus het is een 401 en een gesloten socket. Het gewenste gevolg
  treedt wel op, via de client: die is local-first en levert bij toelating de hele
  geschiedenis. Blokkeren werkt daarentegen pas bij de volgende verbinding, en dat
  staat als open punt.
- De blobs zijn niet met een xpub te ontcijferen; een OwnerId komt daar niet uit.
  Met de SLIP-21-node van het apparaat kan het wel, maar die geeft volledige
  zeggenschap, dus dat hoort niet in een relay. Als plan-punt opgenomen bij de tool
  in HomeGit/Trezor.

Nog niet uitgerold: de image 0.5.0 moet gebouwd en geduwd worden. De digest staat
daarom niet in de compose, want een oude digest onder een nieuwe tag levert stil de
oude relay.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Harmen
2026-08-30 12:26:56 +02:00
co-authored by Claude Opus 5
parent 83af97c2c5
commit b3b1881af2
27 changed files with 3163 additions and 411 deletions
+138 -5
View File
@@ -560,6 +560,19 @@ body {
want in de melding staat een zin die je eerst leest. */
.alert-action { margin-top: 0.7rem; }
/* De dialoog "About this app" is lopende tekst en geen lijst met waarden, dus hij
heeft opmaak nodig die de certificaatdialoog niet gebruikt. Kopjes in de maat
van een .t-label, want ze verdelen de tekst en zijn er niet om gelezen te
worden. */
.sheet-prose h3 {
font-size: 0.68rem; font-weight: 600; letter-spacing: 0.08em;
text-transform: uppercase; color: var(--text-sec);
margin: 1.6rem 0 0.5rem;
}
.sheet-prose h3:first-child { margin-top: 0; }
.sheet-prose p { font-size: 0.85rem; line-height: 1.65; color: var(--text-sec); margin-bottom: 0.7rem; }
.sheet-prose p strong { color: var(--text-primary); font-weight: 600; }
/* ── Het instelkader ────────────────────────────────────────────────────── */
.setup > summary { list-style: none; cursor: pointer; display: flex; align-items: baseline; gap: 0.8rem; flex-wrap: wrap; }
.setup > summary::-webkit-details-marker { display: none; }
@@ -692,6 +705,19 @@ body {
<button class="menu-item" id="menu-certs" type="button">
<span>Settings…</span>
</button>
<!-- "About this app" op verzoek van de gebruiker (30-08-2026), en aan
beide apps in deze store toegevoegd. Hier stond de marketingtekst
nergens op de pagina: die staat in umbrel-app.yml en dus alleen in de
winkel, terwijl je juist ná het installeren nog eens wil kunnen
nalezen wat dit ding voor je doet en wat het niet doet.
Boven "Appearance" en onder "Settings…": van wat je het meest doet
naar wat je het minst doet is hier de verkeerde ordening, want dan zou
het thema bovenaan staan. De ordening is wat over de app gaat eerst,
wat over deze pagina gaat daarna. -->
<button class="menu-item" id="menu-about" type="button">
<span>About this app…</span>
</button>
<!-- De regel toont de huidige stand en niet de handeling. De losse knop
hiervoor deed het andersom, met "Light" terwijl de pagina donker was.
Als knop klopte dat, want dat was waar je heen ging; als menuregel
@@ -943,6 +969,82 @@ body {
</div><!-- /sheet-body -->
</dialog>
<!-- De dialoog "About this app", toegevoegd op verzoek van de gebruiker
(30-08-2026) en in dezelfde vorm aan Evolu Relay.
De tekst is die uit umbrel-app.yml, ingekort en met kopjes erin. Wijzig je
daar iets aan de strekking, dan hoort het hier ook te veranderen; ze staan
niet met een test aan elkaar vast omdat de vorm verschilt.
Wat hier bovendien staat en nergens anders: de grens van de statuswidget.
Die zei tot 0.0.24 "answered in 7 ms, from inside the app", en dat was
precies genoeg tekst om onduidelijk te zijn en te weinig om de nuance te
dragen. De nuance is nu hier, waar er ruimte voor is. -->
<dialog id="about-dialog" class="sheet">
<form method="dialog" class="sheet-head">
<h2 class="t-h3">About this app</h2>
<button class="sheet-close" value="close" aria-label="Close" title="Close">×</button>
</form>
<div class="sheet-body sheet-prose">
<h3>What it is for</h3>
<p>
The privacy win is already yours: you run the Electrum server. A public one gets asked for the
history of every address in your wallet, and that tells it which addresses and which balance
belong to one person. Your own server never reports back.
</p>
<p>
What is left is a trade. <strong>Tor</strong> is the more private way in, and every wallet
speaks it. But it adds hundreds of milliseconds to every request, it drops when a phone sleeps
or changes network, and plenty of networks block it outright. Electrum Gate is the other side
of that trade: a TLS front door on your node. Fast, no fingerprint to type over, and it works
on any network you happen to be on.
</p>
<h3>The certificate</h3>
<p>
It reuses the Let's Encrypt certificate your reverse proxy already manages, so there is
nothing to request and nothing to renew. A renewal is picked up on its own, without dropping
connections that are already open. You can also upload a certificate you manage yourself, if
you do not run a reverse proxy on this machine.
</p>
<p>
When more than one certificate could fit and you have not chosen, the app <strong>refuses</strong>
rather than picking one. A wrong certificate gives a connection that looks fine and only breaks
later, inside your wallet, on name verification.
</p>
<h3>Nothing in the middle</h3>
<p>
The connection runs from your wallet straight to your own node, encrypted with a certificate
you already own, for a domain you already control. There is no account to create, no tunnel
service that terminates your traffic along the way, and no client to install on every device
you use: the wallets already speak TLS, they only need an address. What it does ask of you is
one forwarded port on your router.
</p>
<h3>What the Status card does and does not prove</h3>
<p>
The app opens a TLS connection to its own front door every minute and checks that the
certificate it gets back is the one you selected. <strong>Running</strong> means that
succeeded.
</p>
<p>
It is measured from inside the app, so it says nothing about the way in from outside: a router
that no longer forwards the port, or a firewall in between, still reads as Running here. If a
wallet cannot connect while this card says Running, the fault is on the route and not in this
app.
</p>
<h3>Which wallets</h3>
<p>
Useful for wallets connecting from outside your home: Trezor Suite, Electrum, Sparrow,
BlueWallet, Nunchuk, Blockstream and BitBoxApp. Apps on the Umbrel itself do not need it, they
already reach the Electrum server directly.
</p>
</div>
</dialog>
<!-- Voorstel, nog niet besloten. De agent stelt de regels samen uit de
stream-access-log van nginx. Bewust zonder client-IP en zonder bronpoort:
dat is precies het soort gegeven dat deze app dichtzet, en het is niet
@@ -1121,6 +1223,20 @@ body {
el('menu-certs').addEventListener('click', openSheet);
el('alert-nocert-open').addEventListener('click', openSheet);
// ── De dialoog "About this app" ─────────────────────────────────────────
// Dezelfde route als hierboven, met dezelfde terugval voor een browser zonder
// showModal.
var about = el('about-dialog');
el('menu-about').addEventListener('click', function () {
closeMenu();
if (typeof about.showModal === 'function') {
about.showModal();
} else {
about.setAttribute('open', '');
}
});
// ── Hulpjes ─────────────────────────────────────────────────────────────
function el(id) { return document.getElementById(id); }
@@ -1290,12 +1406,26 @@ body {
// Gebouwd in de agent in 0.0.16, hier getoond sinds de tweede indeling.
//
// Let op het woord dat hier niet staat: "reachable". De agent verbindt van
// container naar container, dus dit zegt niets over de poortmapping naar de
// host of over de doorstuurregel in de router. "Answering" met daaronder "from
// inside the app" is wat er gemeten is; alles wat verder gaat, zou de tegel
// container naar container, dus dit zegt niets over de poortmapping naar de host
// of over de doorstuurregel in de router. Alles wat verder gaat, zou de tegel
// laten liegen op precies het punt waar iemand hem gelooft.
//
// Hier stond "Answering", met daaronder "answered in 7 ms, from inside the app".
// De gebruiker meldde op 30-08-2026 dat die tegel onduidelijk is en dat die van
// Evolu Relay ("Running") meteen leest, en dat is terecht om twee redenen:
//
// - "Answering" is een tegenwoordig deelwoord en leest dus als een handeling die
// bezig is, niet als een toestand. "Checking" ernaast maakt dat erger, want
// dát is er wél een;
// - "from inside the app" is de nuance hierboven in vier woorden, en vier
// woorden zijn er te weinig voor. Wie het niet al weet, leest er niets uit.
//
// Wat er nu staat is "Running", met de meting eronder. De nuance is niet
// weggelaten maar verplaatst naar de dialoog "About this app", onder een eigen
// kopje: daar is ruimte om te zeggen wat het wél en niet bewijst. Haal dat kopje
// niet weg zonder hier iets terug te zetten, want dan staat de bewering nergens.
var SELF = {
ok: { label: 'Answering', bad: false },
ok: { label: 'Running', bad: false },
failed: { label: 'Not answering', bad: true },
'wrong-certificate': { label: 'Wrong certificate', bad: true },
// Geen certificaat is de normale begintoestand van een verse installatie en
@@ -1322,7 +1452,10 @@ body {
node.classList.toggle('bad', kind.bad);
if (self.state === 'ok') {
sub.textContent = 'answered in ' + self.ms + ' ms, from inside the app';
// Wat er gemeten is, in gewone woorden. Het getal blijft, want dat is het
// enige harde gegeven op deze tegel; wat het niet bewijst staat in de
// dialoog "About this app".
sub.textContent = 'the TLS port answered in ' + self.ms + ' ms';
} else if (self.state === 'off') {
sub.textContent = 'no certificate selected, so nothing is listening';
} else if (self.state === 'wrong-certificate') {