1341 Commits

Author SHA1 Message Date
archipelago
63b98599e8 Revert "fix(fedimint): run fmcd with seccomp=unconfined so its DHT can start (#7)"
This reverts commit 409543c41e78025354acbdde5ffc6445895d4508.
2026-06-20 14:37:24 -04:00
archipelago
409543c41e fix(fedimint): run fmcd with seccomp=unconfined so its DHT can start (#7)
fmcd crash-looped "Operation not permitted (os error 1)" on .116 (kernel
6.12.74): the default rootless seccomp profile blocks a syscall its Mainline-DHT
/ iroh transport needs, so the REST API never came up (:8178 → HTTP 000) and
federations couldn't be joined. Verified: with seccomp=unconfined fmcd boots and
answers /v2/* (HTTP 401 instead of dead). fmcd works on other nodes, so this is
kernel/seccomp-specific — but the relaxation is safe for an outbound-networking
daemon and harmless where not needed.

- new `security.seccomp_unconfined` manifest flag (SecurityPolicy);
- libpod backend sets `seccomp_profile_path: "unconfined"` (== --security-opt
  seccomp=unconfined); quadlet backend emits `SeccompProfile=unconfined`;
- enabled in apps/fedimint-clientd/manifest.yml.

NOTE: manifests live on-disk at /opt/archipelago/apps/<id>/manifest.yml, so the
node needs the updated manifest deployed + the fmcd container recreated to apply.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 13:08:13 -04:00
archipelago
d59cf6d299 docs: session 3 — ecash confirm+refund, #5 confirmed, #7 fmcd-on-.116 EPERM
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 12:28:24 -04:00
archipelago
12f54e390d feat(wallet): ecash pay confirmation screen + auto-refund on failed sale (#3)
- PeerFiles: new confirmation step after "pay from ecash" — shows the amount and
  which wallet will be spent (Cashu/Fedimint) with balances, lets the user switch
  backends, and a styled Confirm button. The chosen backend is passed to the
  payment so it spends exactly what was confirmed.
- content.download-peer-paid: accept `method` (cashu|fedimint) to honor the
  confirmed choice; log the backend + outcome; backend-specific rejection errors
  ("not in the same Fedimint federation" / "doesn't accept your Cashu mint").
- AUTO-REFUND: a minted token whose sale fails (peer unreachable, rejected, or
  error) is now reclaimed (fedimint reissue / cashu receive) so the buyer no
  longer loses the spent ecash — fixes the stuck-Fedimint-notes report.
- wallet.ecash-balance already reports cashu_sats/fedimint_sats/total_sats which
  the confirm screen uses to pick/show the covering wallet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 12:16:02 -04:00
archipelago
242baf5deb fix(ui): on-screen error overlay so companion crashes are visible without a console
chrome://inspect isn't always reachable on the Android companion WebView, so the
real error stayed invisible. Add a plain-DOM, screenshot-able overlay (built
without Vue so it survives a crash in Vue itself) that shows the captured error
message + stack and a Copy button for the full window.__archyErrors buffer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 10:23:59 -04:00
archipelago
0ab160b5c3 docs: deploy state — all 6 nodes on 4a8f2198 build (#12/#2/#3/#10)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 10:15:59 -04:00
archipelago
a6957a48f7 fix(netbird): wait for OIDC discovery before reporting install done (#10)
Right after install the dashboard SPA opens and, if it loads before NetBird's
embedded OIDC provider is serving, caches a bad auth state — the user appears
logged-in but can't log out until it self-corrects. Container "running" != OIDC
ready, so gate the install's Done phase on the management server's
/oauth2/.well-known/openid-configuration answering (best-effort, 60s cap, never
fails the install since the stack is already up).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 08:57:37 -04:00
archipelago
2761f0d70f docs: handoff — session 2 progress (#12/#2/#3 code-complete, deploy held)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 08:52:07 -04:00
archipelago
a8c668ee0a fix(ui): stop mobile tab bar covering last row of content (#2)
On Cloud/files (and any scrolling view), the bottom of the list could sit behind
the fixed mobile tab bar. Cause: DashboardMobileNav measured the bar's
offsetHeight and wrote it to --mobile-tab-bar-height, but when the bar was hidden
or not yet laid out the measurement was 0 — and writing "0px" defeats the
", 88px" fallback in the .mobile-scroll-pad clearance calc (an explicit 0 is
still a set value), so the clearance collapsed and the ~88px bar overlapped the
last row.

- never write 0px: only set a real measured height, else remove the var so the
  88px fallback applies.
- re-measure after first paint (rAF) and after the WebView safe-area injection,
  so the clearance reflects the bar's final laid-out height.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 08:50:44 -04:00
archipelago
8f06d88fbf feat(wallet): pay for peer files from BOTH Cashu and Fedimint ecash (#3)
Paying for a peer file minted a Cashu-only token, so a node whose ecash balance
lived in Fedimint couldn't pay even with funds. Now both backends are tried:

- payer (content.download-peer-paid): mint a Cashu token first; on failure fall
  back to spending Fedimint notes. Only error if BOTH backends can't cover it.
- seller (verify_and_receive_payment): accept Fedimint notes as well as Cashu —
  anything not starting with "cashu" is redeemed via reissue_into_any.
- new fedimint_client::spend_from_any() — spend from whichever joined federation
  has the balance, returning the notes + federation id (mirrors reissue_into_any).
- wallet.ecash-balance now also reports fedimint_sats + combined total_sats; the
  pay-for-file pre-check uses the combined total so a Fedimint-funded node isn't
  wrongly blocked.

Compiles (cargo check + vue-tsc). Live cross-node federation validation pending
(dual-ecash phase 6) — needs two nodes sharing a federation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 08:13:23 -04:00
archipelago
b3633ec525 fix(ui): surface real error instead of generic toast + catch async errors
The global Vue errorHandler swallowed every crash into "Something went wrong.
Please refresh the page." — which hides exactly what we need to diagnose the
companion-app (Android WebView) post-login crash. Now:
- the toast shows the real (truncated) error message;
- a 25-entry ring buffer is kept on window.__archyErrors for retrieval where
  there's no console (companion WebView via chrome://inspect, or a debug view);
- window 'error' and 'unhandledrejection' listeners catch async/non-Vue errors
  that Vue's errorHandler misses (e.g. a JS API absent in an older WebView).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 08:05:51 -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
5f7e8dca80 docs: handoff — mesh rename done, .120->.89 dup-contact diagnosis, netbird TODO
Resume notes for the 1.8.0 bug-bash mesh work: Meshtastic rename shipped +
verified; .120->.89 'non-delivery' diagnosed to a duplicate-contact surfacing
bug (messages inject fine, split across federation/radio twin contact_ids);
design for the dedup fix (#12) and the netbird logout-race map (#10).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 06:06:03 -04:00
archipelago
d00d1b20d7 fix(mesh): rename Meshtastic radio to the node's server name
Meshtastic device rename was a no-op — set_advert_name only updated an
in-memory field and never told the radio, so the device kept its firmware
default ('Meshtastic xxxx') and wasn't findable from external Meshtastic
apps. MeshCore already renamed correctly (CMD_SET_ADVERT_NAME); this brings
Meshtastic to parity.

Send an AdminMessage{set_owner=User{long_name,short_name}} to the locally
connected node (admin packet to our own node_num on the ADMIN_APP port).
Local serial admin needs no session passkey, matching the official client.
long_name = server name (<=39 chars); short_name = first 4 alphanumerics,
upper-cased. Verified on real hardware: .120 -> 'Archy-X250-EXP', .5 ->
'Archy-X250-Beta' (name read back from the radio after reconnect).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 06:04:22 -04:00
Dorian
b00c5247f5 chore(android): update companion apk download 2026-06-20 10:34:49 +01:00
Dorian
e39e0370e2 fix(android): push icon ring to home-screen visible edge (scale 0.65, v0.4.6)
Calibrated from a device home-screen screenshot: launcher3 crops less than the
App-info view, so the ring at 0.53 sat ~78% out. Scale 0.65 reaches the edge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 10:34:44 +01:00
Dorian
3b9eb35a37 chore(android): update companion apk download 2026-06-19 22:22:59 +01:00
Dorian
011f6559e1 fix(android): icon ring matching logo.svg gradient at visible edge (v0.4.5)
Ring uses logo.svg's #000->#666 gradient (stroke 22.8834) pushed to scale 0.53
so it sits at the launcher's visible crop edge (calibrated from a device
screenshot). Grid at 0.55. versionCode 9 so launcher3 refreshes its icon cache.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 22:21:58 +01:00
Dorian
979e6525b7 fix(android): icon ring at visible crop edge (scale 0.50) + version 0.4.4
Device App-info screenshot showed the launcher only renders the central ~54%
of the adaptive icon, clipping the ring. Calibrated the ring to scale 0.50 so it
lands at the visible circle edge; grid to 0.55. Bump versionCode 8 so launcher3
refreshes its icon cache (it keys the cached bitmap by versionCode).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 22:21:58 +01:00
archipelago
af816c61a5 fix(ui): reliable federation-join feedback (90s timeout + re-check + success)
Joining a Fedimint federation is heavy and routinely outlasts the default 15s
client timeout while still succeeding server-side, so the UI wrongly showed
failure. Bump the join timeout to 90s, and on any error re-check the list: if a
new federation appeared the join worked — show 'Federation joined.' instead of
a misleading error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 16:43:30 -04:00
archipelago
63611a4453 fix(mesh): honour explicit !ai allowlist for unauthenticated stock clients
A stock meshcore client (e.g. a phone) can't sign our typed envelopes, so it is
never 'authenticated' — which meant ticking it as an allowed assistant contact
had no effect and !ai stayed denied. The explicit per-contact allowlist is a
deliberate operator opt-in for a specific key, so match it regardless of
authentication, keyed on the asker's resolved identity (bound archipelago key,
else firmware routing key — how meshcore addresses the contact). The spoofable
federation-trust-list match still requires authentication.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 16:43:30 -04:00
archipelago
7831e68d13 fix(wallet): redeem across all federations, unified ecash history, fmcd healthcheck
- reissue_into_any now tries the UNION of the local registry AND fmcd's live
  joined set (/v2/admin/info) before failing, so a valid Fedimint token isn't
  wrongly rejected when the registry has drifted. On all-fail it returns a
  friendly message: notes already redeemed into this wallet (funds safe) vs
  didn't match any connected federation.
- Unified transaction history: a local Fedimint tx log (recorded on each
  successful redeem) is merged with the Cashu history in wallet.ecash-history,
  newest-first, each tagged kind=cashu|fedimint. Previously a Fedimint receive
  appeared nowhere.
- fedimint-clientd healthcheck -> type:tcp. It was probing /health, which fmcd
  doesn't serve (only /v2/*), pinning the container in (starting) forever; the
  TCP probe is skipped by the Quadlet renderer (host-side lifecycle verifies),
  so it reports running. Cosmetic for ecash, which worked throughout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 16:43:29 -04:00
Dorian
0f2e6f6aaf chore(android): update companion apk download 2026-06-19 21:28:29 +01:00
Dorian
5afe9e4aec fix(android): whole badge in background layer, ring inset to survive mask
Put dark fill + inset metallic ring (0.88) + grid (0.58) all in the background
(renders to the mask edge, no safe-zone crop); transparent foreground. Matches
a locally-rendered, circle-masked preview so the ring is visible and uncut.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 21:28:26 +01:00
Dorian
857dc66240 chore(android): update companion apk download 2026-06-19 19:22:00 +01:00
Dorian
75f7020e3e fix(android): ring at circle edge (background layer) + smaller grid
Move the metallic ring into the background (renders to the mask edge, unlike the
foreground which is cropped to the safe zone) so the border is finally visible
at the circle's rim; shrink the grid to ~0.55 so the mark isn't too big.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:21:57 +01:00
Dorian
75666cdc31 chore(android): update companion apk download 2026-06-19 19:20:21 +01:00
Dorian
8977ea92e8 fix(android): shrink icon grid within the ring for more margin
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:20:18 +01:00
Dorian
ca38f5d8f4 chore(android): update companion apk download 2026-06-19 19:05:57 +01:00
Dorian
d72cb57545 fix(android): brighter, thicker icon rim (#555->#A5A5A5, stroke 28)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:05:55 +01:00
Dorian
dc2cdca549 chore(android): update companion apk download 2026-06-19 19:00:35 +01:00
Dorian
ee01ab9427 fix(android): make icon rim softly visible (#3A3A3A->#888)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 19:00:35 +01:00
archipelago
cebbde7bde fix(ui): square mobile file tiles, files scroll clearance, apps-tab swipe guard
- Apps tab: a horizontal swipe that starts on an app icon no longer flips the
  top tab — it lets the app-page scroll / icon tap win (swipe empty space to
  change tab). Fixes the swipe conflict with two pages of apps.
- Files: file cover tiles are forced square on mobile (aspect driven by CSS,
  not a Tailwind arbitrary class) so the grid is uniform and tappable.
- Files: scroll container gets bottom safe-area + tab-bar padding so the last
  row clears the mobile back button / bottom nav.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:57:51 -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
Dorian
839da80e0b chore(android): update companion apk download 2026-06-19 18:50:39 +01:00
Dorian
f0e9343d74 fix(android): drop white-wrapping round PNG, single SVG-matched icon ring
Revert to a pure adaptive icon (the bare round PNG was getting legacy-wrapped
onto a white circle by the launcher). One ring only, in the foreground, using
the SVG's dark #000->#666 gradient on a plain dark tile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:50:34 +01:00
Dorian
bf6d98195e chore(android): update companion apk download 2026-06-19 18:40:39 +01:00
Dorian
846b2d9646 fix(android): match icon ring to logo.svg gradient (#000->#666)
Revert the brightened grey->white ring back to the original logo.svg gradient
(black->#666, stroke 22.8834) on both the round PNG icon and the adaptive
foreground.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:40:37 +01:00
Dorian
6df776b25a chore(android): update companion apk download 2026-06-19 18:32:00 +01:00
Dorian
1074f89c47 feat(android): true-circle round launcher icon (PNG badge)
Render the full circular badge (bright grey->white ring + grid) to round-icon
PNGs at all densities and drop the adaptive round XML, so launchers that use
round icons show a real edge-to-edge circle instead of a mask-cropped coin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:31:57 +01:00
Dorian
726cc132af chore(android): update companion apk download 2026-06-19 18:26:59 +01:00
Dorian
078c1793a9 fix(android): fit full badge (ring + grid) inside icon safe zone
Scale the whole badge to ~0.64 so the bold grey->white ring isn't clipped at
the edge by the launcher mask; bigger, brighter ring. Background is plain dark.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:26:54 +01:00
Dorian
b83e2c2f37 chore(android): update companion apk download 2026-06-19 18:26:34 +01:00
Dorian
a2fa57456d fix(android): scale icon badge into safe zone so the ring is visible
The ring at 0.96 sat in the adaptive-icon bleed zone (outer ~18dp cropped by the
launcher), so only the grid showed. Scale badge + grid to 0.68 so the ring lands
at the edge of the visible circle, and brighten it to grey->white.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:26:32 +01:00
Dorian
64937df8a2 chore(android): update companion apk download 2026-06-19 18:12:41 +01:00
Dorian
6527e66c07 fix(android): visible metallic icon ring at circle edge
Move the badge ring into the background layer (brightened grey->white so it
reads on #0A0A0A) at ~0.96 so it sits at the masked-circle edge; foreground is
just the white grid. Also honor SHIP_COMPANION in the pre-push hook.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 18:12:38 +01:00
Dorian
07b611d07d chore(android): add companion APK auto-publish hook + script
scripts/publish-companion-apk.sh builds the debug APK and refreshes the served
download neode-ui/public/packages/archipelago-companion.apk.zip; .githooks/pre-push
runs it on every push to main that touches Android. Enable per clone with
  git config core.hooksPath .githooks

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:53:38 +01:00
Dorian
dcedf9582a chore(android): update companion apk download 2026-06-19 17:46:44 +01:00
Dorian
f2c420d9c0 feat(android): app icon gradient ring border + companion publish script
Adaptive icon foreground now draws the full badge (black→grey gradient ring +
white grid) scaled to ~0.94 so the ring reads as a clean border at the circle
edge. Adds ship-companion.sh: builds the debug APK and publishes it to
neode-ui/public/packages/archipelago-companion.apk.zip, then commits + pushes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:46:41 +01:00
Dorian
68cd1c120a fix(android): translucent glass DARK controller so backdrop shows through
The controller body/face were opaque, so the synthwave backdrop only peeked
out above/below the controller. Make the DARK palette surfaces translucent
(body/face/inlay) and drop the opaque shadow platform + the gradient's forced
0.95 alpha, so the backdrop reads through the controller as glass. CLASSIC
palette stays solid.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 16:52:02 +01:00