57 Commits

Author SHA1 Message Date
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
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
archipelago
a57ae388ec fix(mesh): restore Meshtastic inbound stream after radio reboot
archy went deaf to inbound LoRa packets after every config write.
A config write (region/channel/owner) reboots the radio, which resets
the firmware PhoneAPI to STATE_SEND_NOTHING; it won't stream received
packets again until the client re-sends want_config. archy ignored
FromRadio.rebooted (field 8) so never resubscribed — which is why old
messages only arrived after a full restart (restart = fresh want_config).

- meshtastic.rs: handle FROM_RADIO_REBOOTED -> set pending_reinit;
  try_recv_frame re-sends want_config to resubscribe the packet stream.
  Add send_keepalive (bare heartbeat) and pin modem_preset=LONG_FAST in
  set_lora_region so all radios share frequency.
- listener/session.rs: MeshRadioDevice::send_keepalive; 10s sync_timer
  sends a keepalive each tick (insurance vs 15-min idle serial close).
- mod.rs send_message: device-aware send — Meshtastic archy peers get a
  plain TEXT_MESSAGE_APP DM (firmware PKC E2E); Meshcore archy peers keep
  the typed envelope (no meshcore regression).

Verified: .198->.228 directed DM arrives as RECEIVED enc=True
peer="Arch Optiplex"; all 3 nodes (.116/.198/.228) + 3ccc hear each
other. Binary 737b16c3 deployed+active on all three.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 12:44:31 -04:00
archipelago
fbfeeeb0f5 fix(mesh): native E2E DM for archy↔archy text + software radio-reboot
- send_message now sends archy↔archy plain text as a native TEXT_MESSAGE_APP
  DM (firmware PKC-encrypts E2E), not wrapped in the binary typed envelope
  that silently broke archy↔archy LoRa delivery. Archy peers' Sent rows are
  marked encrypted so the E2E pill shows; rich typed msgs still use the
  typed-wire path.
- Add a software radio-reboot to recover a wedged/RX-deaf radio without
  physical access (and for the Device-tab settings panel): driver reboot()
  via AdminMessage reboot_seconds=97 (verified vs meshtastic/protobufs),
  MeshCommand::RebootRadio, MeshService::reboot_radio, RPC mesh.reboot-radio.
- Handoff doc: docs/SESSION-1.8.0-OTA-PROGRESS.md "RESUME HERE" — RF link is
  the proven blocker (radios not hearing each other); modem_preset mismatch
  is the prime suspect; on-device Meshtastic-app check + fix plan documented.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 10:39:34 -04:00
