675 Commits

Author SHA1 Message Date
archipelago
7d7ba5734a fix(ui): wire up OpenWrtGateway's back button
BackButton is presentational-only (emits click, parent wires navigation)
per its own doc comment, but OpenWrtGateway.vue rendered it with no
@click handler at all -- clicking it did nothing. Added useRouter +
goBack() (-> the 'server' route, matching the page's location under
views/server/), same pattern as PeerFiles.vue/CloudFolder.vue.

Router-detection (openwrt.scan) spot-checked live: RPC plumbing works
end-to-end and returns a valid response, but no physical OpenWrt device
was on hand to confirm a true-positive detection. Also noted:
detect::scan_subnet does blocking TCP/SSH calls inside an async fn with
no .await points -- not proven to cause a real issue yet, but worth
hardening (spawn_blocking or async I/O) before a large subnet scan is
exercised for real.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-02 03:24:37 -04:00
archipelago
9f52e81471 fix(ui): remove vestigial ref, fix stale MeshMap test mock
Web5ConnectedNodes.vue declared nodesContainerRef but never consumed it
(the controller-nav system scans [data-controller-container] globally,
no other view uses a per-component ref for it) — broke the vue-tsc build.
MeshMap.test.ts's mocked mesh store predated federatedPositions (added
earlier this session for the Mesh Map federated-node feature) and crashed
on mount. Found live merging PR#67 (reticulum) + UI/UX work +
archy-openwrt into main for a combined fleet deploy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 18:20:04 -04:00
archipelago
27093e682f Merge remote-tracking branch 'gitea-ai/archy-openwrt' 2026-07-01 18:09:41 -04:00
archipelago
8256fde1a6 fix(ui): mesh/web5/apps layout, modal, and search UX fixes
- Mesh: fix 920-1280px bottom margin (phantom mobile-nav reservation
  leaking into the desktop-sidebar range), let the mesh view scale to
  full width on wide screens instead of capping at 1600px, and make the
  Device panel collapsible on desktop (previously mobile-only)
- Search/controller-nav: a global gamepad/keyboard-nav feature was
  auto-clicking "the next button in the DOM" on Enter in any text input,
  which cleared the mesh peer search and popped the sideload modal from
  the App Store/My Apps search boxes. Opt out via data-controller-no-submit
  on all filter inputs; bump the mesh clear button's touch target
- Modals: several (sideload, credential, Lightning channel open, identity
  create) used ad-hoc blue buttons and non-fullscreen backdrops that only
  covered the main content area, not the sidebar. Teleport them to body,
  unify backdrop/button theming to the dark+orange convention, fix the
  sideload modal's square bottom corners on desktop, and standardize
  close buttons to the ghost-icon style
- Web5: remove the redundant/dead "Messages" tab from Connected Nodes
  (its deep-link was unreachable dead code); fix the "view message" toast
  to actually open the Archipelago channel instead of silently failing to
  match a LoRa peer; make identity rows responsive via a container query
  (viewport-based breakpoints don't work in the page's 2-column grid) and
  right-justify their action icons; collapse DID/DHT/Wallet/Nostr/Connected
  Nodes by default on mobile
- Apps/App Store: match the search bar and sideload button's height,
  padding, and background to the mode-switcher tabs beside them
- Mesh chat: keep the compose input focused after sending

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 18:04:31 -04:00
archipelago
4c3aa8cc8e fix(icons): remove remaining electrs icon references, use electrumx.png
GoalDetail.vue, EasyHome.vue, and the backend's docker_packages.rs
metadata still pointed electrs-family app ids at the old electrs
icon (svg). Point them at electrumx.png like every other reference,
and delete the now-unused electrs.svg asset.
2026-07-01 14:48:14 -04:00
archipelago
ed95d54ffe chore(assets): replace lnd icon svg with png
lnd.svg no longer exists; every reference now points at lnd.png.
2026-07-01 14:41:15 -04:00
archipelago
7d2ac1f842 chore(assets): update searxng app icon 2026-07-01 14:36:12 -04:00
archipelago
177b8a4338 feat(mesh): show federated Archipelago nodes on the Mesh Map
Peers that opt in via a new "Share Location" toggle in Settings
(server.set-location RPC) get plotted on other trusted peers' Mesh Map
with a distinct Archy-logo marker, separate from raw LoRa radio peers.
Location is persisted locally, carried in NodeStateSnapshot, and
propagated through federation sync/delta like other node state.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 12:04:31 -04:00
archipelago
bebf3bae10 fix(mesh): Reticulum garbage-text + reconnect churn + signal bars + node naming/HTTPS
- reticulum.rs: send_text_msg was lossy-UTF8-mangling binary CBOR control
  envelopes (ReadReceipt etc.) before sending as LXMF text; base64-encode
  with a marker instead, decoded losslessly on receive.
