879 Commits

Author SHA1 Message Date
Dorian
875da6c630 fix(fips): harden companion mesh joins 2026-07-24 18:47:57 +01:00
Dorian
98e15b3af0 fix(ecash): change proofs no longer ride along inside sent tokens — double-credit bug
send_token_at split the mint swap's results with
partition(send_denoms.contains(amount)) — membership, not count. When a
change denomination equaled a send denomination (guaranteed when
spending from a proof worth exactly 2x the amount: send [n], change
[n]), the change proofs were serialized INTO the token, and the
receiver redeemed double. Both the wallet send flow and peer-files
purchases mint through this path.

The split now consumes exactly one proof per required send denomination
(everything else returns to the wallet as change) and hard-fails if the
mint returns an incomplete denomination set, so a token can never again
be silently over- or under-packed.

Note: tokens minted BEFORE this fix already contain the extra proofs
and will still redeem at their inflated value.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 13:34:48 +01:00
archipelago
df88d6d00a chore: signed manifest for v1.7.112-alpha
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 07:04:16 -04:00
archipelago
5771f318bd chore: release v1.7.112-alpha 2026-07-24 05:36:45 -04:00
archipelago
0cd2164d24 style: cargo fmt across today's touched modules
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 04:35:12 -04:00
archipelago
d924c59c6c style: cargo fmt on mesh_ports
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 04:29:15 -04:00
Dorian
e679b4e886 feat(companion): night-test polish sweep — 0.5.9
Everything from tonight's remote field testing:

- Back-to-dashboard fixed: JS bridges on the retained WebView delegated
  through live-composition callbacks (stale closures made apps refuse to
  launch after remote ⇄ dashboard), and reattach forces a layout pass
  (top/bottom UI was wrong until a tap).
- F*CK IPs MESH branded loader: full-screen on relaunch (startup race)
  and during the first connect after scanning a node QR.
- Party 'Share this app' is now a QR of the public vps2 download link
  (scan with any camera → install over any internet); direct APK
  file-share kept as a secondary action.
- Mesh party pairing is MUTUAL: scanner announces itself to the scanned
  phone's Flare /hello — both sides get the peer, a chat entry and a
  '👋 joined the party' message; scanner auto-opens the chat.
- Party scanner asks for CAMERA permission (fresh installs/reinstalls
  landed on a black preview).
- Node: device tokens mint unique companion-<id> names — pairing a
  second phone no longer revokes the first phone's login (the source of
  'reconnecting a lot' and the second phone's failure).

Served APK: 0.5.9 (vc29).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 02:03:47 +01:00
archipelago
2ad57c63f1 feat(mesh): app direct ports reachable over the mesh — IPv4 listeners mirrored onto [::]
The companion reaches the node at its fips0 ULA, and the web UI builds app
links as http://[<ULA>]:<direct port> — but rootless-podman published ports
bind 0.0.0.0 only, so every app URL was refused over the mesh (:8334 first).
A reconcile loop in the backend mirrors public IPv4 listeners: any port
>=1024 on 0.0.0.0 without an IPv6 any-listener gets a v6-ONLY [::] forwarder
to 127.0.0.1, following /proc/net/tcp* so install/remove and hardcoded
companion ports are covered without touching a single container. Purely
additive: IPv4/LAN/Tor access paths are untouched, v6only listeners cannot
collide with or intercept v4 traffic, and foreign IPv6 listeners win.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 19:09:14 -04:00
archipelago
1e89362e71 fix(nginx): IPv6 listeners — companion mesh HTTP to the node's ULA never connected
Phones on the FIPS mesh reach the node at http://[<fips0 ULA>], but every
shipped nginx config listened on IPv4 only — nothing answered [::]:80/443,
so mesh HTTP failed with the tunnel fully healthy (found live on
framework-pt during the vc24 5G session, 2026-07-23). Canonical conf gains
listen [::] lines and patch_nginx_conf self-heals deployed nodes (covers
the sites-enabled -> archipelago-http dev variant via the existing
canonicalize pass); validated by the existing nginx -t + rollback flow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 17:57:58 -04:00
archipelago
54ddd043bd feat(tv-input): gamepad->keyboard bridge daemon + controller-navigable modals
- archipelago-gamepad-keys: evdev->uinput daemon (pure python3 stdlib) that
  mirrors any attached controller as a virtual keyboard — D-pad/stick to
  arrows, A/B to Enter/Escape, X/Y to Space/f, shoulders to Tab/Shift+Tab.
  The browser sees real keys, so controllers work inside EVERY app iframe
  (IndeeHub, Jellyfin…) with zero per-app code. Ships via the ISO splice and
  bootstrap self-heal (kiosk nodes only), hotplug via 5s rescans.
  Implements docs/tv-input-iframe-apps.md.
