238 Commits

Author SHA1 Message Date
archipelago
c6f11f8ddb feat(wallet): on-chain send fee control + BTC/sats amount entry
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m55s
- sats/BTC unit toggle on the on-chain amount field with live conversion
  hint; canonical value stays sats end-to-end
- Fast / Standard / Slow fee presets (1 / 6 / 144 block targets) plus a
  custom pane taking target blocks or an explicit sat/vB rate
- confirm pane shows LND's fee estimate for the chosen speed via the new
  lnd.estimatefee RPC (GET /v1/transactions/fee)
- lnd.sendcoins now accepts target_conf / sat_per_vbyte with the same
  mutual-exclusion + range validation as channel opens

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 07:05:10 -04:00
archipelago
3037e9808e docs(handoff): app direct ports over mesh — DONE, mesh-verified
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 19:12:46 -04:00
Dorian
e17de63016 docs(handoff): app direct ports are IPv4-only over the mesh — node-side task
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 23:55:43 +01:00
archipelago
6ba4540c53 Merge vc25 (PR #111) — handoff reconciled: vps2 daemon restart resolved the mesh path; private-tree withdrawn
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 18:42:15 -04:00
archipelago
5454bed038 docs(handoff): RESOLVED — vps2 daemon degradation was the mesh blocker; privatization withdrawn
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 18:40:51 -04:00
Dorian
75ecd1d3ea feat(companion): 60s mesh probe window + session pre-warm from the VPN service
Implements the app-side recommendations from the node diagnosis
(HANDOFF-2026-07-23): first session through the public tree takes 15s+
when it works at all, far beyond the old ~8s probe window.

- connect(): mesh ULA probed inside a 60s budget, 15s per-phase timeouts.
- ArchyVpnService: session warmer probes every saved node ULA as soon as
  the tunnel is up (5s cadence first minute, then 60s keep-warm) so the
  UI hits a warm session and it never idles out.
- Served APK: 0.5.5 (vc25); handoff updated with 25-ping evidence that
  public-tree discovery currently fails outright (supports the
  private-tree infra decision).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 23:36:34 +01:00
archipelago
82fcccd113 docs(handoff): node-side mesh diagnosis — nginx v6 fixed, real blocker is session path quality
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 18:25:18 -04:00
Dorian
53037b2b71 fix(companion): TUN reader no longer dies on non-blocking fd + VPN not metered
On-device diagnosis (phone on adb, 5G): mesh sessions established but no
packet ever entered the tunnel — Android hands the VpnService TUN fd over
non-blocking, and the fips fork's dedicated blocking-read thread treats
EAGAIN as fatal, dying at startup. archy-fips-core now forces the fd into
blocking mode before Node::start_with_tun_fd. Verified on-device: reader
survives, packets flow into the mesh, and the 30s anchor-link flap is
gone (stable 8+ min on 5G).

Also setMetered(false): Android 10+ defaults VPNs to metered, flipping
the phone into data-restricted behaviour whenever the mesh is up.

Served APK: 0.5.4 (vc24). Handoff updated with the remaining node-side
blocker: phone->anchor works, phone->node ULA gets no reply — needs
fipsctl introspection on Framework PT (suspect fork/daemon session or
routing mismatch).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 22:47:26 +01:00
Dorian
e49af6ff40 fix(companion): guarantee dual-path peering — LAN p2p + public anchor, always
The whole point of FIPS: the phone peers with the node's npub using the
LAN endpoint as a direct p2p dial hint AND rendezvouses via a public
anchor when away — neither LAN nor 5G location may matter. The anchor
half only existed when the scanned QR carried fanchors, so pairing
against an older node left the phone LAN-only and everything died on 5G.

The Archipelago vps2 anchor (npub + 146.59.87.168:8444/tcp, lockstep
with core fips/anchors.rs) is now baked into upsertNodePeer as a
guaranteed peer. Mesh connect retry window widened so a first-ever
pairing survives the VPN consent dialog. Served APK: 0.5.3 (vc23).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 22:17:24 +01:00
Dorian
979113c598 fix(companion): mesh VPN no longer blocks IPv4 + off-LAN connect via mesh ULA
Field test on 5G (Framework PT): pairing scanned fine but connect
hard-failed on the scanned LAN IP, and enabling the mesh VPN killed the
phone's internet.

- ArchyVpnService: the TUN is IPv6-only, and Android blocks every
  address family the VPN holds no address for — allowFamily(AF_INET)
  (+ allowBypass) so normal traffic flows while only fd00::/8 is routed.
- ServerConnectScreen.connect(): when the LAN address doesn't answer and
  the entry carries a mesh ULA, bring the tunnel up (autoStartIfReady)
  and probe the ULA with retries before reporting failure — the scanned
  IP is a dial hint, the npub/ULA is the identity (npub-first contract).
- Served companion APK refreshed as 0.5.2 (vc22); handoff doc updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 22:00:15 +01:00
Dorian
35849c88c6 chore(android): companion 0.5.1 (vc21) — refresh served APK + deploy handoff
The APK served from the pairing QR predated today's scanner fixes, so
the download-then-scan flow still hit the laggy scanner. Bumps the
version so phones update in place, refreshes the served artifact
(signed v1+v2+v3, verified), and adds the archi-dev-box deploy handoff.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 21:53:29 +01:00
Dorian
bb4166954a feat(companion): saved servers keyed by node npub — pairing contract item 1
Re-scanning a node after a LAN renumber updated the FIPS peer (npub-
matched) but duplicated the saved-server entry (address-matched).
ServerEntry now carries the node's fnpub as a trailing serialization
field (legacy entries still parse) and every saved/active-entry match
goes through sameNode: npub identity first, address/port/scheme only as
the LAN-only fallback. Edit forms that don't carry the npub keep the
stored one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 21:47:10 +01:00
archipelago
265196bc2c docs: TV input design — keyboard/gamepad inside iframe apps, globally
Some checks failed
Demo images / Build & push demo images (push) Failing after 36s
evdev->uinput gamepad-as-keyboard daemon on kiosk nodes (works in any
iframe, any origin) + shell-side focus handoff via useControllerNav;
CDP/per-app injection rejected. Implementation order included.

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

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

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

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 22:04:59 +01:00
archipelago
6171168927 docs: Pine voice command book — every phrase that works, local vs Claude-routed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 12:58:47 -04:00
archipelago
184257390d feat(pine): 1.3.0 manifests — openwakeword member + live node status on the launcher page
Some checks failed
Demo images / Build & push demo images (push) Failing after 1m40s
pine-openwakeword manifest (wyoming-openwakeword 2.1.0, :10400, /custom model
dir for the future Yo Archy model). Pine 1.3.0: launcher page gains a live
node-status card fed by /api/pine/status via a same-origin nginx proxy, and
copy for the new intents / Claude fallback / mesh announcements. Catalogs,
app-session config, drift ids regenerated; release catalog embeds 56
manifests (unsigned until the ceremony). Framework PT test plan in docs/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 02:59:34 -04:00
archipelago
4fb200e938 docs: handoff for the v1.7.106-alpha OTA + ISO release
Captures the three commits this release carries, the FIPS 0.4.1 upgrade
state (2 nodes done, fleet not rolled), the peer-files diagnosis including
what is explicitly NOT explained, the open decisions not taken, and the
exact release + ISO ritual.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 15:01:38 -04:00
archipelago
9e264611e2 docs: Nostr signer-login research + identity-import UX plan
Research report on the most frictionless "sign in with your Nostr signer"
flow for node login (NIP-07 extension + NIP-46 QR scan with Amber, password
always kept as fallback), and a plan for adding existing Nostr identities
(nsec import / npub watch-only / browser extension) to the Nostr Identities
screen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 21:59:54 -04:00
Dorian
2b3a18f189 feat(android): QR pairing — scan, deep link, one-step auto-login
Companion side of docs/companion-pairing-qr.md (v1 contract):
- Connect screen lands on Scan Node's QR / Enter Manually; NESMenu gains
  an add-server-by-QR icon button
- QrScannerOverlay: CameraX + ZXing core (pinned, on-device, no ML Kit)
- ServerQrParser: archipelago://pair?v=1&url=..[&pw=..]; unknown major v
  -> 'update the app'; upsert by origin so re-scans never duplicate
- archipelago:// deep link registered (singleTask + onNewIntent) for the
  modal's 'Open in companion app' button
- WebView auto-fills and submits Login.vue's password form when a stored
  password exists — demo QR is one step to a logged-in session
- Web modal + handoff doc copy synced to the real label: Scan Node's QR
- v0.4.13 (versionCode 17)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 01:41:35 +01:00
archipelago
3028685e6b feat(ui): companion modal pairing screen — scan a QR to auto-connect the app
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m45s
The Remote Companion intro modal gains a second screen: an "I've installed
it" button next to the download button slides over to a pairing QR the
companion app scans to load the server details automatically, with a Back
button to return. The QR is an archipelago://pair deep link carrying the
server URL — the demo embeds https://demo.archipelago-foundation.org plus
the shared demo password so a scan lands in a logged-in demo session; real
nodes send only the address (kiosk advertises the mDNS .local name since
its own origin is localhost). Phones also get a tap-to-open deep-link
button since you can't scan your own screen.

App-side contract + requirements handed off in docs/companion-pairing-qr.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 20:07:56 -04:00
Dorian
0a734cb6b2 chore(dev): vite /app catch-all + filebrowser proxy to mock; demo docs to main
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 03:39:56 +01:00
Dorian
533c0713c7 docs(tracker): tick §F per-device secrets + ISO checksums (caf9e6d3)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 13:42:23 +01:00
Dorian
4b0e44fc6c chore: purge retired git.tx1138.com registry host from the codebase
The tx1138 Gitea was retired as a release server 2026-06-13 and its registry
frontend is fully dead (500 on every /v2 manifest read, observed 2026-07-10).
Nothing may reference it anymore:

- registry.rs: no longer a default registry, no longer force-enabled on
  load; saved configs are stripped of it on load (same one-time migration
  treatment as the decommissioned Hetzner mirror), with a regression test.
- image_policy.rs: removed from TRUSTED_REGISTRIES — refs through the dead
  host are now refused at the pull site (rejection test added).
- api/handler: dropped the legacy catalog-proxy fallback URL.
- .gitmodules: indeedhub submodule repointed to the OVH Gitea.
- scripts, image-recipe, app-catalog data, neode-ui strings, docs, and all
  test fixtures repointed to 146.59.87.168:3000 (or neutral example hosts).
- image-versions.sh: ARCHY_REGISTRY_FALLBACK emptied (guarded consumers
  skip it); reconcile-containers.sh candidate guard hardened.

The only remaining occurrences of the host string are the strip/reject
enforcement paths and their regression tests — the code that guarantees it
is never used again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 18:55:32 +01:00
ai
6109074748 Merge pull request 'feat(mesh): !archy command — node status over the mesh, no AI' (#72) from feat/mesh-archy-command into main 2026-07-09 12:48:28 +00:00
Dorian
e56c5af3ba feat(mesh): !archy command — node status over the mesh, no AI
`!ai` sends the question to a language model. `!archy` never does: it reads
the same status caches the /bitcoin-status and /electrs-status endpoints
serve, so answers are deterministic, cost no tokens, and stay available when
the assistant is switched off.

Sub-commands: status (default), btc, electrs, version; anything else returns
a usage hint. Replies are single-frame terse to respect airtime.

Reuses the assistant's trust gate (is_sender_allowed) and reply routing
unchanged — blocked, allowlist, trusted_only and federation-Trusted all
behave identically. The one deliberate asymmetry is that !archy does not
require assistant_enabled, since it never calls a model.

Wired into both entry paths: plain radio text (decode.rs) and typed 1:1
chat (dispatch.rs). A command hooked into only one of them would work from
a phone but not the UI, or vice versa.

Adds docs/COMMANDS.md — the user-facing surface for mesh, voice and HTTP,
which had no reference until now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 12:57:14 +01:00
archipelago
75cb9f9aff docs(hardening): tick transitional-state stick + install_log + gate 123/124 fixes
All three landed 2026-07-09 (dd3afbba, c3f0a306, 2683ad4f0). Files the
repair-wait user-stop preemption as the open follow-up of the stick fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 03:11:45 -04:00
archipelago
922b79bf95 fix(lifecycle): resurrect fully-stopped stacks member-by-member (quadlet)
Second instance of the stop→start destruction class from tonight's gate
runs (.228): package.stop of a multi-container stack marks every member
user-stopped and quadlet-removes their containers; package.start's fallback
then resurrected only the bare package id, leaving the other members absent
AND user-stopped — indeedhub lost all 7 containers this way (mempool needed
its umbrella alias for the same reason).

- New stack_member_app_ids map (real manifest app ids, dependency order) —
  used by the start fallback when a stack has no live containers, and by
  package.restart instead of hard-failing "No containers found".
- orchestrator_uninstall_app_ids grows the missing indeedhub arm (same
  reconciler-ghost rationale as the immich arm).
- do_orchestrator_package_start no longer aborts the whole sequence when one
  member's unknown-app-id fallback fails (the #73/#74 wish) — collects and
  continues.

NOTE: extends the known §G per-app-map debt (tracker updated) — the proper
home for stack membership is a manifest field; parked post-tag by design.

Tests: package:: 48/48 incl. new fallback + indeedhub-uninstall coverage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 21:22:40 -04:00
archipelago
ffc1ebd0f3 docs(hardening): record the four user decisions of 2026-07-08
Bitcoin RPC loopback+gateway bind DONE dd61a204; version = 1.8.0-alpha;
bitcoin multi-version rides the next OTA; 3ccc RF validation dropped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 20:29:56 -04:00
archipelago
81743874a1 fix(tests): bound the lnd getinfo probe + track the backend-recreate cascade gap
lnd.bats retry loop had no per-exec timeout — a wedged lnd RPC (chain-blind
after bitcoin-knots was recreated under it) hung one podman exec, and the
whole gate, indefinitely (.228, 30+ min at test 75). timeout 10 per attempt
keeps the intended ~4min bound. Tracker: new §C item for the root cause —
apps cache their backend container's IP; a backend recreate must cascade a
dependent restart/alert or lnd goes chain-blind silently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 19:48:30 -04:00
archipelago
e706d446e7 docs(hardening): tick §B/§C/§H items landed 2026-07-08 + two new findings
Secrets→podman-secrets verified on .228; release-path guards e77ccff0; test
creds/timeouts 380f4f19; mempool umbrella lifecycle fix 161a6e4d. New open
items: manifests hardcoding secrets in plain environment, orphan umbrella
container cleanup follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 19:36:27 -04:00
archipelago
1316b0c0d4 docs(tracker): fix SESSION-1.8.0-OTA-PROGRESS links to archive/ location
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 18:50:52 -04:00
archipelago
1394994e0c docs(ops): fix runbook/hotfix/troubleshooting against reality + redact committed sudo password
- operations-runbook: redact the plaintext sudo password (rotate it — it
  stays in git history); replace the nonexistent test-cross-node.sh /
  test-reboot-survival.sh with the real entry points (lifecycle gate,
  multinode smoke, e2e/post-install scripts)
- hotfix-process: de-hardcode the v1.0.x framing, point releases at
  create-release.sh + primary Gitea, correct the rollback backup paths
  (/opt/archipelago/rollback + updater backup dir, not /usr/local/bin)
- troubleshooting: connectivity probe start9.com -> debian.org
- INSTALL.sh: drop removed endurain app, point at developer-guide.md
  (development-setup.md never existed)
- tests/lifecycle/TESTING.md: replace the stale 2026-06-21 mid-session
  resume block with a resolved note; release-gate item 8 reflects the
  2026-07-08 version decision (1.8.0-alpha)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 18:46:51 -04:00
archipelago
6e2d128c51 docs(app-platform): sync the platform docs with the shipped code
- app-manifest-spec.md: full rewrite from the real schema in
  core/container/src/manifest.rs — it was 5 months stale and missing the
  entire modern feature set (build, network/network_aliases, derived_env,
  secret_env, generated_secrets/certs, data_uid, files, interfaces, hooks,
  extensions flatten) and documented wrong network_policy values.
- app-developer-guide.md: add generated_secrets/generated_certs/
  network_aliases/hooks to the field table.
- APP-PACKAGING-MIGRATION-PLAN.md: phase status stamped (1-3 done, 5 mostly,
  4+6 open); deleted meshtastic app removed from regression-proof lists.
- registry-manifest-design.md: status design → implemented (phases 1-3),
  stale manifest_dir:Option line fixed.
- marketplace-protocol.md: reframed proposal → as-built (marketplace.rs +
  RPCs + UI shipped; create-invoice noted; schema disambiguated).
- manifest-hooks-design.md: phase 3 (indeedhub) done, phase 4 resolved via
  orchestrator+generated_secrets instead of hooks.
- README/architecture: restore the NIP-07 signer-bridge claim — it is real
  (neode-ui/public/nostr-provider.js), the earlier audit only grepped Rust.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 17:24:22 -04:00
archipelago
9897af1236 docs: fix developer-guide route registration + CONTRIBUTING dead links
developer-guide: RPC dispatch moved from mod.rs handle_rpc_call to
dispatcher.rs dispatch() long ago — the registration instructions now point
at the real file; module tree updated (package/, federation/, mesh/ are
directories; mesh is tri-protocol); roadmap pointer loop/plan.md → docs/ROADMAP.md.
CONTRIBUTING: docs/development-setup.md never existed → developer-guide.md;
GitHub-specific fork URL generalized to the Gitea instance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 17:10:48 -04:00
archipelago
8b16999a6e docs(architecture): rewrite against code ground truth
Fixes: phantom parmanode crate removed and crate table now matches the real
workspace (5 members + 4 standalone), Debian 12 box → 13, LOC/file counts
were understated ~2x (49k→117k Rust, 47k→69k TS/Vue), 30→51 apps, dispatcher
is dispatcher.rs with ~380 methods (not mod.rs '100+'), NIP-07 claim replaced
with the NIPs actually implemented (33/44/04), Meshcore-only mesh → tri-protocol
with Reticulum daemon, dead MASTER_PLAN.md/BETA-PROGRESS.md links replaced.
Adds as-built sections for the app platform (manifest → Quadlet → signed
catalog overlay → marketplace) and drops the environment-specific node
IP inventory from a public doc.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 17:09:24 -04:00
archipelago
fb46c9fa39 docs(tracker): ceremony done 2026-07-02, version decided 1.8.0-alpha, note quadlet false-drift fix
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 17:08:41 -04:00
archipelago
fcd5a065dc docs: rewrite README against code ground truth + add public ROADMAP
README fixes: version badge 1.3.1→1.8.0-alpha, real LOC (117k Rust/69k TS-Vue
vs the stale 49k/47k), 51 apps not 29, tri-protocol mesh
(Meshtastic/MeshCore/Reticulum) not Meshcore-only, Nostr NIP-33/44/04 (NIP-07
was never implemented), Ed25519 signed catalog + pinned release-root anchor,
OTA host de-hardcoded (tx1138 retired), dead docs/MASTER_PLAN.md link removed,
crate map corrected (openwrt added, phantom crates dropped), new Philosophy and
Roadmap sections reflecting the manifest-driven/signed-registry north star.

docs/ROADMAP.md is the public-facing shipped/in-progress/planned summary,
synthesized from the master plan, unified tracker, and 1.8.0 hardening plan —
including the completed signing ceremony and the 1.8.0-alpha version decision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 17:07:57 -04:00
archipelago
9221d1bfaf docs: move 10 finished/historical docs into docs/archive/ with an index
Session logs, handovers, point-in-time snapshots, the v0.1.0 security audit,
stale generated HTML guides, and shipped design docs (three-mode UI, install
screens) move out of the living docs/ tree. docs/archive/README.md explains
what each was and why it's archived; all in-repo links updated.
(docs/UNIFIED-TASK-TRACKER.md links intentionally untouched — it has another
agent's uncommitted work in flight.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 17:04:35 -04:00
ai
833527078c Merge branch 'main' into worktree-reticulum-tcp-interop 2026-07-06 11:54:53 +00:00
archipelago
804874a78b docs(tracker): record the failed-unit self-healing gap observed live on .228
fedimint sat 'failed' for 7+ hours (exit 255 at 21:21, pre-gate) — the
reconciler repairs missing/drifted containers but never reset-fails +
starts a failed quadlet .service, and the health monitor can't see an
app whose container is gone. Concrete lever added to the Tier 2
container-flapping item.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 01:44:21 -04:00
archipelago
4665e497d7 feat(security): move secret env out of podman inspect and Quadlet unit files
Secret env used to merge into manifest.app.environment, landing in
'podman inspect' Config.Env on the API backend and — worse — as
plaintext Environment= lines in Quadlet unit files on disk. Now:

- expand_and_partition_env (container crate, pure + tested) expands
  ${KEY} placeholders and splits env into plain entries and
  secret-bearing pairs. Plain entries that interpolate a secret
  (btcpay's Password=${BTCPAY_DB_PASS} connection strings) are
  tainted and travel as secrets too. Secret values themselves are
  never expanded (a generated value containing '${' passes verbatim).
- values register as podman secrets: stdin (never argv/tempfile),
  --replace, content-hash label to skip no-op rewrites; a per-app hash
  cache in the orchestrator makes steady-state reconciles free of
  podman secret calls. Registration goes through the runtime trait
  (default no-op keeps mocks/docker inert).
- containers reference secrets by name: secret_env map in the libpod
  create spec, Secret=<name>,type=env,target=<KEY> in Quadlet units.
  Verified empirically on fleet podman 5.4.2: value absent from
  inspect Config.Env, runtime injection works rootless.
- rotation detection: io.archipelago.secret-env-hash container label
  (API) / the changed unit bytes (Quadlet). Pre-upgrade containers
  lack the label, so every secret-bearing app recreates ONCE on the
  first reconcile after deploy — deliberate, it scrubs the plaintext
  secrets out of existing container configs. Data dirs untouched.
- docker dev fallback keeps plain -e injection (no secret store);
  podman secrets persist across uninstall, matching the
  preserve-credentials invariant (reinstall re-registers by hash).

In-container /proc/<pid>/environ is unchanged — env remains the
app-compat contract; the closed leaks are inspect output and unit
files on disk.

Tests: archipelago-container 61/61 (3 new: taint partition, verbatim
secrets, hash order-independence), archipelago container:: 160/160
(fedimint install test now asserts the secret arrives as a ref, not
env; quadlet render test asserts Secret=/Label= lines). NEEDS the
on-node gate re-run before the item counts as verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 13:55:15 -04:00
archipelago
eed830e1ee feat(security): enforce declared cosign image signatures at the pull sites
New container::image_verify gates PodmanClient::pull_image and the
dev-only DockerRuntime::pull_image. Signature claims classify three
ways: absent/empty (pull unverified, logged), the literal
'cosign://...' placeholder every fleet manifest carries today (same —
enforcement stays dormant until the signing ceremony ships real
values), or a declared signature, which must verify via
'cosign verify --key /etc/archipelago/cosign.pub
--insecure-ignore-tlog=true' (plus --allow-insecure-registry
--allow-http-registry for the HTTP mirror; flags checked against
cosign's own docs) before anything is fetched. Missing key, missing
cosign binary, timeout, or verification failure all hard-fail the
pull — a declared signature cannot be skipped on either runtime. Key
path overridable via ARCHIPELAGO_COSIGN_PUBKEY for tests/staging.

Deletes security::ImageVerifier: zero callers, blocking
std::process::Command on would-be async paths, and a fantasy
'cosign verify --signature' invocation (that flag belongs to
verify-blob).

Activation ships with the Workstream B ceremony, in order: pin
cosign.pub on nodes + install cosign, then publish real
image_signature values in the catalog.

Tests: archipelago-container 58/58 (5 new), archipelago container::
159/159, security check clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 18:11:32 -04:00
archipelago
2c8c99fd28 fix(security): bind seq into mesh signatures (v2 preimage), guard DID slice, cfg-gate dev password
- mesh: verify_signature accepts a v2 preimage (t,v,ts,seq) alongside
  legacy v1 (t,v,ts); signed_with_seq() is the v2 sender path, not yet
  wired — senders stay v1 until the fleet verifies v2 (receivers
  hard-drop bad sigs, so flipping send-side first would break
  mixed-fleet alerts). Tests: v2 verify, v2 seq-tamper rejection,
  v1 sign-then-set-seq compat.
- mesh listener: malformed radio-supplied DID shorter than the
  'did🔑' prefix can no longer panic advert_name (slice -> .get()).
- auth: the pre-setup password123 dev login and the constant itself are
  now #[cfg(debug_assertions)] — no release binary carries the bypass,
  whatever its runtime config says.
- orchestrator: canned host-facts under #[cfg(test)] — awaiting real
  subprocesses under tokio's paused test clock deadlocks against
  auto-advanced timers (the old blocking detection only worked by never
  yielding).
- drop two now-unused std::process::Command imports left by 4c75bb3d.

Tests: mesh 110/110 (incl. 2 new), api 68/68, container 159/159,
archipelago-container check clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 17:49:52 -04:00
archipelago
291f2d7186 docs(tracker): add explicit container-flapping/reconciler-churn workstream to Tier 2
Was implicit across the Phase-3 Quadlet flip and Workstream F; now one
consolidated pre-tag item with the lever list, an observability proposal
(per-app restart counter + flap log line), and an already-landed list so
nothing gets re-done.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 17:41:27 -04:00
archipelago
9020b8526c fix(security): stop trusting client-supplied forwarded headers in rate limiting
extract_client_ip took X-Real-IP/X-Forwarded-For from any request, so
a client talking to the backend directly (the FIPS peer listener, or
any non-proxy path) could rotate a fake IP per request and never trip
the login rate limiter. The accept loop now records the TCP peer
address in request extensions, and forwarded headers are honored only
when the connection itself is from loopback — where nginx overwrites
X-Real-IP with the real client address. Direct connections bucket
under their socket IP.

§C of the 1.8.0 hardening plan; 3 new unit tests cover the
loopback/direct/no-header matrix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 15:48:07 -04:00
archipelago
bd7edb4376 feat(update): deepen post-OTA verification beyond a frontend 200
verify_pending_update previously cleared the rollback marker on any
2xx/3xx from GET / — a release with a dead RPC API or broken podman
access passed and never rolled back. Verification now requires, in the
same attempt: the frontend via nginx, backend RPC liveness (an
unauthenticated POST /rpc/v1 — 401 proves the stack is up, 5xx/404/
refused fails it), and rootless podman reachability. A pre-loop check
also asserts the running binary's version matches what the marker says
was applied, catching a silent or half swap deterministically.

Per-app container assertions are deliberately excluded: the
pre-Quadlet service restart legitimately takes containers down and the
boot reconciler can need minutes for heavy apps — that would
false-rollback healthy updates. Revisit after the Phase-3 flip.

§B of the 1.8.0 hardening plan; update suite 38/38 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 13:50:00 -04:00