Commit Graph
82 Commits
Author SHA1 Message Date
archipelago 8f144c3038 chore: remove retired AdGuard app and refresh release docs 2026-09-12 05:05:33 -04:00
archipelago 8258705df7 chore: sync v1.8.13-alpha whats new 2026-09-12 04:44:34 -04:00
archipelago e625b29d9e fix: route GitWorkshop installs through orchestrator 2026-09-12 04:41:14 -04:00
archipelago c34d6ef76f docs(release): finalize 1.8.12 notes
Demo images / Build & push demo images (push) Successful in 4m22s
2026-09-11 06:55:17 -04:00
archipelago dac29baf97 fix(release): surface companion build and secure GitWorkshop deps 2026-09-11 06:10:59 -04:00
archipelago ef8c3a76be chore(release): define 1.8.12 publication gates 2026-09-11 05:37:21 -04:00
archipelago f5c0ba85cd feat(release): stage GitWorkshop and next node updates 2026-09-09 18:15:21 -04:00
archipelago 973356df16 fix(ecash): harden Minibits claim persistence 2026-09-08 21:16:57 -04:00
b9862c7643 fix(ui): escape a second live vue-i18n message-compile crash + add a full-sweep test
Same class of bug as the Minibits address label
(settings.passwordNeedSpecial: "...(!@#$%^&* etc.)" — a bare @ vue-i18n
parses as linked-message syntax). This one is live in
ChangePasswordSection.vue's password-strength validator: typing a new
password with no special character throws this exact
SyntaxError the moment the message is rendered. Fixed the same way
({'@'} escaping).

Added locales/__tests__/i18nMessagesCompile.test.ts, which walks every
string in every locale file and asks the real vue-i18n compiler to
parse it — confirmed it fails on both bad strings before their fixes
and passes clean now, with no other landmines left in either locale
file. This closes the whole bug class rather than just these two
instances; a future bad interpolation string fails `npm test` instead
of only a live crash report.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EawZPP9iidXj6Tvg3EpG3a
2026-09-08 21:16:57 -04:00
6fe9c5f81b fix(ui): escape the literal @ in the Minibits address label
Root cause of "click Receive, click Ecash, the modal disappears" (in
both the browser and the Android companion's WebView, since both host
the same neode-ui bundle): vue-i18n treats a bare @ as the start of
"linked message" syntax. receiveBitcoin.lnAddressLabel ("Your
@minibits.cash address:") isn't valid linked-message syntax, so
*compiling* that message throws a SyntaxError the instant it's first
rendered — i.e. the moment wallet.ecash-lnaddress resolves and the
address section becomes visible. The uncaught render-function error
blanks the whole teleported modal, which is indistinguishable from it
just closing.

Confirmed with a real (non-mocked) Vue app + real vue-i18n compiler in
a headless Chromium — a Vitest run with `t` mocked to a no-op, which is
how the existing component test suite covers this file, cannot catch a
bad message string at all. Fixed by escaping the @ as {'@'} — the same
pattern the codebase already uses for settings.domainNamePlaceholder
("user{'@'}example.com"). Added a regression test using the real
vue-i18n instance instead of the mocked one; verified it fails on the
old string and passes on the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EawZPP9iidXj6Tvg3EpG3a
2026-09-08 21:16:57 -04:00
28454264ac test(ui): guard the ecash-tab-click path in ReceiveBitcoinModal
Operator report (2026-09-08): clicking the Ecash tab appeared to close
the whole Receive modal. Added a regression test simulating the exact
click, both for wallet.ecash-lnaddress succeeding and failing — the
tab switch alone never emits `close` or unmounts the dialog in either
case, so this isn't reproduced by a plain component-level click; the
investigation continues with the reporter for a browser-console repro.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EawZPP9iidXj6Tvg3EpG3a
2026-09-08 21:16:57 -04:00
ce5c04d49d fix(ecash): stop Minibits LN-address claims from being silently lost
A Minibits /claim response consumes the payment server-side the instant
it's returned — it can never be re-fetched. claim_and_redeem previously
decrypted/redeemed each claim inline and just warn!-logged any failure,
so a mint-unreachable blip, a stale cached server key, or an operator
who'd edited their accepted-mints list to drop the default mint (via
streaming.configure-mints) could make a real payment vanish with
nothing but a log line to show for it — claimed_count/received_sats
still came back as a clean 0, identical to "nothing arrived."