- useControllerNav: an open [role=dialog][aria-modal] owns navigation —
  focus is pulled inside, arrows move spatially between its controls, Enter
  activates, Escape stays with the modal's own close handling. One standard
  mapping for every modal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 16:17:11 -04:00
archipelago
f72d4b92ac feat(content): seller-picked payment methods + music always in the bottom bar + video PiP
- Paid sharing: AccessControl::Paid gains an accepted-methods list
  (lightning/onchain/ecash/fedimint; empty = all, back-compat). Sellers pick
  methods in ShareModal, gated on what the node can actually receive (LND
  running/channel open, ecash wallet, fedimint joined) with an ⓘ that
  explains exactly how to enable a missing rail. Enforced server-side (the
  invoice/onchain mints refuse non-accepted methods; the serve gate only
  honors tokens/hashes for accepted rails) and the buyer's pay modal only
  offers what the seller accepts.
- Purchased music: the two remaining lightbox paths now use the bottom-bar
  player — the immediate post-ecash-purchase viewer and the Paid Files
  tab's window.open.
- Picture-in-picture buttons on the peer video player and the cloud media
  lightbox (utils/pip.ts; Chromium/Safari, no-op elsewhere).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 16:15:11 -04:00
archipelago
c58f84c6e9 feat(companion): npub-first pairing — node self-anchor + guaranteed public anchor in the QR
The pairing QR was effectively IP-first: the phone dialed the scanned LAN
host and went dark when the LAN renumbered or it left home. FIPS peers on
npubs; IPs are only dial hints. fanchors now leads with the paired node
ITSELF (fnpub @ current host, npub-keyed so LAN contact is direct p2p over
FIPS and survives DHCP), and fips.pair-info always includes the Archipelago
vps2 public anchor (pairing hint only — the node's own anchor file is not
modified) so the phone can rendezvous through the public mesh when away.
Contract doc updated with the REQUIRED app-side changes (built on the Mac).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:30:40 -04:00
archipelago
e59ea1da69 fix(mesh): never show mojibake device names — strict decode with readable fallback
Name fields sit at firmware-version-dependent offsets; when an offset lands
in binary data, from_utf8_lossy handed the UI replacement-character soup in
the mesh modals and settings panel. decode_mesh_name(): strict UTF-8 to the
first NUL, no control chars, else a readable fallback (short pubkey /
node-<id>).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:30:09 -04:00
archipelago
8a450bb8f8 feat(audio): HDMI audio works out of the box — pipewire in ISO + router daemon + ELD boot-race heal
Root cause of silent HDMI on kiosk TVs (framework-pt, LG TV):
1) fresh ISOs installed NO audio stack even though the kiosk launcher
   expects PipeWire-Pulse — add pipewire/pipewire-pulse/pipewire-alsa/
   wireplumber/alsa-utils to the rootfs and put the archipelago user in
   'audio' (linger user manager gets no logind seat ACLs on /dev/snd);
2) the kiosk's boot-time Xorg modeset races the i915→HDA audio-component
   bind, the ELD notify is lost, and every HDMI profile stays
   'available: no' forever. Verified live: one off/on modeset re-delivers
   the ELD and WirePlumber immediately switches to HDMI.

New archipelago-audio-router daemon (same splice-from-configs pattern as
the kiosk, ISO + include_str! self-heal): routes the card to the best
available HDMI profile, keeps the default sink on it, migrates live
streams on hot-plug, unmutes (HDMI forced 100% — the TV owns volume), and
re-modesets a connected external output once when no ELD reports a
monitor. bootstrap::ensure_audio_stack() installs packages + router on
already-deployed kiosk nodes via OTA (apt through systemd-run — the
service sandbox keeps /usr and dpkg read-only). main.rs also spawns the
pine satellite keeper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:28:47 -04:00
archipelago
852ffc5b18 feat(pine): wyoming satellite keeper — re-point speaker entries when DHCP strands them
HA stores a voice satellite as a pinned LAN IP and never re-resolves; when
the LAN renumbers (framework-pt: entry at 192.168.1.241, LAN now
192.168.63.0/24) the speaker silently drops forever. A periodic keeper
probes IP-pinned wyoming entries, sweeps the node's local /24s for the
satellite's port when one dies, rewrites core.config_entries (HA stopped
around the edit so its shutdown flush can't clobber it) and starts HA again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 15:28:07 -04:00
archipelago
db6003aef6 fix(lightning): actionable expired-invoice error + payment failures reach the user
- payments.rs: an expired invoice can never succeed on retry, so the
  error now says so and tells the payer the way out (ask the recipient
  for a fresh invoice) instead of echoing LND's raw reason.
