Commit Graph
122 Commits
Author SHA1 Message Date
archipelago 4237fb5e79 fix(wallet): prioritize LND boot and reject unavailable balances 2026-09-15 15:09:08 -04:00
archipelago 4021c1f496 chore: prepare release v1.8.17-alpha 2026-09-15 12:53:06 -04:00
archipelago 5f8de584bc docs: add v1.8.17-alpha release notes
Demo images / Build & push demo images (push) Failing after 42s
2026-09-15 12:33:24 -04:00
archipelago 1b0ed281b2 chore: prepare release v1.8.16-alpha 2026-09-15 03:59:43 -04:00
archipelago 9c6580f5c0 fix: prevent stale catalog updates and redundant container recreation
Demo images / Build & push demo images (push) Failing after 40s
2026-09-15 03:40:21 -04:00
archipelago 969570e38b chore: prepare release v1.8.15-alpha
Demo images / Build & push demo images (push) Successful in 3m29s
2026-09-13 04:32:16 -04:00
archipelago b73d646db5 docs: prepare 1.8.15 alpha notes
Demo images / Build & push demo images (push) Successful in 3m22s
2026-09-13 03:06:53 -04:00
archipelago 8c37ff412c fix: present Cuprate as one launchable app
Demo images / Build & push demo images (push) Successful in 3m28s
2026-09-13 03:06:22 -04:00
archipelago a4f3415f0f chore: prepare release v1.8.14-alpha
Demo images / Build & push demo images (push) Successful in 3m25s
2026-09-13 02:53:11 -04:00
archipelago c9c9ebe6d4 docs: sync whats new for 1.8.14 alpha
Demo images / Build & push demo images (push) Successful in 3m18s
2026-09-13 02:34:45 -04:00
archipelago a4f80e7ec1 test: update GitWorkshop launcher deep-link expectation
Demo images / Build & push demo images (push) Successful in 3m14s
2026-09-13 01:48:36 -04:00
archipelago c9bae926a5 test: satisfy strict indexed access
Demo images / Build & push demo images (push) Successful in 3m14s
2026-09-13 01:41:11 -04:00
archipelago 00682e6420 test: expect Cuprate launches through companion UI 2026-09-12 16:17:47 -04:00
ssmithxandarchipelago 7c0ba14a00 feat(neode-ui): launch cuprate tiles on the Cuprate UI companion
cuprate publishes only raw JSON RPC (18090 restricted, 18183 p2p), so
launches must land on the companion on :18091, never on the running
node's runtimeUrl — same root-path special-case bitcoin uses, with the
dev vite proxy for /app/cuprate-ui/. Alias cuprate -> cuprate-ui so the
port-auth lookup finds the gated launch port on HTTPS nodes; pin the
companion icon to the cuprate mark.
2026-09-12 16:14:55 -04:00
archipelago 4f0d123f27 feat: open GitWorkshop at Archipelago repository
Demo images / Build & push demo images (push) Successful in 3m33s
2026-09-12 15:57:57 -04:00
archipelago 13b1329c21 test: keep Cuprate stack as one app entry
Demo images / Build & push demo images (push) Successful in 4m0s
2026-09-12 15:33:05 -04:00
archipelago c4aa72dccc fix: route installs to apps or services
Demo images / Build & push demo images (push) Successful in 3m39s
2026-09-12 15:07:33 -04:00
archipelago d35474f774 fix: defensively hide legacy node identity
Demo images / Build & push demo images (push) Successful in 3m31s
2026-09-12 10:24:01 -04:00
archipelago a03f340bd1 fix: keep node key out of profile signer picker
Demo images / Build & push demo images (push) Successful in 3m26s
2026-09-12 10:06:41 -04:00
archipelago caaa2e729e fix: gate app launches on health readiness
Demo images / Build & push demo images (push) Successful in 3m47s
2026-09-12 09:35:25 -04:00
archipelago 39727dacbc style: format generated app ports 2026-09-12 05:37:55 -04:00
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 c1e14f7c7a chore: prepare release v1.8.12-alpha 2026-09-11 15:13:40 -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 b87f1f0612 chore: prepare release v1.8.10-alpha 2026-09-01 18:58:33 -04: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 2b4b60013c feat(lnd): build LND 0.21.2-beta in-house and sweep the pin
Demo images / Build & push demo images (push) Failing after 40s
Upstream publishes no docker images; our v0.18.4 image was built in-house.
This pass: official v0.21.2-beta release binaries (sha256-verified against
the signed release manifest), static, on alpine with the same entrypoint
shape as our existing image, pushed to our registry and smoke-run.
LND 0.21 auto-migrates the channel DB on first start (keeping a backup) —
the Update button is user-initiated, never auto-applied.
2026-09-01 05:14:50 -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 576c642da4 fix(apps): ollama resource type + adguardhome port; gate on collisions
Demo images / Build & push demo images (push) Failing after 38s
Ollama's embedded manifest failed the typed parse (memory_limit wants a
string) so the catalog overlay was skipped for it; AdGuard Home's
conventional :3000 collided with Grafana's. The release gate now runs
the host-port collision test (repo_app_manifests_have_no_host_port_collisions)
so this class can never ship untested again.
2026-09-01 03:31:02 -04:00