Now: every fetched claim is persisted to MinibitsState.pending_claims
before decrypt/redeem is attempted, survives failures across polls
instead of being dropped, and claim_and_redeem no longer bails out on a
fetch error without first retrying whatever was already pending.
ensure_mint_accepted self-heals the accepted-mints allow-list so the
Minibits mint (the address is inherently backed by it) can't be
excluded out from under a claim. ClaimOutcome gains failed_count,
threaded through wallet.ecash-lnaddress-claim and shown in
ReceiveBitcoinModal so a stuck claim is visible instead of silent.

Also fixes the server_nostur_pubkey field-name typo (no live state to
migrate — this feature hasn't shipped yet).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EawZPP9iidXj6Tvg3EpG3a
2026-09-08 21:16:57 -04:00
ssmithxandarchipelago ce9fca1c38 feat(ecash): Minibits @minibits.cash Lightning address on Cashu receive
The wallet used Minibits only as a Cashu mint, so the node could hold and
swap ecash there but had no addressable name at it. This derives a LUD-16
Lightning address (name@minibits.cash) from the node's own ecash wallet and
surfaces it in the ecash Receive tab above the existing paste-token box.

Identity reuses the NUT-13 ecash phrase, so there is no second secret:
  - seedHash = sha256(mnemonic.to_seed("")) — the exact hash the Minibits app
    stores, so restoring the same phrase recovers the same address both ways;
  - Nostr keys via NIP-06 at m/44'/1237'/0'/0/0 (nostr-sdk Keys::from_mnemonic,
    pinned by a unit test against the NIP-06 vector so a bump cannot silently
    move the derivation and orphan the profile).