- middleware.rs: the error sanitizer masked every Lightning payment
  failure as 'Operation failed. Check server logs' — but LND's reasons
  ('invoice expired', 'unable to find a path') are written for the
  payer and are all actionable. 'Payment failed', 'Invalid payment
  request' and 'Missing payment_request' now pass through, with a
  regression test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 04:51:57 -04:00
archipelago
fb72e3e159 fix(fedimint): gateway/guardian follow the running bitcoin container via {{BITCOIN_HOST}}
The gateway crash-looped dialing bitcoind at host.archipelago:8332 (the
host gateway IP, where bitcoind does not listen). Root-cause fix, three
parts:

- fedimint-gateway manifest: --bitcoind-url now comes from
  $FM_BITCOIND_URL, filled by a {{BITCOIN_HOST}} derived-env — works on
  Knots, Core, or any future Bitcoin distro.
- fedimint manifest: same derived-env replaces the hardcoded
  FM_BITCOIND_URL=http://bitcoin-knots:8332 environment entry.
- orchestrator: removed the hardcoded FM_BITCOIND_URL=bitcoin-knots
  override that clobbered the derived-env, and bitcoin_host() now
  accepts any running bitcoin*/bitcoin container (excluding -ui and
  archy-* sidecars), preferring the known names in order.

Catalog regenerated + signed (nodes install from the signed catalog, so
the manifest fixes only reach them via this regen).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 22:07:15 -04:00
archipelago
f339358109 fix(content): double-pay is now impossible + purchases auto-file + Paid Files tab
Some checks failed
Demo images / Build & push demo images (push) Failing after 32s
The double-share/double-pay chain (user hit it live, 2026-07-22):
ShareModal's already-shared lookup compared the slash-stripped stored
path against the leading-slash filepath — never matched — so every
re-share minted a NEW catalog entry with a new id, and the buyer's
owned-guard (keyed by id) saw the duplicate as unowned and paid again.
Three independent walls now: (1) ShareModal normalizes both sides so
re-shares reuse the entry; (2) content_server::add_item dedupes by
filename server-side (updates in place, keeps the id so buyers' owned
records stay valid); (3) the buyer REFUSES to pay for content it
already owns — matched by (onion, content_id) OR (onion, filename) —
and serves the cached copy instead, before any ecash is minted.

Purchases also auto-file into Photos/Music/Documents on the node (same
buckets as the Cloud view, collision-safe naming) so bought files show
up where files live on every device, and Cloud gains a Paid Files tab
listing every purchase (name, size, sats paid, date) with in-app view.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 21:10:48 -04:00
archipelago
df9b9905b6 feat(wallet): LND wedge watchdog + Fedi send rail + token QRs + Auto hidden
Some checks failed
Demo images / Build & push demo images (push) Failing after 33s
- LND watchdog: framework-pt's LND sat wedged 14h (RPC up, server never
  ready, channels inactive) with nothing noticing. 15 consecutive bad
  minutes now bounce the container automatically (30min cooldown) —
  silent multi-hour Lightning outages become self-healed blips.
- Send modal: Auto tab hidden; Ecash splits into Cashu and Fedi (new
  wallet.fedimint-send RPC wrapping the existing spend_from_any); both
  rails render the generated token as a scannable QR alongside the
  copyable text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 20:55:37 -04:00
