From 5088aef556c5246d13c339dc091a4a5d6104b25b Mon Sep 17 00:00:00 2001 From: archipelago Date: Mon, 3 Aug 2026 10:31:39 -0400 Subject: [PATCH] fix(lnd-ui): pin the image and host-network it so OTA actually updates it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported: Framework PT took the OTA and got the new bitcoin-ui but not lnd-ui. Two causes, both in the update path rather than the app. 1. LND_UI_IMAGE was "lnd-ui:latest" while BITCOIN_UI_IMAGE was pinned to 1.7.119-alpha. Podman does not re-pull a tag it already holds locally, so a node that ever pulled lnd-ui:latest keeps that copy forever and every subsequent release silently no-ops. Pinned to 1.7.119-alpha, so a version change is what triggers the pull — the same mechanism that made bitcoin-ui update correctly. 2. first-boot-containers.sh declared lnd-ui as bridge with -p 18083:80. docker/lnd-ui/nginx.conf listens on 18083 DIRECTLY (it must, to proxy the backend on 127.0.0.1:5678 same-origin), so that maps a host port onto a container port nothing serves — reproduced on-node as HTTP 000. This is the THIRD copy of the same declaration: container-specs.sh and apps/lnd-ui/manifest.yml were both already corrected, this one was missed, and it is the copy fresh installs use. Now host-networked with no published ports, matching its siblings and the other two copies. The underlying hazard is that one container spec lives in three files that can disagree; recorded as a follow-up rather than refactored here. Also opens .planning/RELEASE-1.7.121-TASKS.md — every outstanding item for the next release with its evidence, so nothing in a fast-moving queue gets lost between sessions. Co-Authored-By: Claude Opus 5 (1M context) --- .planning/RELEASE-1.7.121-TASKS.md | 142 +++++++++++++++++++++++++++++ scripts/first-boot-containers.sh | 8 +- scripts/image-versions.sh | 2 +- 3 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 .planning/RELEASE-1.7.121-TASKS.md diff --git a/.planning/RELEASE-1.7.121-TASKS.md b/.planning/RELEASE-1.7.121-TASKS.md new file mode 100644 index 00000000..f2783ccd --- /dev/null +++ b/.planning/RELEASE-1.7.121-TASKS.md @@ -0,0 +1,142 @@ +# Release 1.7.121 — task list + +Opened 2026-08-03, immediately after v1.7.120-alpha shipped. Everything the operator has +asked for since, plus the items v1.7.120 deliberately left open. Ordered by severity. + +Status key: **DONE** (committed) · **READY** (written, not yet committed/tested) · +**OPEN** (not started) · **BLOCKED** (needs an operator decision) + +--- + +## P0 — Security + +### 1. App ports are reachable with no login, on every transport — **OPEN** +> "if I'm logged out I can reach every app port on tailscale and LAN, this can not be +> allowed… it must present the login to access the app with an app icon of what you're +> accessing to confirm, and 2FA if present" — operator, 2026-08-03 + +- Applies to **Tailscale, LAN, Tor, FIPS** alike, and to "ssh access to that port or whatever". +- Required behaviour: an unauthenticated request to any app port serves a **login page + naming and showing the icon of the app being accessed**, then honours **2FA when set**. +- **Research first:** how umbrelOS and StartOS gate app access (operator asked explicitly). + Both are open source — `getumbrel/umbrel` and `Start9Labs/start-os`. Do not guess at + their model; read it. +- This is the same class as the v1.7.120 `/lnd-connect-info` + `/bitcoin-rpc/` leaks, but + **fleet-wide across every app port** rather than two endpoints. Those two were closed by + moving authorisation to the resource; this needs a general gate. +- Scope note: `fips/app_ports.rs` holds the mesh allowlist; `is_peer_allowed_path` in + `server.rs` holds the peer HTTP allowlist. Neither currently authenticates app ports. + +### 2. Filebrowser ships an insecure default login — **OPEN** +- Change the default credential **without breaking the dashboard's Cloud view**, which + authenticates to filebrowser on the user's behalf. +- Related prior art: FED-07 rotated the shipped Fedimint gateway credential and had to + recreate the running container for it to take effect (`06e0e695`) — the same trap + applies here. + +### 3. Federation trust escalation — **READY** (compiles; tests running) +Two independent fail-open paths granted `Trusted` without any operator decision: + +- `federation.peer-joined` is **unauthenticated** (middleware no-session list) and + peer-reachable on `/rpc/v1`. Its ed25519 check verifies the caller against **the pubkey + the caller supplied**, so it proves key possession, never authorisation. A join with no + `invite_token` fell through to `TrustLevel::Trusted.min(claimed_trust)`, and + `claimed_trust` defaults to `Trusted` — so anyone able to reach the node could + self-grant Trusted. **Now capped at `Observer`.** +- `merge_transitive_peers` added every peer advertised by a Trusted source as `Trusted`, + making trust viral across the whole federation graph. **Now `Observer`** — which is what + `NodeStateSnapshot.federated_peers`' own doc comment always said it should be + ("adds them as Observers on her side… doesn't auto-promote to Trusted"). The code + contradicted its own spec. +- Added `FederatedNode.trust_source` (`invite` | `uninvited-join` | `transitive-merge` | + `manual`, `None` = pre-existing/unknown) so existing grants are **auditable**. Per + operator decision: existing peers are **left alone, not auto-demoted**. +- **Still to do:** surface `trust_source` in `federation.list-nodes` + the UI so the + operator can actually review the `None`/`uninvited-join` population. + +### 3b. Granting Trusted must require the node password — **OPEN** +> "to make someone trusted must require the node password to generate the code or change +> in the modal dropdown when you click a node" — operator, 2026-08-03 + +Re-authentication on privilege escalation. Two entry points, both must be covered: + +- **Minting a Trusted invite** (`federation.invite` with `trust_level: "trusted"`) — + "Link Your Nodes" mints Trusted today with no re-auth. +- **Changing a node's level in the UI dropdown** (`federation.set-trust-level` / + `handlers.rs:342`) — promoting Observer → Trusted. + +Demotion must NOT require the password: making something less privileged should never be +harder than leaving it. Grant `TrustSource::Manual` on the operator path so the audit +trail distinguishes it from the capped automatic ones. + +--- + +## P1 — Correctness the operator hit directly + +### 4. LND UI never updates over OTA — **READY** (uncommitted) +- `LND_UI_IMAGE` was `lnd-ui:latest` while `BITCOIN_UI_IMAGE` was pinned to + `1.7.119-alpha`. Podman will not re-pull a tag it already holds locally, so nodes kept a + stale lnd-ui forever. **Now pinned to `1.7.119-alpha`.** +- `scripts/first-boot-containers.sh` declared lnd-ui as bridge `-p 18083:80`. That is the + **third copy** of the declaration the UI agent already corrected in + `scripts/container-specs.sh` and `apps/lnd-ui/manifest.yml` — so **fresh installs** still + produced the reproduced `HTTP 000`. **Now `--network host`, ports empty.** +- Root cause worth fixing separately: the same container spec is declared in three places. + +### 5. Federated/peered nodes must message without a LoRa hop first — **OPEN** +> "make it so federated/peered nodes can message without needing to connect on Lora first +> once connected" + +- Investigate the split contact model (radio contact vs federation peer) — there is prior + art in memory: `project_archy_lora_e2e_rootcause` ("split contact model; don't touch + federation") and `mesh::seed_federation_peers_into_mesh` / + `upsert_federation_peer`, which already mirror federation peers into the mesh table. +- Likely the gap is addressing/route selection rather than transport availability. + +### 6. In-app app updates, independent of OTA — **OPEN** +> "we need app update to see updates in the registry, whether UI or not… show the update +> mechanism in the app… a modal and update now / cancel… same in the detail page… the +> update button should show 'see update' and a different graphic for just ui, app, or both +> together. All pushed through the signed-catalog flow." … "This has to show independent of +> OTA updates as a separate pipeline, I think we've done a lot of work on it." + +- **Operator says much of this already exists — research the codebase before building.** + Known groundwork: the signed catalog (`releases/app-catalog.json`, `sign-catalog.sh`), + catalog→manifest runtime reload, `package.update` RPC, `check-app-catalog-drift.py`, + and `scripts/image-versions.sh` pinning. +- Needs: registry-version awareness per app, a diff of what changed (UI vs app vs both), + the modal + detail-page affordance, and distinct iconography for the three cases. + +--- + +## P2 — Carried over from v1.7.120 + +### 7. `create-release.sh` commits the manifest BEFORE signing — **OPEN** +Release commit always carries an **unsigned** manifest; nodes fetch it from branch `main` +and refuse to auto-apply. Caught manually this cycle. Fix the ordering so it cannot ship. + +### 8. `gitea-vps2` remote is dead, and is the same server as `gitea-ai` — **OPEN** +Stored token fails auth. `source.archipelago-foundation.org` == `146.59.87.168`, so +`git push gitea-ai` already publishes to the "primary" OTA host. Ties into the existing +"migrate VPS2 IP to domain" todo. + +### 9. Fleet SSH host-key rotation — **BLOCKED** (operator decision) +`archipelago-1`, `archy-x250-beta`, `archipelago` share all three SSH host keys; two also +share a TLS private key. Detection shipped; rotation deliberately not performed. + +### 10. 5× lifecycle gate — **OPEN** +Not run for v1.7.120 (disclosed in its changelog). Needs repeated reboots of a live node. + +### 11. `prod_orchestrator.rs:3181` unreachable code — **OPEN** +`bitcoin_host()` returns unconditionally at :3171, so the podman container-name lookup +below is dead on every path. Pre-existing; spotted in the v1.7.120 build warnings. + +--- + +## Notes for whoever picks this up + +- A separate agent is doing **AIUI planning with GSD** — do not touch AIUI. +- AIUI must always be built `VITE_BASE_PATH=/aiui/` (see the memory note); a hand-built + bundle renders a black page. +- Verify security claims on the node, not from the source. v1.7.120's headline bug was a + fix that shipped in the binary and silently never reached the running container. diff --git a/scripts/first-boot-containers.sh b/scripts/first-boot-containers.sh index fe18893d..6751f51b 100755 --- a/scripts/first-boot-containers.sh +++ b/scripts/first-boot-containers.sh @@ -1386,7 +1386,13 @@ for ui in bitcoin-ui lnd-ui electrs-ui; do # UI containers use --network host so they can proxy to localhost services # Internal nginx ports: bitcoin-ui=8334, electrs-ui=50002, lnd-ui=80 (host 18083) bitcoin-ui) PORT_ARG=""; NET_ARG="--network host"; REG_IMG="${BITCOIN_UI_IMAGE}" ;; - lnd-ui) PORT_ARG="-p 18083:80"; NET_ARG=""; REG_IMG="${LND_UI_IMAGE}" ;; + # Host-networked like its siblings, NOT bridge with -p 18083:80. + # docker/lnd-ui/nginx.conf listens on 18083 directly (it must, to proxy the + # backend on 127.0.0.1:5678 same-origin), so publishing 18083->80 maps the + # host port at a container port nothing serves: reproduced as HTTP 000. + # container-specs.sh and apps/lnd-ui/manifest.yml were corrected; this was + # the third copy of the same declaration and still broke FRESH installs. + lnd-ui) PORT_ARG=""; NET_ARG="--network host"; REG_IMG="${LND_UI_IMAGE}" ;; electrs-ui) PORT_ARG=""; NET_ARG="--network host"; REG_IMG="${ELECTRS_UI_IMAGE}" ;; esac CONTAINER_NAME="archy-$ui" diff --git a/scripts/image-versions.sh b/scripts/image-versions.sh index 3d4e92e4..4e349e80 100644 --- a/scripts/image-versions.sh +++ b/scripts/image-versions.sh @@ -118,7 +118,7 @@ PENPOT_FRONTEND_IMAGE="$ARCHY_REGISTRY/penpot-frontend:2.4" # Custom UI containers (built from docker/ dirs, pushed to registry) BITCOIN_UI_IMAGE="$ARCHY_REGISTRY/bitcoin-ui:1.7.119-alpha" -LND_UI_IMAGE="$ARCHY_REGISTRY/lnd-ui:latest" +LND_UI_IMAGE="$ARCHY_REGISTRY/lnd-ui:1.7.119-alpha" ELECTRS_UI_IMAGE="$ARCHY_REGISTRY/electrs-ui:latest" # Base images