Backend (wallet/minibits.rs) implements the verified live /v3 flow: NIP-42
challenge/verify -> JWT, idempotent /profile registration with collision
retry, and /claim polling that NIP-04-decrypts each token (service pubkey read
from the address's own LUD-16 metadata, constant fallback) and redeems it
through ecash::receive_token. Mainnet-only; state cached 0600 in
wallet/minibits.json.

New RPC: wallet.ecash-lnaddress (register-or-read, idempotent) and
wallet.ecash-lnaddress-claim (sweep Lightning payments into ecash). The modal
fetches the address on tab open, renders QR + copy, and sweeps claims while
open; a registration failure is non-fatal so paste-token still works.

Verified end-to-end against production: registered a disposable
@minibits.cash address, confirmed it resolves via /.well-known/lnurlp, and the
claim poll returns cleanly.
2026-09-08 21:16:57 -04:00
f9af30b08a feat(openwrt): make TollGate payout Lightning address configurable
Archipelago never touched /etc/tollgate/identities.json — the "owner"
payout identity was whatever the router's TollGate install happened to
default to. Confirmed live against archy-x250-pa3: an unmodified upstream
placeholder (tollgate@minibits.cash), meaning 79% of every customer payment
would auto-payout to an address the operator never chose and doesn't
control.

Adds TollGateConfig.payout_address (opt-in — None leaves the router
untouched), config::apply_payout_identity() to merge it into the "owner"
entry of identities.json without disturbing the merchant keypair or the
other profit-share identities, an RPC param on openwrt.provision-tollgate,
and a status field + reconfigure-form input in the OpenWrt Gateway panel.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KTdMfVJChCwCCYF1ZTRQLc
2026-09-08 21:06:36 -04:00
archipelago 5da91e4099 chore: prepare release v1.8.11-alpha 2026-09-07 04:32:02 -04:00
archipelago 62731cc729 test(ui): use shipped app for generated launch port check 2026-09-07 03:30:29 -04:00
archipelago b010471a4a chore(release): prep v1.8.11 notes and link checks 2026-09-07 03:26:01 -04:00
ssmithxandClaude Sonnet 5 be06b3ce2b fix(ui): stop sending an empty ssh_password over the saved router connection
provisionTollgate/saveTollgateConfig/scanWifi/configureWan all fell
back to the Connect form's local refs (host/sshUser/sshPassword) when
connectedParams was null. Those refs only get populated if the form
was actually submitted this session — on a normal page load the
router reconnects via the server-persisted config instead, leaving
sshPassword at its default ''. Sending that as an explicit
(empty-but-present) ssh_password overrides the backend's saved-config
fallback, so every action auths with a blank password instead of the
real saved one.

Added authParams(): omit host/ssh_user/ssh_password entirely unless
connectedParams is actually set, same as the status poll already does.
Caught live: dropbear on archy-x250-pa3's router logged a single bad
password attempt at the exact moment "Install TollGate" was clicked,
sandwiched between periodic status-poll connections succeeding with
the real saved password.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0176RpCxFNS9ZaSJjL72W9Z5
2026-09-05 15:14:07 +00:00
archipelago 1ca002661b fix(lnd): SendPaymentV2 needs an explicit fee budget — absent means ZERO
Demo images / Build & push demo images (push) Successful in 3m28s
v1.8.9's move to Router.SendPaymentV2 shipped without fee_limit_sat,
and the v2 route treats an ABSENT fee limit as zero allowed fees.
Every real route carries a routing fee (the 2-hop route here: 1.5
sats), so the pathfinder rejected them all and the wallet answered
"No route to the recipient" on EVERY send — all day, on healthy
channels with plenty of liquidity both ways.

The router debug log makes it unambiguous:
  wallet payment (v1.8.9 backend): fee_limit=0 mSAT     -> no route
  same payment by hand (lncli --fee_limit=100): fee_limit=100000 mSAT -> settles in 0.65s

My earlier "pipeline verified" claim was wrong — the manual lncli
verification set a fee limit by hand and masked this exact bug. The
400k that succeeded this morning went through the pre-update backend
on the pre-update LND.

Payments now carry lncli's own default budget — the payment amount
(100%), preferring the payer-supplied amount for zero-value invoices
and the invoice's own amount otherwise, with a nominal floor so the
limit can never be zero. Unit-pinned so it cannot regress.
2026-09-01 18:42:37 -04:00
archipelago 0d0e2e243a feat(lnd): channel-peer watchdog — a dropped peer link heals itself
Demo images / Build & push demo images (push) Successful in 3m49s
LND normally reconnects channel peers after a restart, but not reliably:
after long or repeated downtime (an app update, a node reboot,
reconciler churn) the peer link can stay down for hours while BOTH
endpoints keep the channel flagged disabled in the routing graph. The
node looks perfectly healthy, the wallet shows balance, and every
payment in either direction fails "no route to the recipient" —
observed live on framework-pt (2026-09-01): its only channel sat
disabled on both policy sides for ~17 hours after the LND 0.21.2
update, while shorty had 583k spendable and the user was told, by a
mis-mapped modal, that they had 'no payment channel'.

The channel graph is desired state — every open channel should have a
live peer connection. A daemon-side watchdog now enforces it:

- every 2 minutes, list channels + peers over LND REST
- for each channel whose remote peer is not connected, look the peer's
  advertised addresses up in the public graph and dial one
- per-peer retries throttled to 10 minutes so an unreachable peer is
  not hammered; 'already connected' counts as done; a peer with no
  advertised address is logged once per pass (cannot be dialed)
- no-ops quietly on nodes without LND (missing macaroon) and while a
  wallet is locked (503 body has no channels)

Unit tests pin the selection against the live REST shapes
(remote_pubkey in /v1/channels vs pub_key in /v1/peers).

v1.8.10 CHANGELOG + What's New entries staged so the next release run
is clean first time.
2026-09-01 17:51:15 -04:00
archipelago 1464b1b24d fix(wallet): the Lightning funding gate states the node's real channel state
Demo images / Build & push demo images (push) Successful in 3m38s
"LND thinks I do not have a channel" while the wallet showed plenty of
liquidity (framework-pt, 2026-09-01): the send gate sums outbound over
FULLY-OPEN channels only, which is correct — a just-opened channel
sits in LND's pending list until it has ~3 confirmations, and an
open channel can have all its balance on the far side — but the modal
then claimed the node had NO channel at all, in every one of those
states, and pointed the user at opening another one.

The gate already fetched the full channel list; it now records WHY
liquidity is zero and the modal says the truth per state:
- pending channels -> "your new channel is waiting for on-chain
  confirmations, it unlocks automatically, nothing is needed from you"
  (and no "Open a channel" button — that would send the user to fix
  a problem they don't have, possibly opening a second channel)
- open channels, zero on the needed side -> "balance is on the far
  side — you can receive but there's nothing to send right now"
- payment refused with a routing/liquidity error -> says so, instead
  of claiming no channels
- only a genuinely channel-less node keeps the open-one guidance

Eleven unit tests pin the state machine, including the regression
case (pending-only -> 'pending', not 'none') and fail-open on RPC
errors.
2026-09-01 11:40:25 -04:00
archipelago 8e988be853 chore(release): v1.8.9-alpha prep — What's New block + version bumps
Demo images / Build & push demo images (push) Successful in 3m48s
The release gate requires the freshly-built bundle to embed the new
version, and the version reaches the bundle through the What's New
modal in AccountInfoSection — there was no v1.8.9-alpha block yet, so
create-release.sh correctly refused to ship a bundle that looked stale.
This adds the block (the user-facing summary of today's LND/HTTPS/
launcher/NPM/Portainer fixes) and carries the version bumps the
aborted run had already written (Cargo.toml, Cargo.lock, package.json,
package-lock), so the re-run starts from a clean tree.

Verified: npm run build now produces assets containing 1.8.9-alpha
(Settings chunk), i.e. the exact check the script runs passes.
2026-09-01 10:54:47 -04:00
archipelago 3347b8b8b9 fix(ui): https app launches and the nostr bridge follow the frame's real origin
Three launcher/bridge defects combined to make HTTPS dashboards look
broken while HTTP ones worked:

1. portAuth() looked the launch port up under the name the user clicks
   ('mempool-web', 'lnd', 'bitcoin-knots'…), but the signed catalog
   declares those ports under the manifest id that owns them
   (archy-mempool-web, lnd-ui, bitcoin-ui). The lookup missed,
   portIsGateFronted answered false, and an HTTPS dashboard handed app
   frames http:// URLs — blocked as mixed content: mempool and IndeeHub
   'did not connect', bitcoin knots/core opened http:// in a new tab.
   Resolution now follows launch aliases, then a port-wide catalog scan
   that only answers when every declarer of that port agrees (a port
   any app publishes as plain HTTP is never upgraded to https).

2. The signed-catalog cache was only warmed by the Store/Discover
   views, so a user who went straight to My Apps launched apps with an
   empty cache. Warmed at dashboard mount now — fetchAppCatalog()
   already memoizes with a 1h TTL.

3. The NIP-07 bridge compared event.origin for strict equality with the
   recorded (http) app URL and replied to the recorded URL as the
   postMessage targetOrigin — both break the moment a frame is scheme-
   upgraded (cached HSTS did exactly that): every nostr request was
   silently dropped and replies to the stale origin threw. The bridge
   now matches host+port (scheme deliberately ignored) and always
   replies to event.origin — the frame's real origin.

Unit tests cover alias resolution (incl. bitcoin-knots→8334→https),
the conservative port-scan, and scheme-agnostic sender matching.
2026-09-01 10:29:05 -04:00
archipelago e382e679ae fix(apps): NPM needs /etc/letsencrypt mounted and NET_BIND_SERVICE
Converting Nginx Proxy Manager to a platform manifest (fc68c5b6) dropped
two things its image hard-requires, and the result was an endless
start/die loop — shorty-s watched it restart 3,176 times:

1. /etc/letsencrypt mount: NPM's s6 'prepare' service refuses to boot
   without it ('ERROR: /etc/letsencrypt is not mounted!'). Mounted from
   the same persistent app directory as before
   (/var/lib/archipelago/nginx-proxy-manager/letsencrypt), so existing
   certificates are preserved — no data moves, no migration.

2. NET_BIND_SERVICE: NPM's internal nginx listens on 80, 443 AND 81,
   and the orchestrator runs --cap-drop=ALL. The legacy podman-run path
   defaulted to the full capability set (and the legacy repair path in
   package/config.rs always listed it), which is why this only broke
   once the manifest became the source of truth.

The signed catalog embeds manifests with origin-wins semantics, so the
catalog carries the fix for every catalog-covered node — regenerate it
here (plus the generated store/launcher-port artifacts, which also pick
up drift from bf6ef964's retired apps). Catalog re-signing follows the
usual ceremony.
2026-09-01 10:29:05 -04:00
archipelago 19467e9b7c chore(apps): sweep pin bumps — gitea 1.27.3, vaultwarden 1.37.2, filebrowser 2.63.23, home-assistant 2026.8.3, adguardhome 0.107.79, portainer 2.45.0, pine-whisper 3.6.0
Demo images / Build & push demo images (push) Failing after 39s
First upstream sweep since v1.8.5: the safe patch/minor pins, mirrored
into our registry first (source.archipelago-foundation.org/lfg2025/*).
Held for their own careful passes: the majors (grafana 11, nextcloud,
uptime-kuma 2, bitcoin-core 29, the DBs) and consensus-sensitive apps
(fedimint, electrumx 2.0). LND 0.21.2 needs an in-house image build —
upstream publishes none.
2026-09-01 04:50:49 -04:00
archipelago 192e045426 feat(ui): SSH-over-mesh card, store-listing filter, icon treatment
Demo images / Build & push demo images (push) Failing after 41s
Settings gains the SSH-over-mesh card (danger-zone confirmation for the
any-peer scope, sshd preflights, fipssh copy hint). The signed-catalog
merge filters components via the shared serviceNames module; Discover
grids get the standard icon container; install no longer yanks the user
to My Apps; v1.8.8 release notes.
2026-09-01 02:41:55 -04:00
archipelago 2ad0171e5f fix(ui): drop the now-unused scheme helper
Demo images / Build & push demo images (push) Failing after 36s
2026-08-31 19:08:17 -04:00
archipelago 46cb0bfd37 fix(ui): gate-fronted https launches + signed-catalog App Store
Demo images / Build & push demo images (push) Failing after 36s
directAppUrl(), the legacy open() path, and resolveRuntimeLaunchUrl()
now upgrade to https only for ports the app gate fronts — decided from
the signed catalog's embedded manifest ports (auth gated/open), so
plain-HTTP publishes (legacy installs, auth:none API ports like
Cuprate's RPC) keep http instead of failing outright. fetchAppCatalog()
merges the daemon-verified signed catalog into the App Store listing
(signed entries appear immediately; community copy supplies featured
and curated metadata), and Marketplace.vue uses the same dynamic fetcher
as Discover so the grid sees signed-new apps too.
2026-08-31 18:41:00 -04:00
archipelago b8593c9090 docs(release): v1.8.7 notes — https app launches + platform round
Demo images / Build & push demo images (push) Failing after 34s
2026-08-31 18:40:51 -04:00
archipelago 687196ad3b chore: prepare release v1.8.7-alpha
Demo images / Build & push demo images (push) Failing after 37s
2026-08-31 17:08:37 -04:00
archipelago 7c0a492c43 fix(ui): launch apps on the page's scheme over HTTPS
New-tab apps and the companion WebView got hardcoded http:// URLs, so a
node reached over HTTPS opened Vaultwarden, BTCPay, Grafana et al in
cleartext. Every app port is gate-owned and serves TLS on the same port
(appgate/tls.rs), so directAppUrl(), the legacy open() path, and
resolveRuntimeLaunchUrl() now follow the page's scheme. HTTP pages (the
kiosk, LAN) are unchanged; netbird keeps its unconditional https.
2026-08-31 17:08:26 -04:00
archipelago 5ffdcc9936 docs(release): explain the v1.8.7 correction
Demo images / Build & push demo images (push) Failing after 39s
2026-08-31 15:46:18 -04:00
archipelago 9cf07e1eac fix(release): enforce the v1.8 What's New floor
Demo images / Build & push demo images (push) Failing after 39s
2026-08-31 15:44:48 -04:00
archipelago 7bc9f69b1f fix(settings): start What's New history at v1.8.0 2026-08-31 15:12:18 -04:00
archipelago 7a39d8fbd1 fix(settings): sort What's New history newest-first
Demo images / Build & push demo images (push) Failing after 36s
2026-08-31 14:50:55 -04:00
Dorian 7d09418a09 Companion 0.5.28 — backup & restore (#128), NIP-46 remote signer (#139), companion-gated install pitch (#61 residual)
The companion-agent queue from the 2026-08-30 handoff, complete:

- Backup & Restore: hub sub-page, SAF export/import sealed in the
  node's ADR-005 envelope (Argon2id + ChaCha20-Poly1305, byte-compatible
  with core backup.rs), merge-only restore, no cloud.
- Remote Signer: the phone is the NIP-46 bunker — nsec generate/import,
  nostrconnect:// QR pairing (scanner + OS deep link), per-request
  approve/deny card, NIP-44 v2 transport with NIP-04 receive fallback,
  wire-faithful to rust-nostr's reference bunker. Crypto pinned to the
  official NIP-44 + BIP-340 vectors; e2e harness included.
- #61 residual: banner + manual intro trigger + overlay all gate on
  isCompanionApp() (web-side, vitest-covered).
- Hub modal: new sub-pages like Nodes/FIPS, 70% height cap, node ULA
  display/copy in the Nodes list, fipssh Termux helper (npub→ULA is a
  pure public-key function — verified against the fips crate).

Issues #61 (comment), #128, #139 closed on the tracker.
2026-08-31 19:34:05 +01:00
archipelago 3409db569e docs(release): complete the v1.7.44→current release-notes audit
Demo images / Build & push demo images (push) Failing after 37s
The RELEASE_NOTES_BACKLOG gate for cutting the next release, closed out:

- Eight sections backfilled to the curated standard, from the Settings
  What's New blocks, the old-lineage release commits, and the hotfix
  diffs: v1.7.44 (was four raw commit-hash lines), v1.7.47/48/64/65
  (thin), and v1.7.50/51/107 (real tagged releases whose sections were
  missing entirely — v1.7.107 restored verbatim from the curated copy
  at 35e9c624 that later went missing).
- Mechanical inventory across all 92 sections in range: every section
  now has ≥3 curated bullets, zero raw-hash entries.
- What's New modal regenerated for the three restored versions
  (sync-whats-new --check passes, 92 versions present).
- Manifest-notes-only confirmed by construction: the manifest reads its
  changelog from CHANGELOG.md and check-release-manifest.sh rejects raw
  or thin entries before publishing.

Evidence trail for the backfills is recorded in
docs/RELEASE_NOTES_BACKLOG.md.
2026-08-31 08:05:42 -04:00
Dorian d259f3cbb9 fix(web): companion-gate the store banner + manual intro trigger (#61 residual)
Only the auto-popup was companion-gated — inside the companion WebView
users still saw the 'install the companion' banner in the App Store and
could pop the intro overlay through it. Gates all three paths on
isCompanionApp(): CompanionBanner self-hides, openCompanionIntro() is a
no-op, and the manual-open watcher in CompanionIntroOverlay refuses to
open (the overlay's raw window check also moves to the canonical helper
so there is exactly one detection). No APK change.
2026-08-31 12:56:32 +01:00
archipelago c5eeb31055 fix(ui): wifi setup on a fresh install — reveal toggle + a no-network callout (#145)
Demo images / Build & push demo images (push) Failing after 39s
Two reports from a fresh install without a cable:

(a) No way to see the WiFi password being typed. Every password field in
    the app was a bare type=password input. PasswordRevealInput is the
    reusable fix — masked by default, one-tap eye toggle, v-model and
    enter pass-through — first applied to the WiFi prompt in ServerModals
    so a long key typed from across the room can be verified.

(b) WiFi settings are undiscoverable with no wired internet. New
    OnboardingNetworkCallout floats over every onboarding step when the
    node has NO physical link at all (no ethernet up, no WiFi associated
    — polled from network.list-interfaces, self-dismissing the moment a
    link exists) and deep-links 'Connect to WiFi' to
    /dashboard/server?open=wifi, which Server.vue consumes by popping the
    WiFi picker on arrival. Deliberately scoped the other way too:
    Archipelago is offline-first, so 'no internet' never nags — only 'no
    link at all', only during onboarding (the wrapper hosts /login too;
    the callout is restricted to /onboarding/* routes), and a failed probe
    stays silent. The query is consumed via history.replaceState so a
    KeepAlive tab-return never re-pops the modal, and Server.vue keeps
    reading it from the real URL rather than vue-router — its
    KeepAlive-mounted tests have no router context to give.

Verification: full frontend suite 1023/1023; type-check clean; production
build clean with both new strings confirmed in the emitted bundles
(OnboardingWrapper + Server chunks).
2026-08-31 07:47:47 -04:00
archipelago 51a5473e22 docs(release): v1.8.5-alpha changelog section + What's New sync
Demo images / Build & push demo images (push) Failing after 42s
Curated release notes for the pending v1.8.5-alpha: Cuprate (with the
two review catches), kdump/rasdaemon + the host-fixup OTA channel, the
uninstall-abort fix, federation inline-picture routing, honest disk
usage, the three lying-screens fixes (#143/#127/#129), durable mesh
notifications + router recovery (#57/#103), and upstream-release tracking
with the first-sweep safe bumps.

What's New modal synced via scripts/sync-whats-new.py (--check passes;
89 versions, all present). Per docs/RELEASE_NOTES_BACKLOG.md the
v1.7.44-alpha -> current section audit remains the open item before the
tag.
2026-08-31 07:23:53 -04:00
archipelago 7b88ba59b2 chore(apps): bump the pins that need no mirroring; curate Cuprate's store entry
Demo images / Build & push demo images (push) Failing after 40s
Pin bumps (all verified pullable from their public registries before
editing, so none can become an image-not-found on a node):

  strfry           1.1.1 -> 1.1.2              (dockurr/strfry, direct pull)
  btcpay-server    2.4.2 -> 2.4.3             (docker.io/btcpayserver, direct pull)
  netbird (nginx)  1.31.3-alpine -> 1.31.4-alpine
  pine   (nginx)   1.31.3-alpine -> 1.31.4-alpine

image-versions.sh moved in lockstep for BTCPAY_IMAGE — it is the baseline
the update badge compares against. Held back deliberately, per the risk
policy from the Aug-17 pass: gitea (four minors of DB migrations),
portainer (six minors), filebrowser (2.27 -> 2.63), fedimint/gateway
(0.8 -> 0.12, real migrations), lnd (money-critical), netbird-server/
netbird-dashboard (0.x, must move in lockstep), and everything with a
major jump or a data migration.

Cuprate also gets its curated store entry (category money, tier optional,
icon, repo) — same shape as the Alby Hub / phoenixd entries — synced
through generate-app-catalog.py into both store catalogs and the
app-session config. The fips launch-port list is unchanged (Cuprate has
no UI port; the generated file round-trips to the committed bytes after
cargo fmt).

Three further bumps are prepared and parked on the
app-bumps-mirror-pending branch, blocked only on a registry-push token:
vaultwarden 1.37.2-alpine, archy-nbxplorer 2.6.11, home-assistant
2026.8.3 — all mirror-backed, and the push credential on record for the
lfg2025 namespace is dead.

Drift gate: check-app-catalog-drift.py --release --strict clean
(31 store entries, 0 drift, 0 missing). appSessionConfig tests 7/7.
2026-08-30 16:22:26 -04:00
archipelago b4714f1773 fix(store): defer multi-version app version choice (#129)
Demo images / Build & push demo images (push) Failing after 39s
2026-08-30 10:23:58 -04:00
archipelago d79ca54019 fix(wallet): disclose backup passphrase only when needed (#127) 2026-08-30 10:23:58 -04:00
archipelago 758332d63d fix(openwrt): make stale router config recoverable (#103) 2026-08-30 10:23:58 -04:00
archipelago ee5123af68 test(ui): satisfy strict build indexing
Demo images / Build & push demo images (push) Failing after 41s
2026-08-30 10:18:02 -04:00
archipelago a624d11b6a fix(mesh): make radio message notifications durable (#57) 2026-08-30 10:16:33 -04:00
archipelagoandClaude Opus 5 2c984fbd49 fix(ui): the IBD-finished toast no longer tells a node without LND to fund its wallet
Demo images / Build & push demo images (push) Failing after 52s
When Bitcoin's IBD completed mid-Lightning-goal, the watcher toasted
"you can now fund your wallet" — but the on-chain wallet lives in LND,
not Bitcoin Core. The watcher only checked that the goal had pending
manual steps, never that the install-LND step had completed, so a user
whose LND wasn't installed yet was pointed at a flow that could not
work: the fund modal's address comes from lnd.newaddress and does not
exist until LND is installed (issue #143).

The toast now checks LND's install state at fire time. With LND
installed the message is unchanged; without it, the toast says the
actual next step — install Lightning (LND) — and the Finish setup
button lands on the goal wizard, whose active step is the pending
install-LND one (the wizard itself was already correctly sequenced).

The watcher had no tests; added four pinning its contract: the two
message branches, silence with no in-progress goal, and silence when
the chain was already synced at page load.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 09:24:01 -04:00
archipelagoandClaude Opus 5 3d7de3e902 fix(release): a dateless changelog header silently skipped the release
Demo images / Build & push demo images (push) Failing after 38s
create-release aborted at [4/8] with "web/dist/neode-ui does not contain
v1.8.4-alpha — the frontend build no-opped or its output is stale". The
build had not no-opped: it was fresh, and simply had no 1.8.4 string to
embed.

sync-whats-new.py only matches '## vX.Y.Z (YYYY-MM-DD)'. The entry read
'## v1.8.4-alpha (draft — date set at cut)', so the version was invisible
to it: the gate's whats-new-sync stage reported "87 versions, all present"
while the release being cut had no What's New block. That modal is the
only place a version string appears in the frontend, so the bundle carried
none and the freshness check — correctly — refused it, while naming the
wrong cause. Step [5/8] only greps for '^## v1.8.4-alpha (' so it passed
the draft too.

Three changes: date the v1.8.4-alpha entry, insert the modal block it was
owed, and make the sync tool refuse any version header without a real date
instead of skipping it. Skipping is what let a wrong "all present" through.

Verified: the draft header now fails the check with an explicit message,
the dated one passes (88 versions, up from 87), and a rebuilt bundle
contains 1.8.4-alpha where it did not before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 03:01:39 -04:00
archipelagoandClaude Opus 5 bd299318c0 chore(catalog): re-sign the catalog with the new pins, and fix a stale firewall list
Demo images / Build & push demo images (push) Failing after 41s
Regenerates both catalogs from the manifests so the 15 pin bumps become
real. The catalog overrides on-disk manifests on every node, so until
now those bumps were edited but inert.

There are two catalogs and regenerating one is not enough:
generate-app-catalog.sh writes releases/app-catalog.json (the signed one
nodes fetch), while generate-app-catalog.py writes app-catalog/catalog.json
and neode-ui/public/catalog.json (the source pair, the second baked into
the frontend app store). check-app-catalog-drift.py --release --strict
reads the *source* catalog, so regenerating only the release one left it
failing and would have aborted the ISO gate at stage 1 — after the
signing and tagging were already done. Drift is now 0.

The regeneration also rewrote fips/app_ports.rs, which had not been
regenerated since the initial open-source import. Diffing the port values
rather than the reformat: 36 -> 37, a single addition, **8187 — Alby
Hub**. Its port has never been in the FIPS firewall allow-list, and by
the same token neither has any app onboarded since that import. Nothing
else changed.

Catalog signed by the pinned release root and verified with
`ceremony verify`; registry trust floor checked before signing, both
hosts trusted by the deployed fleet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 12:02:40 -04:00
archipelagoandClaude Opus 5 ee40880ce5 feat(ecash): import a backup phrase from another NUT-13 wallet
Demo images / Build & push demo images (push) Failing after 2m2s
Bring-your-own, the open question the migration plan left. Point this
wallet at a phrase you already hold — Minibits, Nutstash, cdk-cli — and
its coins become restorable here, which is the other half of "these
words are portable".

Replacing an established phrase is the one genuinely lossy thing this
module can do, so it is treated that way. The coins already held stay
spendable: they are proofs, not derivations, and nothing here touches
`ecash.json`. But they were minted under the *old* phrase, so a restore
will no longer find them. Hence an explicit confirm, a prompt to reveal
and write down the current phrase first, and — most importantly — the
replaced phrase is archived beside the wallet, never overwritten. It may
be the last copy of the words a balance was minted under, and quietly
destroying that is precisely what this module exists to prevent.

Re-importing the phrase already in use is a no-op rather than a
replacement, so it archives nothing.

Counters are deliberately left alone. They are per-keyset and
seed-relative, so under a new seed they merely start high, which costs
nothing because a restore scans from zero regardless. Resetting them
would be the dangerous choice on the day someone imports the phrase they
were already using.

`imported` is its own provenance rather than reusing `independent`: both
mean the node's recovery phrase does not cover the wallet, but only one
of them means the operator already knows where else the words live.

15 NUT-13 tests green, 1000 frontend tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 10:09:19 -04:00