- typed_messages.rs: mesh.send-read-receipt fired automatically on every
  chat view with no is_archy_peer gate, so viewing a message from a stock
  (non-archy) LXMF peer auto-sent it an undecodable control envelope,
  surfacing as garbage text right after whatever it just sent. Now a no-op
  for non-archy peers.
- mesh/listener/mod.rs: RX_STALL_TIMEOUT was 300s and forced a full
  auto-detect reconnect on any otherwise-healthy but quiet mesh link
  (visible as "Connecting..." flapping); this also wiped Reticulum's
  in-memory peer-address table every cycle, breaking messaging with peers
  who hadn't re-announced in the window. Bumped to 1800s.
- reticulum.rs: persist the peer prefix/dest-hash/display-name table to
  disk so a restart doesn't force every peer back to "Anonymous Peer"
  until they re-announce.
- decode.rs/frames.rs: Meshcore was discarding the SNR its wire format
  carries; wire it onto the peer record. Mesh.vue's signalBars() now falls
  back to SNR-based bars when RSSI is unavailable (always true for
  Meshcore); Reticulum has neither and correctly stays at 0/"no data".
- system/handlers.rs, dispatcher.rs: new system.get-hostname RPC + cert
  regeneration (with a proper SAN) whenever server.set-name changes the
  hostname, so HTTPS doesn't add a mismatch warning on top of the
  self-signed one after a rename.
- AccountInfoSection.vue: surface the mDNS hostname + http/https links in
  Settings (HTTPS needed for mic/camera secure-context features) — never
  forced, both keep working.
- build-auto-installer-iso.sh: ship avahi-daemon so .local names actually
  resolve on the LAN, and give the self-signed cert a real SAN instead of
  a bare CN, both at image-build and install-time-fallback.
- Mesh.vue/MediaLightbox.vue/mesh-styles.css: mic/attach-stack no longer
  closes on a plain hover-past; mesh images open in the shared lightbox
  and have a real download button; lightbox close button moves to
  bottom-center on mobile instead of under the status bar; mesh device
  panel gets the same height/padding as its sibling tabs.