archipelago
b4531bb4fc fix(mesh): enforce LoRa-only off-grid labels 2026-06-30 06:22:45 -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
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
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
archipelago
f92e442bfc fix(mesh): collapse cross-transport twin contacts into one conversation (#12)
A node reachable both over LoRa and federation has two MeshPeer rows (radio
twin: low contact_id + firmware key; federation twin: high contact_id +
archipelago key), and messages key by peer_contact_id split across the two ids
— so opening one twin shows an empty thread (the .120->.89 symptom).

- backend: new group_peer_twins() helper groups peers by arch_pubkey_hex (set on
  BOTH twins by bind_federation_twins), keeps the radio id as the mesh-first
  send target, and unions messages across all twin ids. Wired into
  conversations.list / conversations.messages / mesh.contacts-list. +3 unit tests.
- frontend: the live chat list merges client-side (mergedPeers) and matched twins
  by the "Archy-z6Mk..." advert prefix, which the Meshtastic device rename broke
  (radio now advertises the server name). Merge by arch_pubkey_hex instead, which
  the backend reliably sets on both twins. Expose arch_pubkey_hex on MeshPeer.
- fix unrelated stale test: EcashTransaction test missing the new `kind` field.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 08:01:14 -04:00
archipelago
a0b80dd27d fix(mesh): authenticate !ai over LoRa via federation-twin binding + signed Text
A !ai (or any typed message) from a trusted, federated node was denied when
it arrived over the radio. The radio half of a node that is also a federation
peer carried no archipelago identity (identity adverts are no longer broadcast
on the public channel), so the trusted_only gate and signature verification
had no key to check the asker against — and the same node showed up as two
contacts (a radio twin + a federation twin).

- bind_federation_twins(): correlate a radio contact with its federation twin
  by exact, case-insensitive advert_name and copy the federation peer's
  arch_pubkey_hex/did/x25519 onto the radio record. Called from
  upsert_federation_peer and refresh_contacts. Ambiguous names (held by >1
  federation peer) are skipped. This is only a CANDIDATE key — security is
  unchanged: the inbound envelope signature must still verify against it.
- send_message now signs the typed Text envelope (new_signed) so a radio !ai
  authenticates against the bound key. A meshcore node merely named like a
  trusted node cannot forge the signature, so it is still denied.

Receiver-side verification (handle_typed_envelope_direct) and federation-trust
matching (is_sender_allowed) already existed; this supplies the missing key
binding and signature. Also resolves the radio/federation duplicate-contact
display for same-named nodes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:57:50 -04:00
archipelago
75e470bfa4 fix(mesh): mesh-preferred message routing with FIPS/Tor fallback
Messages to a federated peer that is out of LoRa range (e.g. on another
continent) were dropped into the radio with no fallback, or hung on a dead
FIPS path before reaching Tor — so they never arrived.

- Route a radio contact over the federation transport (FIPS->Tor) when it is
  the same node as a federated peer (known archipelago identity -> onion) AND
  it is not currently reachable over the radio. Reachable radio peers stay on
  the mesh (preferred); oversized/file envelopes still always take federation.
- Resolve the onion via the archipelago identity key (arch_pubkey_hex), not
  the firmware routing key, so a radio contact maps to its nodes.json onion.
- Add .fips_timeout(8s) to the federation message POST so an unreachable FIPS
  overlay fast-fails to Tor (~3-5s) instead of burning the 120s budget.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:09:14 -04:00
archipelago
1bce694ebb feat(ui): mobile mesh tabs, AIUI-style audio player, cloud grid + map fixes
UI (this session):
- Global audio player now scales the whole interface into the space above it
  on desktop (sidebar + main) and docks directly above the tab bar on mobile;
  it stays visible while navigating.
- Mesh mobile redesign: floating Chat / BTC / Dead Man / AI / Map tab strip
  with a single fixed, internally-scrolling pane (page no longer scrolls);
  tabs hide while a conversation is open; floating back button; collapsible
  Device panel (starts collapsed); keyboard-aware conversation sizing via
  VisualViewport so the chat sits just above the keyboard.
- Cloud file grid: uniform 4/3 card heights (folders + images match).
- Swipe left/right switches tabs on the Apps and Web5 screens.
- Map tool fills its pane (no bottom gap); fix skewed Share Location toggle
  on mobile (global min-height rule was deforming the switch).
- Trim redundant helper copy from the mesh AI tab.

Also bundles pre-existing in-progress work that was already in the tree:
mesh listener/session + wallet + container + bitcoin-status backend changes,
docker UI updates, and assorted other UI tweaks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 09:52:26 -04:00
archipelago
f0fdc23cc9 feat(mesh): native-unicast DMs, contact import/remove, reachability, contact search
- DMs now use native meshcore unicast (CMD_SEND_TXT_MSG) instead of @DM2 channel
  broadcasts: private (E2E-encrypted to the recipient pubkey by firmware), off the
  public channel, and decodable by stock clients. Plain text (split, not MC-chunked)
  to non-archipelago contacts; typed envelopes to archy peers.
- !ai replies now DM the asker privately (RadioDm) instead of broadcasting on ch0.
- Auto contact-import: a heard advert (PUSH_CONTACT_ADVERT/0x80, 32-byte pubkey) is
  added via CMD_ADD_UPDATE_CONTACT (0x09) so contacts appear without a flood advert.
- clear-all now DELETES firmware contacts via CMD_REMOVE_CONTACT (0x0F) instead of
  blocklisting; blocking filter removed entirely. Wiped contacts return when reachable.
- Contact reachability: MeshPeer carries last_advert + reachable (path-based); UI shows
  a reachability dot.
- Peers list: contact search box (filter by name/DID/npub/pubkey) with a clear button.
- send_message routes stock contacts as plain native text (fixes garbled envelopes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 08:08:52 -04:00
archipelago
3a21243be7 fix(mesh,ui,fedimint): mesh-AI chat trigger + transport-aware reply, stop ARCHY:2 public-channel spam, AI allowlist + model dropdown, Fedimint client manifest, settings reorder, chat scroll
- mesh: stop broadcasting ARCHY:2 identity on the public channel (startup + every advert tick); receive path still parses inbound. No more public-channel spam.
- mesh assistant: trigger on !ai/!ask typed in 1:1 chat (was only the dead AssistQuery path + bare channel text); route the reply transport-aware via MeshService::send_message (Tor for federation peers, LoRa for radio) through a new AssistChatReply event consumed at the server layer — fixes replies never reaching federation askers.
- mesh assistant: per-contact !ai allowlist (allowed_contacts) bypassing trusted_only; config + RPC + is_sender_allowed.
- fedimint-clientd manifest: network_policy open -> bridge (invalid value made the loader skip the whole manifest, so fmcd never ran and federations never joined/listed).
- ui: AI panel — Claude model dropdown (Haiku/Sonnet/Opus presets) + allowlist contact picker.
- ui: Settings — App Updates + App Registry moved under Account.
- ui: mesh chat — overscroll-behavior: contain so chat scroll no longer bleeds to the contacts panel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 03:33:37 -04:00
archipelago
5b2a11b8c7 Merge meshroller-50: mesh-AI assistant (#50) into release train 2026-06-17 19:22:11 -04:00
archipelago
bd567cd165 feat(wallet,content,seed): Fedimint dual-ecash, paid content streaming, seed ceremony
- Fedimint ecash alongside Cashu: fedimint-clientd (fmcd) HTTP bridge,
  fedimint_client, fedimint RPC, wallet wiring
- Paid peer content: content invoices + streaming content server + content RPCs
- Seed-phrase ceremony/reveal RPCs and CLI ceremony tool
- LND wallet, mesh status/messaging, app-stack (netbird HTTPS), and
  decoupled-update wiring; Fedimint Client core app in catalog

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:21:07 -04:00
archipelago
7a76d32e4b feat(mesh): mesh-AI assistant scheduler + config panel (#50)
Adds the assistant scheduler, MeshAssistantPanel UI, and the remaining
config-RPC / live-toggle / Ollama-detect wiring on top of Phase 1.x.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 19:19:32 -04:00
archipelago
0947ecee11 feat(mesh): assistant config RPCs + live toggle + Ollama detect (#50)
Phase 2 backend. AssistantConfig is now live-updatable (RwLock) so the UI
toggle applies without a listener restart. New RPCs:
- mesh.assistant-status  -> {enabled, model, trusted_only, default_model,
  ollama_detected, models[]} (probes local Ollama :11434/api/tags)
- mesh.assistant-configure -> set enabled/model/trusted_only live + persist

MeshService::assistant_config / configure_assistant. Compiles clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 18:29:36 -04:00
archipelago
d8d014bfd9 feat(mesh): mesh-AI assistant — Phase 1.1-1.4 (issue #50)
Rust-native lift of Meshroller's LLM bridge. Adds typed AssistQuery/
AssistResponse mesh messages, a trust-gated inbound handler that answers
with the node's local Ollama model, and airtime discipline (reply cap,
chunking, one in-flight query per asker). Works over both meshcore and
Meshtastic radios via the existing MeshRadioDevice abstraction.

- message_types: AssistQuery=24 / AssistResponse=25 + payloads
- listener/assist.rs: run_assist (gate -> Ollama -> chunked reply)
- listener/dispatch.rs: AssistQuery/AssistResponse arms
- MeshConfig: assistant_enabled / assistant_model / assistant_trusted_only
- MeshState: AssistantConfig + data_dir + in-flight guard

Compiles clean (cargo check). Off by default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:41:15 -04:00
archipelago
ab56054aeb fix(federation): remove-node also purges the mesh contact/thread (#2)
federation.remove-node only edited nodes.json, so a removed/renamed node
(e.g. a stale "Arch HP") lingered in the mesh chat list with its old
thread. Capture the node's pubkey before removal, then purge its
synthetic mesh peer, shared secret, messages, presence, and persisted
contact entry via the new mesh::purge_federation_peer. Combined with the
#42 name refresh, stale federation contacts can now be fully cleaned from
a node.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 06:12:56 -04:00
archipelago
1ea3f8d65c fix(mesh): message federation contacts without a radio (fixes 'Missing contact_id')
Messaging a federation-only peer (e.g. 'Arch Dev') failed with 'Missing
contact_id'. The UI gave federation-only rows a *negative* placeholder
contact_id derived from a DID hash, but the backend parses contact_id as u64,
so a negative value deserialized to None. The negative id also never matched
the positive federation-synthetic id that federation-routed messages are stored
under, so those threads looked empty.

- Frontend: derive the SAME positive federation-synthetic id the backend uses
  (federationContactId mirrors federation_peer_contact_id) so mesh.send accepts
  it and messages thread correctly.
- Backend: send_typed_wire now resolves a federation-synthetic contact_id from
  nodes.json when it isn't in the live mesh peer table (radio-less node),
  instead of bailing 'Unknown federation peer'.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 03:24:34 -04:00
archipelago
edd03e542d feat(storage): encrypt chat history + mesh contacts at rest, atomic writes, persist contacts (#12)
User: chat history (messages + mesh/Tor contacts) must persist and be
secure/encrypted per best practice. Root cause of the .198 loss was the B17
mount race writing empty stores over real data (B17 already fixes the trigger);
this hardens storage so it can never silently lose or expose data:

- storage_crypto: shared at-rest envelope mirroring credentials::store — key =
  SHA-256(domain ‖ node identity key) (seed-derived, per-store domain
  separation), ChaCha20-Poly1305 AEAD with a random 96-bit nonce, tamper-evident.
  Transparent migration of legacy plaintext files. Unit-tested (round-trip,
  wrong-key/tamper rejection, plaintext detection).
- messages.json: encrypted at rest + ATOMIC write (temp+rename) so a crash/
  reboot mid-write cannot corrupt history; decrypt-with-migration on load; a
  failed decrypt never overwrites the on-disk data.
- mesh contacts (alias/notes/pinned/blocked): were ONLY in memory and lost on
  every restart — now persisted to mesh-contacts.json (encrypted, atomic),
  loaded on MeshState startup, saved after contacts-save/contacts-block.

Explicit clear (mesh.clear-all) still wipes everything, as intended.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:54:37 -04:00
archipelago
ed4931064b fix(federation,cloud): dedup trusted nodes + chat contacts by onion; guard cloud my-folders (B1,B2,B4)
B1/B2: the same physical node can linger in the federation list under two
dids (e.g. after a did/key change). An onion is a node's unique stable
identity, so two entries with the same onion are one node. This showed the
node twice in the trusted-node list (B1) and as two mesh chat contacts —
one by name+logo, one by raw did (B2).
- storage::load_nodes now collapses same-onion entries (keep first, merge
  fips_npub/name/last_state) so every consumer (list + chat seed + sync)
  sees one entry per node.
- federation::sync merge_transitive_peers also matches by onion (not just
  did) so new transitive hints don't re-add a known node under a new did.
- mesh::seed_federation_peers_into_mesh skips already-seeded onions (belt
  and suspenders).
- Unit tests for dedup_nodes_by_onion (collapse + onion-suffix handling).

B4: filebrowser-client.listDirectory only checked res.ok before res.json(),
so when File Browser is absent (nginx serves the SPA index.html, 200) or
down (502) the JSON parse threw the opaque "Unexpected token '<'". Now it
checks the content-type and throws a friendly "File Browser is not
available" the Cloud view already renders as an empty state.

Verified: dedup unit tests 2/2; live .198 (15 entries→13 distinct onions)
restarted healthy on new binary; B4 guard present in built bundle + deployed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 12:29:12 -04:00
archipelago
cfb304a001 feat(mesh): add meshtastic serial radio support 2026-05-17 18:07:40 -04:00
Dorian
8b88c45262 feat(settings): per-service FIPS/Tor transport preference
Adds a user-configurable toggle for how each peer-to-peer service
reaches federated peers. Three options per service:

- Auto (default) — FIPS preferred, Tor fallback (current behavior).
- FIPS only — fail rather than fall through to Tor.
- Tor only — explicit opt-in to onion anonymity for that service.

Services covered (matching the UI rows):
- Federation — state sync, invites, peer notifications
- Peers — address/DID rotation broadcasts
- Peer Files — content catalog download/browse/preview
- Messaging — archipelago channel + mesh bridge
- Mesh File Sharing — content_ref blob fetches

Implementation:
- settings::transport — persisted struct + process-wide OnceLock handle
  (so deep call sites don't need data_dir threaded through signatures).
  On-disk file: <data_dir>/settings/transport_preferences.json; missing
  or corrupt → defaults (Auto everywhere).
- settings::transport::init() called from main.rs after config load.
- fips::dial::PeerRequest gains a .service(kind) builder; send_* checks
  the preference before choosing a transport. FIPS-only fails loudly
  when FIPS is unavailable (so users who pick it know when something
  falls back).
- Every FIPS-first migration site tags its PeerRequest with the
  matching PeerService so the toggle actually applies.
- transport.preferences + transport.set-preference RPCs added; wired
  into the dispatcher.
- neode-ui/src/views/settings/TransportPrefsCard.vue — standalone card
  with a 5-row Auto/FIPS/Tor tri-state. Not wired into Settings.vue —
  the user places components themselves (see feedback_ui_entry_points).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 01:44:41 -04:00
Dorian
dbd19006f2 feat(messaging,dwn,mesh): route peer messaging + DWN sync + blob fetch via FIPS first
Migrates the remaining Tor-direct peer call sites to PeerRequest so
FIPS is the default when the peer is federated and running the daemon:

- node_message::send_to_peer / check_peer_reachable: gain a
  fips_npub parameter. Error messages updated to reference both
  transports.
- Callers (api/rpc/network.rs, api/rpc/peers.rs, server health
  loop): look up fips_npub from federation storage by onion and
  pass it.
- mesh::send_typed_wire_via_federation: the spawned background POST
  for the /archipelago/mesh-typed endpoint now uses PeerRequest with
  federation-resolved fips_npub. Signature domain unchanged.
- api/rpc/mesh/typed_messages.rs fetch_blob_from_peer: blob URL
  rebuilt as (base_url, path_with_query) so PeerRequest can append
  the query string after swapping the host. Cap/exp/peer
  parameters are still signed over the content ref itself, so
  transport choice is invisible to the signature.
- network/dwn_sync.rs sync_with_peers: per-peer fips_npub lookup
  before sync_single_peer; health/pull/push each dial through
  PeerRequest, so any DWN peer known to federation gets FIPS.

Left Tor-only on purpose:
- api/rpc/identity/handlers.rs handle_identity_resolve_peer_onion —
  resolving TO a DID, no anchor yet.
- content.browse / preview calls to non-federated peers fall
  through to Tor naturally inside PeerRequest (no fips_npub → skip
  FIPS branch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 01:36:04 -04:00
Dorian
b614c5c694 chore(ci): rustfmt + clippy clean-up to unblock the Rust CI job
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:

- Applies rustfmt across the tree (the bulk of the diff — untouched
  since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
    container/bitcoin_simulator.rs wildcard-in-or-pattern
    container/manifest.rs from_str rename to parse (reserved name)
    container/podman_client.rs .get(0) -> .first()
    container/runtime.rs manual += collapse
    archipelago/src/constants.rs doc-comment → module-doc
    api/rpc/package/install.rs stray /// comment above a non-item
    container/docker_packages.rs redundant field init
    streaming/advertisement.rs missing Metric import in tests
    tests/orchestration_tests.rs `vec!` in non-Vec contexts
    mesh/listener/dispatch.rs unused store_plain_message import
    api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
  stylistic lints (too_many_arguments, type_complexity, doc indent,
  enum variant prefix, wildcard-in-or, assertions-on-constants,
  drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
  of places with no correctness payoff and have been churning every
  toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
  are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
  rollback compatibility, vpn::get_nostr_vpn_status is surface-area
  for a not-yet-landed RPC.

cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:46 -04:00
Dorian
3a52c766ac fix(mesh): single-flight send + spinner + async federation POST
Root cause of the "every bubble shows twice" complaint after the prior
dedup fix: the frontend was firing mesh.send twice per user action. A
held/repeating Enter key on the input fires a keydown per repeat, and
handleSendMessage didn't guard on mesh.sending, so both calls queued
through the store's sendQueue and both executed against the same
contact_id (backend logs show two mesh.send RPCs 13ms apart, same text).
That's why sender and receiver both saw doubles — the envelope actually
was transmitted twice.

Mesh.vue: handleSendMessage now early-returns if mesh.sending or
sendingArch is already set. Send button replaces the `...` placeholder
with a proper spinning ring (`.mesh-send-spinner`) so the held-Enter case
stops looking like the app is ignoring the user.

mesh/mod.rs: send_typed_wire_via_federation no longer blocks on the Tor
POST. Sent MeshMessage is recorded synchronously (UI bubble appears
instantly); the HTTP goes in tokio::spawn. Tor circuit setup was the
1–5s lag the user was seeing on every send to a federation peer. Delivery
failure still shows as `delivered: false` via the read-receipt path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:57:11 -04:00
Dorian
7e4fed7967 fix(mesh): dedup across transports + persistent radio-contact blocklist
Two mesh fixes bundled so the deploy lands them together:

Doubled messages (radio + federation): dedup at store_message now runs
a third cross-transport check keyed on (sender_seq, plaintext, 120s).
The existing (sender_pubkey, sender_seq) match missed the common case
where the same envelope arrives via LoRa radio (sender_pubkey looked
up from the firmware key) and again via Tor federation (sender_pubkey
= archipelago ed25519), because the two lookups disagree. The new
cross-transport match closes that gap without loosening legacy paths.

Stale contacts after clear-all: meshcore's on-device contact table is
persistent and reads back into peers on the next refresh_contacts, so
the previous "nuclear" clear wiped app state for a few seconds before
the old rows reappeared. New persistent `radio_contact_blocklist`
(mesh-ignored-radio-contacts.json) captures the pubkeys present at
clear-time; `refresh_contacts` filters them on read and the filter
survives restart. Federation-synthetic peers are excluded from the
snapshot so the list rebuilds normally on the next gossip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 14:02:34 -04:00
Dorian
1736f6f99e feat(mesh): server name in adverts + clear-all button + CI fix
- Mesh adverts now use the node's configured server name (e.g. "ThinkPad",
  "Arch Dev") instead of DID key fragments ("Archy-z6MkmkSB")
- Added mesh.clear-all RPC to reset peers, messages, contacts, and history
- Added "Clear All" button in Mesh UI peers panel
- Both glibc and musl builds verified

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 11:53:06 -04:00
Dorian
0c02d06a66 feat: deploy-to-target supports .253 + mesh/federation/VPN updates
- Add deploy_secondary() function for deploying to multiple LAN nodes
- --both now deploys to .198 and .253 (previously .198 only)
- Fleet deploy updated for 3 LAN nodes
- Mesh DM fixes: protocol frame format, DM-via-channel routing
- Federation pending requests, discover modal
- VPN status UI improvements
- Image versions and container specs updates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 11:07:08 -04:00
Dorian
6760d11a57 feat(mesh): Telegram primitives pass + attachment transport router
Bundles the Phase 2b/3/4/5 work that accumulated across prior sessions
and the new attachment chunking router from this session. Everything
ships in one shot so the full mesh surface stays coherent on-wire.

Telegram primitives (variants 13–18, 20–22):
- Reply / Reaction / ReadReceipt / Forward / Edit / Delete
- Presence heartbeat + last-seen tracking
- ChannelInvite + ContactCard payload types
- MessageKey (sender_pubkey, sender_seq) as cross-transport identity
- Action menu, reply banner, edit banner, tombstones, (edited) marker
- Debounced auto-read-receipts on scroll + message arrival

Activated prototypes (Phase 4):
- PsbtHash send RPC
- Contacts CRUD (in-memory alias/notes/pinned/blocked)
- Outbox 📤 badge, rotate-prekeys button
- Chunked send fallback (MCIIXXTT framing) as auto-failover inside
  send_typed_wire when a typed wire exceeds the LoRa per-frame budget

Unified inbox (Phase 1):
- conversations.list + conversations.messages RPCs (UI collapse deferred)

Attachment transport router (new this session):
- ContentInline variant 23 + ContentInlinePayload carrying file bytes
  directly in the envelope for small files with no Tor path
- mesh.send-content-inline RPC — mirrors to local BlobStore, rides
  send_typed_wire which auto-chunks over MCIIXXTT framing (~2.3 KB cap)
- mesh.transport-advice RPC as single source of truth for tier
  decisions: auto-mesh / choose / tor-only / impossible
- Receive arm writes inline bytes to local BlobStore so the existing
  content_ref card renderer handles both transports uniformly
- MeshState.blob_store field + order-independent propagation from
  RpcHandler::set_blob_store / set_mesh_service
- Frontend handleAttachFile calls advice first, branches into silent
  auto-send, transport-chooser modal, Tor-only path, or red error
- Transport modal with 📡 mesh / 🧅 Tor options + ETA + disabled
  state when peer has no Tor reachability

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 20:40:19 -04:00
Dorian
d514e0e5e4 fix(mesh): DM-via-channel tunnel + disable presence spam
Meshcore direct unicast silently drops between our two Archy nodes
(firmware reports flood sends with resp_code=6 but nothing arrives).
Wrap DMs as channel-1 broadcasts with a [0xD1][dest_prefix(6)][inner]
header; receivers filter by prefix and dispatch the inner payload
through the existing typed/base64/chunk ladder. Shrink chunk body to
125B so the wrapper still fits the 160B LoRa budget. Auto-heal
routing: CMD_RESET_PATH (0x0D) any type-1 contact with path_len=0 on
refresh so floods take over. send_text now returns the firmware's
flood/direct mode flag for diagnostics.

Disable the 120s presence heartbeat broadcaster — its CBOR payload
was being re-echoed as plaintext by the shared repeater, spamming
every visible node with garbled "Archy-…: av�…fstatusfonline…"
messages on channel 0. mesh.broadcast-presence RPC stays registered
but no longer transmits. Re-enable only once presence moves off the
shared broadcast path.

Also: MeshState.cmd_tx behind RwLock so stop()→start() cycles don't
fail with "command channel already consumed"; MeshService.send_cmd
helper; drop_message_by_id for control envelopes that shouldn't
appear as Sent bubbles; self_advert_name reflected into MeshStatus
after set; path_len/flags parsed out of RESP_CONTACT.

Frontend: unified inbox merges mesh peers with federation nodes by
DID/pubkey/name; hide presence/read_receipt/edit/channel_invite/
contact_card from chat stream; publicChannel index → 1 to match the
new DM-via-channel routing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 10:24:27 -04:00
Dorian
8ef7af985d feat(mesh): Phase 1/2b/4/5 primitives — ReadReceipt/Forward/Edit/Delete/Presence/Contacts/ChannelInvite + chunked send + unified inbox RPCs
Adds every remaining wire variant and RPC needed to finish the Telegram-quality
mesh plan in a single pass:

* Variants 15 ReadReceipt, 16 Forward, 17 Edit, 18 Delete, 20 Presence,
  21 ChannelInvite; plus MeshMessageType::ContactCard(22) cleanup (was
  enum-only, now wired through from_u8/label/from_label).
* MessageType::from_label() as the inverse of label() — used by the Forward
  path to re-encode a stored typed body back through its original variant.
* RPCs: mesh.send-psbt (variant 3 was previously enum-only),
  mesh.send-read-receipt, mesh.forward-message, mesh.edit-message,
  mesh.delete-message, mesh.broadcast-presence, mesh.presence-list,
  mesh.contacts-list, mesh.contacts-save, mesh.contacts-block,
  mesh.send-channel-invite, conversations.list, conversations.messages.
* MeshState gains presence (pubkey → status+timestamps) and contacts
  (pubkey → ContactEntry{alias,notes,pinned,blocked}) in-memory stores.
* MeshService gains find_message_by_id (Forward lookup), apply_local_edit /
  apply_local_delete (optimistic local echo), and send_chunked_payload — an
  MC-framed base64 splitter that fires as a fallback inside send_typed_wire
  when wire > MAX_MESSAGE_LEN and no federation path is known. Reuses the
  existing receive-side reassembly in listener/decode.rs.
* Receive dispatch arms for PsbtHash, Presence, ChannelInvite, ReadReceipt
  (rolls forward `delivered` flag on own-Sent ≤ seq for that peer), Forward,
  Edit, Delete. Edit/Delete guard against cross-peer tampering by matching
  the target MessageKey pubkey against the sender's advertised pubkey_hex.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 18:24:05 -04:00
Dorian
002032b7da fix(mesh): resolve ContentRef peer via DID + name-match fallback
Mesh peer pubkeys (LoRa advert ed25519) differ from federation node
pubkeys (archipelago identity), so matching on pubkey always missed
and attachments >160B had no transport. Match on master DID instead;
also accept an explicit peer_onion override from the frontend, which
resolves the peer by display name against federation.list-nodes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 14:13:36 -04:00
Dorian
bc3729d99f fix(mesh): route ContentRef over federation when >160B
mesh.send-content was failing with "Message too large for LoRa: 624
bytes (max 160)" because a single ContentRef envelope (cid + onion +
cap_token + thumb) dwarfs a LoRa frame. Add a federation Tor fallback:

- New POST /archipelago/mesh-typed endpoint accepts
  {from_pubkey, typed_envelope_b64, signature}, verifies ed25519 over
  the raw wire bytes, and injects the decoded envelope into MeshState
  via a new MeshService::inject_typed_from_federation helper. This
  shares the same dispatch match as LoRa receives via a new pub(crate)
  handle_typed_envelope_direct extracted from handle_typed_message.
- MeshService::send_typed_wire_via_federation POSTs the signed wire to
  a peer's onion over TOR_SOCKS_PROXY and records a local Sent record.
- handle_mesh_send_content looks up the peer's onion in federation
  storage and routes via federation when available, falling back to
  LoRa only when no federation presence is known (still fails on
  oversized — chunking is Phase 4).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 13:37:48 -04:00
Dorian
a360f90647 feat(mesh): MessageKey + Reply/Reaction variants and sender seq (Phase 2a)
Per-target outbound seq counter on MeshState allocates a monotonic seq
before each typed envelope is encoded; send_typed_wire +
send_channel_typed_wire record it (alongside our own pubkey_hex) on the
Sent MeshMessage so the local store carries the same MessageKey the
receiver will see. TypedEnvelope.with_seq lets the RPC layer stamp the
seq AFTER signing (signature covers t/v/ts only).

New MessageKey struct pairs sender_pubkey+sender_seq as the stable
cross-transport identity. Adds variants 13 Reply and 14 Reaction with
ReplyPayload {target, text} and ReactionPayload {target, emoji}, plus
mesh.send-reply / mesh.send-reaction RPCs and receive-side dispatch
arms that store the payload json for the UI to index.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 13:19:30 -04:00
Dorian
de1b25cc78 feat(mesh): MessageKey foundation and debug-dump RPC
Adds sender_pubkey + sender_seq fields to MeshMessage so received
messages carry a stable cross-transport identity: (sender_pubkey,
sender_seq) pair. This is the foundation for the upcoming reply,
reaction, edit, and read-receipt variants — they need to target a
message by an ID that is meaningful on every node, not just locally.

Receive-side population lives in dispatch.rs::store_typed_message,
which now looks up the peer's pubkey_hex and copies envelope.seq from
the decoded TypedEnvelope. Sent-side population will land when we
plumb a per-node monotonic seq counter through the RPC layer.

Also adds mesh.debug-dump: a full in-memory state snapshot returning
peers, messages, status, shared-secret peer ids, encrypt_relay flag,
and stego mode — intended for smoke tests and bug investigation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 08:18:01 -04:00
Dorian
3ed9243c50 feat(mesh): rich typed Sent records and echo dedup
Adds message_type + typed_payload (JSON) to MeshMessage so the UI can
render invoice/alert/coordinate/tx/lightning messages as structured
cards in both directions instead of showing raw wire bytes on the
Sent side. RPC handlers now route through send_typed_wire /
send_channel_typed_wire which transmit the binary envelope directly
(no utf8_lossy corruption) and record a rich Sent MeshMessage.

Also: store_message deduplicates echo-back doubles (20-msg lookback,
30s window), from_name is plumbed through the federation Incoming
path, and peer_dest_prefix / send_raw_payload are factored out of
send_message.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 08:01:10 -04:00
Dorian
18284e1592 chore: remove CLAUDE.md and stale config files 2026-04-12 12:11:00 -04:00
Dorian
e4e0ef4f11 bug fixing and deploy and build diagnostics 2026-03-22 03:30:21 +00:00
Dorian
94f2de4a64 refactor: centralize constants, eliminate unwraps, remove dead code, resolve TODOs
- R13+R16: Replace .expect() with .context()? in main.rs and identity.rs
- R17+R18+R19: Fix unwrap() calls in helpers and js-engine
- R20+R21: Remove #[allow(dead_code)] annotations and delete truly dead code
- R22-R26: Create constants.rs module, replace 21 hardcoded values across 12 files
- R28+R29: LND/DWN timeouts already present — verified
- R30-R33: Remove TODO comments, implement marketplace payment check

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 01:54:35 +00:00
Dorian
c3d4a7063b fix: systemd resource limits, Tor rotation transition, unwrap elimination, RPC timeouts
- I2: Add MemoryMax=4G, LimitNOFILE=65535, TasksMax=2048 to systemd service
- I3: Tor rotation keeps old service for 1h transition before cleanup
- R14: Replace .parse().unwrap() with .unwrap_or(localhost) in rate limiter
- R15: Replace 7 unwrap/expect in mesh protocol with proper error propagation
- R27: Add 10s timeouts to mesh Bitcoin RPC calls

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 01:46:40 +00:00
Dorian
6f5188ef7f fix: rpcauth credentials, reboot survival, system Tor for all containers
- Bitcoin RPC: switch to rpcauth (salted hash in bitcoin.conf, no plaintext
  in config or CLI). Password stable across reboots/restarts/deploys.
- Remove daily-reboot-test.sh cron on both servers
- Enable podman-restart.service for container auto-start after reboot
- System Tor: SocksPort 0.0.0.0:9050 with SocksPolicy for container access
- LND: tor.socks=host.containers.internal:9050 (system Tor, not container)
- Bitcoin: -proxy=host.containers.internal:9050 for Tor outbound
- bitcoin_rpc.rs: reads from secrets file, cached, stable credentials
- package.rs: dynamic rpc_user/rpc_pass, rpcauth hash generation
- network.rs: fix missing send_to_peer args (mesh encryption update)
- first-boot-containers.sh: rpcauth generation, system Tor config
- deploy-to-target.sh: rpcauth credentials, LND config migration
- Mesh: encrypted channel message support (ChaCha20-Poly1305 updates)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 11:56:20 +00:00
Dorian
4080d0a92b fix: Phase 8 — mesh hardening: atomic writes, unwrap elimination, GPS opt-out
- Ratchet state: atomic write via tmp + rename to prevent corruption on crash
- Block header decode: replaced .unwrap() with proper error handling on
  untrusted network data (was a crash vector from malicious peers)
- Shutdown channel: replaced .unwrap() with .ok_or_else() error propagation
- Dead man's switch GPS: default changed to opt-out (auto_include_gps=false)
- Alert signature verification: already covered by Phase 4 envelope checks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 01:04:19 +00:00
Dorian
909ad5f019 feat: Phase 1 — per-installation credential generation, eliminate hardcoded passwords
Generate unique random passwords at first boot for Bitcoin RPC, all database
services (mempool, btcpay, immich, penpot, mysql-root), and Fedimint gateway.
Credentials stored in /var/lib/archipelago/secrets/ with 600 permissions.

Scripts: first-boot-containers.sh, deploy-to-target.sh, deploy-bitcoin-knots.sh,
container-doctor.sh all read from secrets files instead of hardcoded values.

Rust backend: new bitcoin_rpc module reads password from secrets file, env var,
or dev fallback. All .basic_auth() calls and container config strings now use
the shared credential reader instead of hardcoded "archipelago123".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:39:52 +00:00
Dorian
d37ec1dea5 feat: v1.2.0-alpha — E2E encrypted mesh relay, steganography, relay status polling
Phase 5 mesh networking:
- E2E encrypted TX relay (X25519 + ChaCha20-Poly1305) — non-Archy nodes
  relay encrypted blobs transparently via Meshcore native routing
- Steganographic encoding modes (WeatherStation, SensorNetwork) — traffic
  looks like sensor data on the wire, 0xAA marker, configurable per-node
- Pre-flight Bitcoin Core health check on relay node — specific error codes
  (bitcoin_unreachable, bitcoin_syncing, tx_rejected) instead of generic fails
- mesh.relay-status RPC endpoint — frontend polls for relay result every 3s
- On-Chain / Lightning tabs in Off-Grid Bitcoin panel
- Archy Peers vs Mesh Broadcast relay mode selector
- Mesh view fills viewport (no page scroll), internal panel scrolling
- Version bump to 1.2.0-alpha

Also includes: deploy hardening, container fixes, IndeedHub updates,
boot screen, dashboard improvements, MASTER_PLAN task tracking

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:56:37 +00:00