archipelago
05fc49820c fix(rpc): stop masking user-actionable wallet errors
'Insufficient balance: need 80 sats, have 0 sats' reached the UI as
'Operation failed. Check server logs.' — the sanitizer allowlist didn't
know wallet errors. Also allowlists the calm Lightning still-starting
notice, which it would have swallowed the same way.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 20:22:41 -04:00
archipelago
6347d16a2f fix(recovery): quiet the fleet logs — skip running containers, back off failed companion repairs
Some checks failed
Demo images / Build & push demo images (push) Failing after 32s
Fleet log sweep (2026-07-22): (1) recovery paths podman-start'ed
containers that were already running, producing hundreds of benign but
scary conmon 'Failed to create container' + cgroup Permission-denied
pairs per node per day — both paths now check state first; (2) the 30s
companion-reconcile loop retried registry pulls/builds forever against
unreachable registries (~174×/image/day on an offline node) — failing
rounds now back off exponentially to a 1h cap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 19:44:32 -04:00
archipelago
6a3b46b5a9 fix(handshake): peer requests actually arrive — background poll, unified relays, real send errors
Three silent failure modes killed nostr peer requests (analyzed from the
live code 2026-07-22): (1) nothing ever polled the relays except the
manual Federation Poll button — a 5-minute background poll now fetches
inbound requests and nudges the websocket when something lands (the
handler's discoverability gate still applies); (2) handshake send/poll
used only the two hardcoded config relays (one defunct) and ignored the
user-managed relay list — every nostr op now uses the merged set;
(3) publish errors were swallowed (let _ =) so 'ok' was reported even
when no relay accepted the event — now a delivery failure is an error
the UI shows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 19:44:32 -04:00
archipelago
770e3386f4 perf(nodes): Connected Nodes refresh — parallel probes, instant list, 12s health timeout
Reachability probes ran one at a time with a 30s Tor timeout each, so
Refresh sat disabled for N-offline-peers × 30s. Now the list renders and
the button re-enables immediately; probes run concurrently and fill the
dots in as they land.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 19:44:31 -04:00
archipelago
cad68eb941 fix(mesh): setup modal re-fires on EVERY plug — dismissals key on plug time
detected_device_info now carries plugged_at (the /dev node's creation
time; udev recreates it per plug). Dismissals store (path -> plugged_at),
so swapping sticks or a fast unplug/replug between status polls can no
longer be suppressed by an old 'Not Now' (v1 path-only dismissals are
abandoned wholesale).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 19:08:18 -04:00
archipelago
197e351880 fix(companion): pairing QR advertises the LAN address, never a tailnet IP
The QR mirrored the browser origin — an operator browsing over Tailscale
handed the phone a 100.x address it has no route to, so pairing silently
never connected (reported 2026-07-22; the 'random password' alongside it
was the device token working as designed). system.get-hostname now also
returns the default-route LAN IPv4; the QR substitutes it (or the mDNS
name) whenever the origin is localhost or a CGNAT/tailnet 100.64/10 IP.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 19:08:17 -04:00
archipelago
5f01ec31ed fix(mesh): probe retries across the listener's port-hold windows
Linux double-opens ttys, so a probe racing the reconnect loop corrupted
both handshakes into silence (framework-pt, real Reticulum stick). Three
attempts 7s apart land in the listener's backoff gaps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 17:48:04 -04:00
archipelago
581dbd6337 fix(install): mempool no longer blocked by a resyncing ElectrumX + slower podman probes tolerated
Two real install failures from .116 today: (1) the hard "ElectrumX must
be running" gate — but mempool-api reconnects to electrum on its own and
a resync can take days, so it's now a warning; (2) a 10s podman-ps probe
timeout tripped by ElectrumX compaction disk load — now 45s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 17:31:48 -04:00
archipelago
5e7e928650 feat(wallet): real-time tx push — 0-conf shows in seconds, not on poll
Backend streams LND /v1/transactions/subscribe (fires on mempool arrival
and each confirmation) and nudges the /ws/db revision per event; the Home
wallet card subscribes and refetches (debounced 800ms). An incoming
broadcast now appears while the 30s poll is still asleep. Reconnects
forever with capped backoff when LND is down/locked/absent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 17:31:47 -04:00
archipelago
088b3e255a fix(lnd): retry channel opens during LND startup + calm non-red notice
LND's RPC answers before its p2p server finishes loading, so connect/open
during that window failed red with the raw "server is still in the
process of starting". Now: quiet ~30s retry first; if still starting, a
calm amber "still finishing its startup" notice instead of an error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 17:31:47 -04:00
Dorian
72c1bdd57d test: anchors removal test iterates the full default set
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 22:06:32 +01:00
Dorian
4b91765cc7 feat(companion): embedded FIPS mesh replaces WireGuard for remote access
- Android/rust/archy-fips-core: leaf-only fips node as a JNI cdylib (fips
  pinned to the fips-native fork rev with VpnService fd support), built by
  gradle via cargo-ndk (arm64), tested on host
- ArchyVpnService: split-tunnel VpnService routing only fd00::/8 (MTU 1280,
  foreground specialUse); FipsManager handles the one-time VPN consent and
  silent auto-start — no settings surface at all
- pairing QR now fully configures the mesh: fnpub/fip/fhost/fudp/ftcp plus
  fanchors (the node's seed-anchor list, npub@addr/transport) so the phone
  can rendezvous through public anchors when the LAN endpoint is unreachable
- default seed anchors gain the two dual-transport join.fips.network test
  anchors (23.182.128.74:443/tcp, 217.77.8.91:443/tcp); anchor adverts are
  Nostr kind-37195 events
- device token rides the password field end-to-end: backend accepts tokens
  wherever it accepts the password, so scan = instant login (WebSocket auth
  + WebView form injection unchanged); token logins skip TOTP
- ServerEntry.meshIp + IPv6-bracketed URLs; WebView retries the mesh address
  on main-frame errors, auto-login and origin checks honor it
- companion v0.5.0 (versionCode 20), arm64 abiFilter; FipsNative.available
  gates everything so non-arm64 still runs as a plain companion

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 22:06:32 +01:00
Dorian
b88609e0ff feat: instant companion pairing — device tokens, named QR, FIPS pair-info
- auth.createDeviceToken / listDeviceTokens / revokeDeviceToken RPCs; only
  SHA-256 hashes persist in data_dir/device-tokens.json
- auth.login accepts {token} (same rate limiter, skips TOTP like remember-me)
- pairing QR now carries name (server name, fallback "My Archipelago"),
  tok (instant login), and FIPS mesh params from new fips.pair-info RPC
  (npub, fips0 ULA, transport ports)
- companion onboarding drops the WireGuard install/tunnel screens — remote
  access moves to the FIPS mesh embedded in the companion app
- fix: fips daemon UDP bind now 2121, matching the published container port
  and fleet rosters (was upstream's 8668 — inbound UDP was dead on bridged
  installs, mesh silently rode TCP 8443)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 22:04:59 +01:00
archipelago
8b744d377c feat(mesh): radio hot-swap — probe on plug-in, keep-as-is vs apply-our-settings
Some checks failed
Demo images / Build & push demo images (push) Failing after 1m49s
Plugging in any LoRa radio now surfaces the device setup modal EVERY time
(dismissals clear on unplug; works while mesh is enabled; 2-poll debounce
so ordinary reconnect blips don't flash it), showing what's currently on
the stick before anything is written:

- mesh.probe-device RPC: identifies the firmware read-only in the same
  Reticulum->Meshcore->Meshtastic order as auto-detect. Meshtastic yields
  region/modem-preset/channels (want_config is a read); MeshCore yields
  name/node-id + firmware version via the previously-unused
  CMD_DEVICE_QUERY; RNode via the bare KISS DETECT (no daemon spawn).
  Path must be a detected candidate port; the live session's port refuses.
- "Keep As Is": manage_radio=false persists in MeshConfig and the session
  skips every on-connect config write (region, channel, PHY params, advert
  name) — the radio runs exactly as flashed, hot-swappable.
- "Set Up with Archipelago Settings": second screen shows the params that
  will be applied (channel, region, the node's persisted RF params - e.g.
  the validated Portugal preset - with the per-region community plan as
  fallback) before writing anything.
- Stale-type fix: disconnect now resets device_type/firmware_version, so
  a swapped stick no longer shows the previous radio's firmware; probed
  kind pins device_kind so the right probe runs first on connect.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 16:25:19 -04:00
archipelago
2f26cb2bc4 fix(mesh): persist message history + send-seq counters across restarts
Both lived only in RAM: every archipelago restart/reboot wiped the whole
chat history (user-reported), and — worse — reset the per-target outbound
sequence counters, so peers' (sender_pubkey, sender_seq) dedup silently
dropped the first messages sent after a reboot as replays.

mesh-messages.json in the data dir (0600 — DM plaintext), restored in
MeshService::new before the listener spawns; a 5s debounced persister
task snapshots at one choke point instead of hooking every mutation path
(store, delivered/transport/encrypted stamps, edits, deletes, prunes).
Atomic write-then-rename; corrupt/missing file skips restore instead of
blocking mesh startup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 15:51:32 -04:00
archipelago
a9cd164301 fix(pine): announce the first-ever mesh message + halve announce lag
The seeded announce automation blocked state transitions out of None/
unavailable — but None is exactly the sensor's state before the first
received message (and after the in-memory store restarts empty), so a
fresh node's first DM was silently swallowed (framework-pt 2026-07-22).
Only 'unknown' (the HA-restart marker) still blocks; rest sensors don't
restore state, so restart announce-storms remain impossible. Seeder
upgrades an already-seeded automations.yaml by replacing exactly the v1
clause, leaving user edits untouched.

Sensor scan_interval 30->15s: the sensor only carries the latest message
id, so two messages inside one poll window coalesce into one announce —
halving the window halves both the lag and the coalescing odds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 15:51:18 -04:00
archipelago
1931371058 chore: release v1.7.111-alpha
Some checks failed
Demo images / Build & push demo images (push) Failing after 1m45s
2026-07-22 05:40:40 -04:00
archipelago
bb9ebb1138 fix(pine): availability guards on numeric HA voice sensors
Sensors with a unit (sync %, lightning/onchain sats) rendered the string
'None' when bitcoind is early in IBD or LND is down, which HA rejects
with a ValueError on every 30s scan. Mark them unavailable instead; the
intent scripts already speak a fallback for unavailable states.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 05:00:10 -04:00
archipelago
97fbaf8818 fix(pine): node-status mesh_message is {} instead of null when empty
HA's seeded REST sensor reads attributes via json_attributes_path
"$.mesh_message"; a null there makes HA log a "JSON result was not a
dictionary" warning on every 30s scan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 04:54:20 -04:00
archipelago
2116600e24 fix(pine): write HA bookkeeping fields in seeded config entries
HA's config-entries store already carries the current schema minor_version,
so entries we append are never migrated — a missing created_at is an
unguarded KeyError in config_entries.async_initialize that crash-loops HA
at boot (hit on framework-pt during install verify). Write created_at/
modified_at/discovery_keys/subentries on both the anthropic and wyoming
entries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 04:54:20 -04:00
archipelago
745e180102 fix(quadlet): escape % for systemd specifiers — bitcoind RPC creds were /bin/bash
The creds-off-argv script used printf "rpcuser=%s" in the manifest's
custom_args; quadlet copies Exec= into the generated service's ExecStart,
where systemd expands %s (user's shell) at load time — bitcoind's rpc.conf
came out as rpcuser=/bin/bash and every RPC consumer got 401s on quadlet
nodes (framework-pt: bitcoin-status, HA block-height sensor, LND chain RPC).

Two-layer fix: quadlet.rs now escapes % -> %% in Exec/Entrypoint/HealthCmd/
Environment emission (with regression test), and the bitcoin manifests write
rpc.conf with plain echo so the catalog also heals nodes still running older
binaries. Release catalog regenerated with the fixed scripts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 03:47:06 -04:00
archipelago
e074a117d2 feat(pine): node-status endpoint + Claude voice brain + mesh announcements + wake-word groundwork
- GET /api/pine/status: public tier (version/uptime/bitcoin/mesh facts) for
  the Pine page; bearer-token tier (Lightning balances, latest mesh message)
  for the seeded Home Assistant sensors. Token minted 0600 at
  secrets/pine-status-token; nginx location ships via the bootstrap
  self-heal + canonical ISO conf. Replaces the per-node socat forwarder and
  bitcoind RPC credentials living in HA's configuration.yaml.
- pine_ha seeder: REST sensors + four ask-Archy intents (block height,
  peers, sync %, Lightning balance) with LLM tool descriptions; Claude
  conversation agent (anthropic entry from the node's claude-api-key) set as
  pipeline default with prefer_local_intents so exact phrases stay local and
  fuzzy ones tool-route through Claude; mesh-message announce automation on
  every Assist satellite; bounded config markers with legacy-block migration.
- pine-openwakeword joins the pine stack (all lifecycle sites) — on-node
  wake-word engine groundwork for the custom "Yo Archy" model.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 02:59:34 -04:00
archipelago
905e9cdb98 chore: Cargo.lock version bump missed in v1.7.110-alpha release commit
Some checks failed
Demo images / Build & push demo images (push) Failing after 2m53s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 01:25:55 -04:00
Dorian
84bc3d3138 feat(pine): seed Home Assistant voice defaults when Pine + HA are installed
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m42s
Out of the box, HA needed manual clicks for each Wyoming engine, a
hand-built Assist pipeline, and knew nothing about the node. Now installing
the Pine stack (or HA, whichever comes second) seeds:

- wyoming config entries for pine-whisper (:10300) and pine-piper (:10200)
- an Assist pipeline wired to stt.faster_whisper + tts.piper, and a repair
  for the legacy 'homeassistant' conversation-agent id that modern HA
  rejects (intent-not-supported on every wake word otherwise)
- 'ask Archy' voice intents (custom_sentences + intent_script), starting
  with block height via a mempool REST sensor when that app is present

Seeding is best-effort, idempotent, and never overwrites an existing store
it can't parse; HA restarts only when something was written.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 21:39:04 +00:00
archipelago
aaec25e53b chore: release v1.7.110-alpha
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m50s
2026-07-21 14:50:29 -04:00
archipelago
97358d2314 feat(kiosk): display-size presets in Settings (Auto/Large/Balanced/Native)
system.kiosk-display.get/set RPCs write /etc/archipelago/kiosk-display.conf
(sourced by the kiosk launcher) and try-restart the kiosk so the choice
applies immediately. The Settings section only appears on nodes that have
the kiosk unit installed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:27:04 -04:00
archipelago
1b78da1d7a feat(lnd): accept amount_sats on lnd.payinvoice for zero-amount invoices
Zero-amount BOLT11 invoices need the payer to supply the amount; forward
it to LND REST as the amt field. The wallet scan modal unlocks its amount
field for such invoices and passes the user's entry through.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 13:15:35 -04:00
archipelago
8d4f6cb75e docs(release): third v1.7.109 bullet (Pine voice assistant) + version-bump prep
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m44s
The release gate requires ≥3 curated changelog bullets; the Pine app is
user-facing and deserved one anyway. Carries the version bumps and manifest
from the aborted cut so the tree is clean for the re-run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 07:34:26 -04:00
archipelago
ea80815c98 style: cargo fmt over the pine container list
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 06:55:19 -04:00
archipelago
999a100531 feat(mesh): RF-params settings UI + expose lora_radio_params in mesh.status
Some checks failed
Demo images / Build & push demo images (push) Has been cancelled
Device panel gets MeshCore RF fields (frequency MHz, bandwidth kHz, SF, CR)
wired to mesh.configure's validated lora_radio_params; shown only for
MeshCore radios, all-empty leaves the radio's flashed settings untouched.
Human units in the UI, firmware field units on the wire. Per operator
direction the panel's static stat rows duplicating editable fields (name,
region, channel) are dropped — the editable control is the display. Stale
'adjust via a MeshCore client' hints updated: the node programs the radio now.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 06:53:10 -04:00
archipelago
4c3cd4b6ad feat(mesh): operator-configurable Meshcore LoRa PHY params (freq/bw/sf/cr)
Archy set no radio params on Meshcore devices — SF/CR/BW/freq lived only in
device firmware, so a radio on the wrong preset could not be fixed from the
node (it hears RF energy but demodulates nothing; the fleet hit exactly this
on an RNode: docs/RETICULUM-TRANSPORT-PROGRESS.md item 4).

- protocol.rs: build_set_radio_params — wire format verified against the
  MeshCore companion firmware handler (CMD_SET_RADIO_PARAMS=11:
  [11][freq:u32 LE MHz*1000][bw:u32 LE kHz*1000][sf][cr]); byte-layout test
  pinned to the Portugal preset 869.618 MHz / 62.5 kHz / SF8 / CR8.
- serial.rs: MeshcoreDevice::set_radio_params (device reboots on OK).
- MeshConfig.lora_radio_params: Option — None (default) leaves the radio
  untouched, so only explicitly-configured deployments are affected.
- session.rs: provision on connect, gated on a persisted last-applied marker
  (SELF_INFO offsets shift across firmware versions) + the same attempt cap
  as region/channel so a refusing radio never reboot-loops.
- mesh.configure RPC: lora_radio_params arm with firmware-range validation.

mesh tests: 114/114 pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 06:10:45 -04:00