Verified: 108/108 mesh unit tests, deployed + confirmed healthy on
.116/.198/.228 (matching binary hash across all three), live Reticulum
messaging confirmed working end-to-end post-deploy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 10:42:20 -04:00
2a6e624189 toggle for wifi and switch-router on openwrt page 2026-07-01 14:06:02 +00:00
archipelago
99cd82ab0a fix(ui): catch useAudioPlayer's play() rejection instead of leaving it unhandled
play() on the underlying <audio> element rejects independently of its
'error' event (e.g. NotSupportedError when a peer-content request 404s and
there's no decodable source) — the 'error' listener already sets a friendly
message, but the unawaited play() promise still surfaced as a raw unhandled
rejection in the console. Follow-up from the .116->.228 peer-content
investigation (2026-07-01).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 09:53:50 -04:00
e497f8fed1 feat(home): surface TollGate status on the Network tile
Add a TollGate row (Enabled/Disabled/Not installed) to the Home
dashboard's Network tile, polling the existing openwrt.get-status RPC
on the same cadence as the other network rows. Only rendered once an
OpenWrt router is actually configured, so nodes without one aren't
cluttered with an always-"Not configured" row.

Also fixes the underlying reason this could never have worked: nothing
in the OpenWrt Gateway flow ever persisted the router's host/credentials
server-side — the "connect" form only kept them in local component
state, so any no-args openwrt.get-status call (this new tile, and even
the Gateway page's own reload) always failed with "No router
configured" despite a fully working, provisioned router. Now
handle_openwrt_get_status saves the connection to router_config.json
whenever a host is explicitly passed in and the connection succeeds.
2026-07-01 13:25:43 +00:00
d6c1feca97 fix(openwrt): fix TollGate provisioning pipeline, add reconfigure UI
Several compounding bugs were blocking end-to-end TollGate provisioning
on OpenWrt 25.x (apk-native) routers:

- install_ipk's non-ar fallback assumed a flat tarball, but some .ipks are
  a gzip tar of the three classic ipk members one level deep; it was
  dumping debian-binary/data.tar.gz/control.tar.gz straight into / instead
  of unpacking the real payload.
- Manually-extracted packages never ran their pending /etc/uci-defaults/*
  scripts (that only happens through opkg/apk's own postinst bookkeeping),
  so nothing ever created /etc/config/tollgate.
- uci_apply() never ensured the target config file existed first — `uci
  set` fails outright on a config namespace nothing has created yet, which
  is true for a package-defined one like "tollgate" (unlike wireless/
  network/dhcp, which ship by default).
- The installed-check and restart_services looked for a binary/init script
  named after the opkg package ("tollgate-module-basic-go"/"tollgate"),
  but the real on-disk names are tollgate-wrt — so status always reported
  "not installed" and service restarts silently no-op'd.
- provision_ssid used `uci add`, creating a new wifi-iface section (and
  therefore a new duplicate broadcast SSID) on every provision call instead
  of updating one in place.

Also adds a TollGateConfig.enabled field so the enable/disable state is
actually applied to the running service and the SSID's own broadcast
(stop + disable at boot, or start + enable), not just written to UCI.

On the frontend, the OpenWrt Gateway page's TollGate panel was read-only
once installed — add an edit form (price, step size, min steps, mint URL,
enabled toggle) that reuses the same idempotent provision-tollgate call.
2026-07-01 11:59:43 +00:00
archipelago
d414ae3daa fix(orchestrator,ui): stop crash-looping orphan stack members; dedupe Electrum launch overlay
- crash_recovery.rs: stack boot/runtime recovery (immich/indeedhub/netbird) now
  requires the stack's core dependency container to exist before touching any
  sibling, instead of firing on any leftover container. Fixes an infinite
  120s-interval crash loop where orphan debris from a partial/failed install
  (indeedhub-api with no indeedhub-postgres ever created) was repeatedly
  force-restarted against a dependency that doesn't exist, which also blocked
  a real reinstall via container name conflicts.
- AppSessionFrame.vue: the generic app-loading overlay and the ElectrumX
  sync-in-progress overlay could render simultaneously (same z-index) during
  launch. The sync screen is strictly more informative, so it now takes
  precedence instead of the two stacking on top of each other.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 07:02:01 -04:00
archipelago
46dae75a0f feat(mesh): device onboarding modal (backlog #6)
Guided prompt that pops up when a mesh radio is detected but not yet
connected -- wraps the existing Connect action (mesh.configure with
device_path) rather than building a new setup engine. Dismissible per
device path (won't re-prompt for the same undismissed-but-ignored device on
every poll tick). Not the whole-app identity/seed onboarding system
(useOnboarding.ts) -- confirmed unrelated, this is mesh-specific only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-06-30 23:24:12 -04:00
archipelago
494f272815 feat(mesh): Device settings tab (backlog #8)
New MeshDevicePanel.vue, added as a 4th/5th tab entry to activeTab/toolsTab/
mobileTab following the exact existing pattern (chat/bitcoin/deadman/
assistant/map). Shows firmware version, node ID, advert name, LoRa region,
channel, and device type -- firmware_version/self_node_id were already
server-side but never rendered; region is new (composed into MeshStatus from
MeshConfig.lora_region at read time, not part of the live session state).
Reboot button wired to the already-working mesh.reboot-radio RPC.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-06-30 23:03:09 -04:00
archipelago
4a309a3ee4 feat(mesh): RSSI/SNR dBm tooltip on the existing signal-bars indicator
The bars UI (signalBars/.mesh-signal-bars) was already built and wired to
mp.primary_rssi -- it just needed real backend data, which the previous
commit provides. Adds primary_snr alongside primary_rssi in MergedPeer and a
hover tooltip showing exact dBm/SNR values.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-06-30 22:54:51 -04:00
archipelago
02b6b52a8c feat(mesh): Meshtastic RSSI/SNR + peer-location map wiring (backlog #14/#15, part 1)
Backend: parse_mesh_packet now decodes MeshPacket.rx_snr (field 8, float) and
rx_rssi (field 12, int32), and a new POSITION_APP branch decodes Position.
latitude_i/longitude_i (fields 1/2, sfixed32) -- all field numbers confirmed
against the canonical meshtastic/protobufs mesh.proto, not guessed. Threaded
through ParsedContact -> refresh_contacts -> MeshPeer (mirroring how
pkc_capable was wired for #17), so mesh.peers now surfaces real rssi/snr/lat/
lon instead of always-null. Fixed a real bug found along the way:
update_node_info's unconditional contact replace would have silently wiped
any already-tracked signal/position data on the next NodeInfo packet -- now
preserves it.

Frontend: mesh.ts's updateNodePositionsFromPeers() feeds real position data
into the SAME nodePositions map MeshMap.vue already renders from (parallel to
the existing Coordinate/Alert-message path) -- MeshMap.vue itself needed zero
changes, it was already built for this.

105/105 mesh tests pass (4 new: rx_snr/rx_rssi decode, position decode +
incomplete-field handling, full packet_to_inbound_frame integration).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-06-30 22:52:42 -04:00
archipelago
0eb5c258f5 fix(mesh): Meshtastic 3ccc pkc_capable pill + Sideband image interop + critical CBOR wire-bloat fix
Merges in the meshtastic agent's now-finished work alongside this session's
continuation: stock-peer (3ccc) PKI-capability is now stamped through
get_contacts -> refresh_contacts -> MeshPeer.pkc_capable, so a directed DM to/from
a PKC-capable stock Meshtastic peer correctly shows the E2E pill on the Sent row,
not just received messages. Confirmed live: .198 sees "Meshtastic 3ccc" with
pkc_capable=true.

Also fixes two real interop/correctness bugs found while live-testing the
Reticulum <-> Sideband link:
  - Receive: the daemon only ever read LXMF's plain-text content, silently
    dropping native FIELD_IMAGE/FIELD_FILE_ATTACHMENTS fields — a stock
    Sideband/NomadNet photo vanished into a blank-space message. Now decoded
    into the same ContentInline typed envelope our own attachments use.
  - Send: images to a non-archy (stock) peer now use native LXMF FIELD_IMAGE
    instead of our own opaque CBOR wire format, which Sideband can't decode.
  - Root cause of a garbled MC-chunk-fragment bug: TypedEnvelope.v/.sig (the
    OUTER wrapper every message type uses) serialized raw bytes as a CBOR
    array-of-integers instead of a native byte string, bloating every
    message on the wire ~2-3.5x — enough to push even a tiny ReadReceipt
    over the 140-byte single-frame chunking threshold. Root-caused by
    reading ciborium's deserializer source directly (deserialize_bytes only
    works within its internal scratch buffer; deserialize_byte_buf streams
    unbounded).

Frontend: consolidated the attach/record buttons into a single animated "+"
menu (was overflowing the compose row).

857/857 tests pass. Verified live across all 5 deploy-roster nodes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-06-30 22:07:45 -04:00
archipelago
f54c853128 feat(mesh): Reticulum LoRa hardware gates pass + RNS Resource transfer + image/voice attachments
Phase 0 gates #2/#3 (two-node LXMF-over-LoRa, external Sideband interop) passed
on real hardware (.116's flashed Heltec V3 RNode <-> a phone-flashed RNode running
Sideband) — RNS announce, encrypted DM round-trip, and contact binding all verified
live. Fixed two bugs found in the process: the Reticulum send path wasn't stamping
outbound messages as E2E despite LXMF being unconditionally encrypted, and the
per-message transport pill collapsed Meshcore/Meshtastic into one generic "lora"
color instead of distinguishing the three radio transports.

Built on top of that link: a Columba-style image/file send experience —
compression-quality presets with a real transfer-time estimate (mesh.transport-advice,
now device-throughput-aware), receive-side thumbnail previews + auto-render for
already-local attachments, and async voice messages, all reusing the existing
ContentRef/ContentInline attachment pipeline. The headline addition is genuine RNS
Resource transfer support (daemon-side RNS.Link + RNS.Resource, Rust-side
send_resource/resource_recv plumbing, a new "resource-mesh" transport-advice tier)
so compressed photos up to 2MB now actually transfer over LoRa for Reticulum peers
instead of always falling back to Tor past the small inline-chunk cap.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-06-30 19:57:01 -04:00
f3cbeb2834 first commit of openwrt-tollgate integration 2026-06-30 20:30:26 +00:00
archipelago
12e7990b10 fix(mesh): route Meshtastic public-channel text to the channel thread, not DMs
Inbound Meshtastic text addressed to BROADCAST_NUM (the default public
LongFast channel, or any channel slot) was filed into a per-sender 1:1 DM
thread, so public-channel messages polluted individual people's DM chats
and appeared as if sent directly to the user.

packet_to_inbound_frame now detects `to == BROADCAST_NUM` and emits a new
synthetic RESP_MESHTASTIC_CHANNEL_TEXT frame
([channel_idx][sender_prefix(6)][text]) that the listener files under the
channel thread (contact_id = u32::MAX - idx) while still attributing the
message to its real sender. Directed text (to == our node) still routes to
the DM thread — a regression test locks that split in.

send_channel_text now sets MeshPacket.channel (field 3) so archy actually
transmits on channel 0 (public) instead of ignoring the slot. Mesh.vue keeps
the synthetic "Meshtastic !xxxx" sender id when that is the best identity
available for a stock public-channel device.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:33:30 -04:00
edbad30501 fix(openwrt): TollGate apk-native install for OpenWrt 25.x
- WISP wizard: step-by-step flow for WiFi, DHCP, masquerade config
- WAN status: expose lan_ip, dhcp_start/limit, masq, sta_state, wifi_log
- wifi_scan: detect CCMP as WPA2 (psk2) so association succeeds
- opkg: PkgManager enum — detect apk-native mode when opkg not in repos
- tollgate: apk-native install path using manual ipk extraction
- arch detection: read DISTRIB_ARCH from /etc/openwrt_release; normalise
  bare mipsel/mips from uname -m to mipsel_24kc/mips_24kc
- install_ipk: install binutils via apk when ar not in BusyBox
- install_ipk: wget --no-check-certificate for routers without CA bundle
- install_ipk: ar fallback to tar -xzf for non-standard ipk formats
- install_ipk: 5MB overlay space check with clear user-facing error
- middleware: allow "Not enough flash/space" errors through sanitizer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 17:12:57 +00:00
a862877189 feat(openwrt): add WAN diagnostics to get-status and UI
get_wan_status now returns: radio0_disabled, sta_iface (from iw dev),
sta_state (operstate), assoc_ssid (actually associated SSID vs
configured), and recent wifi_log lines from logread. The WAN panel
shows a diagnostic grid when configured but not connected so the user
can see exactly what's wrong without digging into server logs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 17:12:57 +00:00
9a782fb551 feat(openwrt): WAN/WISP setup from the UI with WiFi network scan
New RPC methods:
- openwrt.scan-wifi: triggers iwinfo scan on the router radio,
  returns networks sorted by signal strength
- openwrt.configure-wan: creates UCI wireless.wwan (sta mode) +
  network.wwan (DHCP) + adds wwan to firewall WAN zone, then
  calls `wifi reload`

get-status now includes a `wan` object with configured/ssid/ip/
internet fields so the UI can show current uplink state.

Frontend WAN panel: scan → pick SSID (signal bars) → enter password
→ apply. Shows "Configure WAN first" hint above TollGate install
button when internet is not available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 17:12:57 +00:00
f69fac627a style(openwrt): adopt glass-card design system for contrast
Replace bg-white/5 card containers with glass-card (rgba(0,0,0,0.65) +
backdrop-blur), match input styling to Login.vue, and use glass-button
variants for actions. Fixes low contrast against the background image.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 17:12:57 +00:00
f054766a58 feat(openwrt): add TollGate provision button and direct-download fallback
- OpenWrtGateway.vue: add "Install TollGate" button when not installed;
  tracks connected credentials for reuse in the provision call
- install.rs: fall back to wget download from GitHub releases when the
  package is not in any opkg feed (mips_24kc and other arches supported)
- openwrt.rs: provision-tollgate now falls back to saved router_config
  for credentials, matching the behaviour of get-status

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 17:12:57 +00:00
d71f36370d feat(openwrt): add OpenWrt gateway status view and get-status RPC
Backend: new `openwrt.get-status` RPC endpoint SSHes into the saved (or
provided) OpenWrt router and returns system info, TollGate config, and WiFi
AP interfaces via UCI.

Frontend: new OpenWrtGateway.vue view at /dashboard/server/openwrt shows
system hostname, OpenWrt version, uptime, TollGate install/enable state with
pricing and mint URL, and all AP-mode WiFi interfaces. Linked from the Local
Network section of the Server view.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 17:12:57 +00:00
archipelago
f392670e2a feat(mesh): show sender identity on received channel messages
Received messages snapshot peer_name at receive time, so a Meshtastic
text that arrived before its sender's NodeInfo was stuck showing the
synthetic "Meshtastic !xxxx" id forever, and channel/group bubbles
showed no sender at all. Add a per-bubble sender label for received
messages in multi-sender views (mesh + Archipelago channels), resolved
LIVE from the peer table so it always shows the current archy identity
(e.g. "Arch Optiplex") the moment NodeInfo is learned. Falls back to
"Unknown sender" rather than echoing a Channel/synthetic placeholder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 13:04:41 -04:00
archipelago
b4531bb4fc fix(mesh): enforce LoRa-only off-grid labels 2026-06-30 06:22:45 -04:00
archipelago
2ac0711f8e fix(ui): refresh mesh transport labels after send 2026-06-30 06:05:41 -04:00
archipelago
a91814641e fix(mesh): set Meshtastic hop limit and show LoRa pill 2026-06-30 05:59:53 -04:00
archipelago
c2c4b5af7d merge: demo build updates
# Conflicts:
#	neode-ui/src/stores/appLauncher.ts
#	neode-ui/src/views/AppSession.vue
2026-06-30 05:22:42 -04:00
archipelago
daf750688d merge: mesh multiversion and transport pills
# Conflicts:
#	core/archipelago/src/mesh/listener/decode.rs
#	core/archipelago/src/mesh/meshtastic.rs
2026-06-30 05:19:58 -04:00
archipelago
df9d3a55be integration: preserve deployed 1.8.0 OTA work 2026-06-30 05:08:17 -04:00
archipelago
067002b04b Merge branch 'bitcoin-version-bulletproof' into mesh-multiversion-integration 2026-06-29 06:45:50 -04:00
archipelago
095a76cd20 fix(bitcoin): bulletproof multi-version switching (Knots & Core)
Three stacked bugs made "switch version" silently fail / crash-loop, and
the data-access mismatch corrupted a node's index during recovery attempts.

Backend renderer:
- sync_quadlet_unit ignored the per-app pinned version and re-rendered the
  quadlet with the manifest's :latest every reconcile tick, reverting any
  switch. Factor the install-time catalog/pin resolution into a shared
  resolve_catalog_image() and call it in BOTH install_fresh and
  sync_quadlet_unit.
- The renderer folded manifest `entrypoint: ["sh","-lc"]` into Exec=, which
  only worked when the image entrypoint was a passthrough shell wrapper. The
  versioned images use ENTRYPOINT ["bitcoind"], so Exec=sh -lc ... became
  `bitcoind sh -lc ...` and crash-looped. Emit a real Entrypoint= override;
  exec_changed now also compares Entrypoint=.

Images:
- Build all bitcoin images (Core + Knots, every version) as container-root
  (USER removed) like the legacy :latest image. Chain data is owned by the
  data_uid (container uid 102); root reads it via CAP_DAC_OVERRIDE (granted in
  the manifest). A non-root USER (the previous uid 1000) can't read existing
  chain data → "Error initializing block database". Still fully rootless:
  container-root maps to the unprivileged host service user.

Catalog:
- bitcoin-knots versions[]: 29.3.knots20260508/20260507/20260210 +
  29.2.knots20251110, "latest" tracking newest.
- bitcoin-core versions[]: add 29.2 + a "latest" entry. All images rebuilt
  root and published to the mirror.

Frontend:
- AppSidebar version dropdown: rename the latest option to "Always use the
  latest version" (no v prefix), fix right padding, and guarantee the current
  selection matches a real option (was rendering blank).
- New InstallVersionModal: full-screen version chooser shown from the App
  Store / Discover install button for multi-version apps (Bitcoin Knots/Core),
  app icon + "Install <name>", latest pre-selected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 05:46:04 -04:00
archipelago
11038cdcc9 feat(mesh,ui): per-message transport pill (Mesh/FIPS/Tor) + fix E2E pill
Adds a per-message transport badge to archy↔archy mesh chats and fixes the
long-broken E2E badge — both meshcore and meshtastic, styled like the existing
E2E pill.

Transport pill:
- New `MeshMessage.transport` ("lora"/"fips"/"tor"), surfaced in the UI beside
  the E2E badge (Mesh.vue transportLabel() → Mesh/FIPS/Tor, mesh-styles.css).
- Sent LoRa → "lora"; sent federation → finalized to the real leg ("fips"/"tor")
  once the background send resolves (req.send_json transport), via an id-keyed
  store update.
- Received: a post-dispatch stamp on handle_typed_envelope_direct's output
  (monotonic ids) tags both transports without threading through all 20 typed-
  dispatch sites — radio wrapper stamps "lora", federation injector stamps the
  peer's last_transport ("fips"/"tor", default tor; the inbound HTTP carries no
  FIPS-vs-Tor signal).
- Plain native/channel LoRa frames → "lora"; channel broadcasts stay non-E2E.

E2E pill fix:
- `encrypted` was hardcoded false at every MeshMessage construction site, so the
  UI badge (Mesh.vue `v-if="msg.encrypted"`) never showed. Now: federation
  envelopes are E2E (identity-signed over an encrypted transport); the meshcore
  native-DM receive path already had a real `encrypted` flag (now also tagged
  with transport). meshtastic-PKI radio E2E flag threading is a noted follow-up.

Backend cargo check + frontend vue-tsc build both green. Needs a live radio +
multi-transport pass on .116/.228 to confirm end-to-end (see
project_transport_pill / project_meshtastic_parity).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-29 04:29:25 -04:00
archipelago
6aa74c7386 feat(bitcoin): multi-version support for Core & Knots (install/switch/pin/auto-update)
Lets a node runner choose which Bitcoin Core / Knots version to install
(latest pre-selected), then switch, pin, or opt into auto-update from the
app's interface — all manifest/catalog-driven, rootless, signed-registry,
zero-data-loss. Motivated by upcoming BIP-110 signalling: runners need a
real choice of software version.

Backend:
- version_config.rs: per-app pin + auto-update persistence (atomic, merge-
  preserving), downgrade detection, auto-update enumeration (+ unit tests).
- app_catalog.rs: CatalogVersion / versions[] schema, catalog_versions(),
  catalog_image_for_version() (same-repo guard); a pin suppresses the update
  badge.
- prod_orchestrator.rs: pinned version wins over the catalog default on every
  install/recreate.
- install.rs: install-time `version` param persisted (default = unpinned).
- set_config.rs: package.versions (read) + package.set-config (write) RPCs;
  downgrade is gated behind explicit confirm (warn + confirm + allow).
- update.rs/main.rs: hourly per-app auto-update tick via the orchestrator
  (opt-in, pin-respecting); fix handle_package_update to be non-fatal for
  orchestrator-managed apps lacking a catalog primary image (bitcoin-core).

UI:
- MarketplaceAppDetails.vue: install-time version selector (shown when an app
  offers >=2 versions).
- appDetails/AppSidebar.vue: "Version & Updates" card (switch / pin / auto-
  update toggle / downgrade warning), per app.
- rpc-client.ts + en.json: RPC methods, types, strings.

Phase 0 image pipeline:
- scripts/build-bitcoin-image.sh: download official tarball + SHA256SUMS(.asc),
  verify SHA-256 + pinned-maintainer OpenPGP signature (fail-closed), build a
  minimal rootless image, smoke-test, tag + push.
- apps/bitcoin-core/Dockerfile rewritten (drops stale community base);
  apps/bitcoin-knots/Dockerfile added.
- generate-app-catalog.sh: emit curated versions[]; published + catalog now
  offers Core 25.2/26.2/27.2/28.4/29.3/30.2/31.0 + Knots 29.3.knots20260508.

docs/bitcoin-multi-version-design.md: live progress tracker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 18:46:17 -04:00
archipelago
6734947c3e fix(fmcd): cap CPU + watchdog-restart the iroh relay hot-loop
On NAT'd nodes that can reach the iroh federation neither directly nor
via iroh's public relays, fmcd's embedded iroh networking enters a
relay/hole-punch reconnect hot-loop that pegs its entire CPU allotment
indefinitely (observed ~1 core sustained for 4 days on a Tailscale node,
while LAN nodes that reach the guardian directly stay <3%). fmcd 0.8.0
exposes no iroh/relay knobs, so:

- fmcd-run now samples fmcd's own CPU and restarts it when it stays near
  its allotment for ~15 min (a restart demonstrably clears the stuck iroh
  state; real work is bursty and never flat-pegs a core for minutes).
- Lower cpu_limit 1 -> 0.25 core so a stuck instance can't starve the
  node (steady-state is <3% of a core; joins are brief).

Ships as fmcd:0.8.1 (launcher-only rebuild, same fmcd binary). Bumped the
image pin + cpu_limit in the manifest, image-versions.sh, the embedded
catalog manifest (releases/app-catalog.json), and the UI catalogs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 12:19:27 -04:00
archipelago
f9a6ae3f32 feat(mesh): Meshtastic region + shared-channel auto-provisioning (MeshCore parity)
Fresh Meshtastic radios ship region-UNSET (RF-silent) and on mismatched
channels, so nodes only ever saw themselves. Bring them to MeshCore parity
using the official Meshtastic admin API:

- Auto-provision LoRa region (set_config, AdminMessage field 34) from a new
  mesh-config `lora_region` (e.g. EU_868) when the radio's region differs.
- Auto-provision a shared primary channel (set_channel, field 33) with a
  PSK derived deterministically from channel_name, so every node converges on
  one mesh — the parity equivalent of MeshCore's named "archipelago" channel.
- Read current region/channel from want_config; only write when different
  (no reboot loop); cap attempts so a radio that won't persist can't loop.
- Active NodeInfo advert scaffolding + aggressive serial drain.

Verified on .116+.228: region+channel persist, discovery works (both see each
other as named reachable contacts), bidirectional RF + sending confirmed.
Receiving in the running driver is still under diagnosis (instrumentation added).

Also removes the unwanted `meshtastic` daemon app from the registry (it was
never meant to be a container — native driver provides system-level support):
deletes apps/meshtastic + catalog entries (app-catalog, neode-ui, releases) +
test refs. Meshtastic stays native, like MeshCore.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 04:46:35 -04:00
Dorian
38d2bbf570 chore(android): update companion APK download [skip ci] 2026-06-26 13:08:37 +01:00
Dorian
a90fea80ed feat(android): edit server entries from in-app settings menu (NESMenu); bump to 0.4.12 (vc16)
The 0.4.11 edit affordance only lived on ServerConnectScreen, which a
connected user never sees. Add edit to NESMenu — the settings modal
reached via two-finger hold while connected: a ✎ pencil on each saved
server opens the form pre-populated (Edit Server header + Cancel),
persists via ServerPreferences.updateSavedServer(), and reconnects when
the edited server is the live one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 13:08:18 +01:00
Dorian
389e602097 chore(android): update companion APK download [skip ci] 2026-06-26 12:54:52 +01:00
Dorian
5677f9cca1 feat(android): edit saved server entries; bump companion to 0.4.11 (vc15)
Add an edit affordance to each saved server in ServerConnectScreen: a
pencil button loads the entry into the form (Edit Server mode) with
Save Changes / Cancel actions. Persisted via a new
ServerPreferences.updateSavedServer() that replaces by connection
identity (address/port/scheme) and keeps the active record in sync when
the edited server is the active one.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:54:07 +01:00
Dorian
ac59771560 fix(android): force v1+v2+v3 signing & clean-build guards in companion publish
The published companion APK was v2-only (AGP silently ignores
enableV1Signing for minSdk>=24) and clean builds broke on stray
space-named resource dirs. Harden scripts/publish-companion-apk.sh:
clean build, remove/ýreject space-named res dirs, force v1+v2+v3 via
zipalign+apksigner, and abort unless all three schemes verify. Wire
ship-companion.sh to the shared script. Re-sign the served 0.4.10 APK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 11:53:25 +01:00
Dorian
d1f9e9ce88 chore(android): update companion apk download 2026-06-26 11:32:00 +01:00
archipelago
9f17ba6867 fix(ui): truthful uninstall progress bar (was a solid full-red block)
AppCard's uninstall bar was hardcoded `w-full bg-red-400/60 animate-pulse`
— a solid, full-width, red, fake-pulsing block that never moved and read
as an error, no matter the actual teardown progress (the install bar, by
contrast, renders a real percentage). Derive a truthful percentage from
the backend's existing `uninstall-stage` label — "Stopping containers
(X/N)" → 10–50%, "Cleaning up volumes" → 70%, "Removing app data" → 90%
— and render it exactly like install: neutral fill, real width + percent,
shimmer (not a fake pulse) carrying motion when a stage has no number.
Frontend-only; the backend already broadcasts these stages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 06:04:48 -04:00
archipelago
80f49cac1c fix(ui): backoff remote-relay reconnects + stop cryptpad icon 404
Two console-noise fixes from a live error dump:
- remote-relay.ts reconnected on a FIXED 5s interval with no backoff, so when
  the backend is briefly down it floods the console/network with failed-WS
  attempts for the whole outage. It's a secondary feature (companion input), so
  add exponential backoff 1s->30s (mirrors websocket.ts), reset on open/start.
- cryptpad's catalog/marketplace entries pointed at a non-existent
  /assets/img/app-icons/cryptpad.webp -> a 404 on every marketplace render.
  Point it at the existing default icon (handleImageError swapped to it anyway).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 08:41:04 -04:00
archipelago
2d8ade629b fix(ui): log global errors silently instead of popping a toast + overlay
The global error handler (Vue errorHandler + window error + unhandledrejection)
fired a red 'Something went wrong: <raw msg>' toast AND an auto on-device overlay
on every caught error — deliberately loud for bug-bash, but it surfaces benign,
non-actionable noise (e.g. a transient RPC rejection during a ws reconnect, or
the service worker failing to register over a self-signed cert) right in the
user's face.

Demote the catch-all to SILENT capture: keep console.error + the
window.__archyErrors ring buffer, and expose the screenshot-able overlay
on-demand via window.__archyShowErrors() — but never auto-pop. Components that
need to report a specific, actionable failure still call toast.error() directly.

Also filter known-benign environmental noise (PWA service-worker registration
failing over a self-signed cert — needs a trusted cert, #56) so it doesn't even
occupy a ring-buffer slot and push out real errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 05:55:49 -04:00