903 Commits

Author SHA1 Message Date
archipelago
581dbd6337 fix(install): mempool no longer blocked by a resyncing ElectrumX + slower podman probes tolerated
Two real install failures from .116 today: (1) the hard "ElectrumX must
be running" gate — but mempool-api reconnects to electrum on its own and
a resync can take days, so it's now a warning; (2) a 10s podman-ps probe
timeout tripped by ElectrumX compaction disk load — now 45s.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

mesh tests: 114/114 pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 06:10:45 -04:00
archipelago
8791ef60f5 feat(pine): wire the Pine 3-container stack into the orchestrator
Register "pine" as a manifest-driven stack (like netbird) across every
lifecycle site so install/stop/start/restart/uninstall/crash-recovery treat
the launcher + two Wyoming engines as one app:

- stacks.rs: pine_stack_app_ids() + install_pine_stack() (orchestrator-first,
  adopt existing, else bail — no hardcoded fallback)
- install.rs: dispatch package_id == "pine" to the stack installer
- app_ops.rs: canonical stack-member table + owning_package STACKS
- dependencies.rs: startup_order + needs_archy_net
- config.rs: all_container_names
- crash_recovery.rs: auto-start members + a StackRecoverySpec (anchor
  pine-whisper) so a genuinely-installed stack self-heals but orphan debris
  does not crash-loop
- docker_packages.rs: exclude the engines from the apps list

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 05:14:32 -04:00
archipelago
146a3bc738 chore: release v1.7.108-alpha
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m48s
2026-07-21 03:42:21 -04:00
archipelago
302f880d99 docs(release): fold everything into v1.7.108-alpha changelog + What's New
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m46s
Supersedes the unpublished v1.7.107. Covers the multi-anchor FIPS fix, WiFi
self-heal, mesh fast-rejoin, kiosk TV-scaling, kiosk dark theme / VT hints /
UTF-8 logo / power-button hardening, and the ISO build fixes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:59:23 -04:00
archipelago
0f13545375 fix(fips): ship a second, reachable default anchor (once-and-for-all)
Nodes depended on a single public anchor (185.18.221.160) that is
unreachable from most real networks — confirmed on the thinkpad WiFi and a
fresh Framework node, both of which islanded (is_root=true, depth=0). Its
DNS even resolves IPv6-first while the daemon is IPv4-only.

Stand up an Archipelago-operated FIPS anchor on vps2 (146.59.87.168, the
OTA host every node already reaches) and ship it as an additional default
anchor. default_public_anchor() -> default_public_anchors() -> Vec; load()
returns the set; the apply loop already dials each, so whichever the node's
network can reach wins. Verified end-to-end: a fresh node joined the tree
via vps2 in 3s (depth 1). anchors tests: 7/7 pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 17:42:18 -04:00
archipelago
7eb4d6a7bf chore: release v1.7.107-alpha 2026-07-20 17:00:20 -04:00
archipelago
cb301b1aef fix(wifi,fips): self-heal polkit rule + fast-retry anchors after daemon restart
Two fleet fixes that OTA can deliver (unlike the ISO-only polkit rule):

- WiFi (issue #99): nodes drive NetworkManager from a system-level service
  with no logind seat, so the stock polkit rule denies them and Wi-Fi setup
  fails with 'Insufficient privileges'. Fresh ISOs since 2026-05 ship the
  scoped rule, but OTA'd nodes never got it (OTA replaces binary + web UI,
  not host system config). Add a startup self-heal that installs the rule if
  missing and best-effort ensures polkitd — both wrapped so an offline/locked
  apt can never fail startup; the rule is written regardless so it activates
  once polkitd lands. Idempotent on the rule's unique subject.user marker.

- FIPS: regenerating fips.yaml (this build does, once, on first boot after
  the OTA) restarts the fips daemon; for a few seconds /run/fips/control.sock
  is gone and every seed-anchor dial fails, islanding the node until the next
  5-min tick. Detect that exact failure (all dials fail on control.sock) and
  retry in 15s instead — bounded to 8 fast retries/episode so a node with no
  fips daemon falls back to the steady cadence rather than busy-looping.

FIPS unit suite: 33/33 pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 16:39:02 -04:00
archipelago
38cb3dd252 chore: release v1.7.106-alpha
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m52s
2026-07-20 15:36:00 -04:00
archipelago
c91887a397 chore: commit Cargo.lock version bump left over from v1.7.105-alpha
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 15:05:31 -04:00
archipelago
5fd0d6c3c8 refactor(fips): generate fips.yaml from typed structs; enable mDNS LAN discovery
The daemon config was built by format!-ing a YAML string literal. Upstream's
config structs are #[serde(deny_unknown_fields)], so a key we get wrong does
not degrade — the daemon refuses to start and the node drops off the mesh.
String-built config made that a runtime discovery on a live node.

Replace it with a typed struct tree serialised via serde_yaml, verified
field-by-field against jmcorgan/fips v0.4.1, plus an exact-output snapshot
test so schema drift fails in CI rather than at boot. Also adds tests for
determinism (server.rs compares the render against disk to detect drift, so
instability would cause a reinstall+restart loop) and for the mDNS key path.

Enables node.discovery.lan.enabled (mDNS/DNS-SD, added upstream in v0.4.0)
so co-located nodes peer directly instead of depending on the public anchor
being reachable — an anchor blackhole on one segment currently islands a node
completely. Emitted unconditionally rather than version-gated: v0.3.0's
DiscoveryConfig has no `lan` field and no deny_unknown_fields, so a v0.3.0
daemon ignores it harmlessly and it activates on upgrade with no second
config migration.

Note: the first startup after this lands renders a config that differs from
disk, so the existing drift check reinstalls it and restarts the daemon once.
That is the intended self-healing path and settles immediately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 14:47:22 -04:00
archipelago
3ab7fb521a fix(rpc): log the full error chain, not just the outermost context
RPC failures logged only the top-level anyhow context, so a peer-files
failure produced exactly "RPC error on content.browse-peer: Failed to
connect to peer" with the real cause discarded. That made it impossible
to tell a dead peer from a slow Tor circuit from a FIPS resolve failure
without reproducing by hand.

Switch the log line to `{:#}` so the whole context chain is rendered.
The client-facing message still uses `{}` through sanitize_error_message,
so no internal detail is leaked to callers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 14:03:33 -04:00
archipelago
e2f83c0157 chore: release v1.7.105-alpha 2026-07-20 03:40:38 -04:00
archipelago
c1d309f21f style: cargo fmt crash_recovery.rs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 02:28:49 -04:00
archipelago
9c39243969 Merge remote-tracking branch 'gitea-ai/fix/companion-autologin-replay-intro'
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m49s
2026-07-20 01:57:19 -04:00
archipelago
f25febf3bb fix(vpn): refresh a stored peer's WireGuard endpoint to the node's current IP
vpn.peer-config returned the config exactly as stored at creation time, so
after a node moves networks the reused companion peer's QR encodes the old
location's address (seen on .116: Endpoint = 10.125.9.0 from the previous
LAN) and the tunnel can never connect. Rewrite the Endpoint line with the
node's current address before rendering the QR, and persist it back so the
downloadable .conf matches. Endpoint detection is shared with
vpn.create-peer via current_wg_endpoint_host().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 01:57:08 -04:00
archipelago
f13fdc6451 fix(recovery): don't brick-loop startup on stale crash-snapshot containers
After an unclean shutdown, crash recovery walked the running-containers
snapshot and retried 'no such container' failures (2 attempts, 10s
backoff) for containers that no longer exist. Recovery runs before the
server binds :5678 and notifies systemd ready, so a stale snapshot
pushed startup past TimeoutStartSec=5min — systemd killed the daemon
mid-recovery, the next boot saw a crash again, and the node looped
forever (151 restarts on .116 after a hard poweroff).

- pre-filter the snapshot against 'podman ps -a' and skip vanished
  containers outright (fail-open if the query fails)
- never retry a 'no such container' failure
- extend the systemd start timeout ahead of each container start so a
  heavy node recovering dozens of real containers isn't killed while
  making progress

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 01:32:42 -04:00
archipelago
163bc3af01 fix(tor): stop listing/pre-baking hidden services for uninstalled apps (#79)
Backend: tor.list-services now hides services whose name maps to a
known-but-uninstalled catalog app (aliases covered: bitcoin/knots/core,
electrumx/electrs, btcpay, mempool). The node's own service, the relay,
and custom user services always show.

ISO: first-boot tor setup no longer pre-creates hidden services for six
apps that may never be installed — only the node's own onion is baked.
It also only seeds services.json/torrc on FIRST boot instead of
clobbering backend-managed services on every boot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 01:32:33 -04:00
Dorian
cf4a8eef0e fix(core): throttle volume-ownership sweep to first-seen + hourly per container
The sweep podman-execs into every running container; doing that on each 30s
reconcile tick was a permanent conmon 'Failed to create container' storm on
hosts where exec from the backend's cgroup context fails (Debian 13 first
boot). Ownership drift is an install/OTA-time event — sweep on first pass
after a container appears, then at most hourly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 06:07:56 +01:00
archipelago
9a7331cead fix(cli): --version/-V and --help print and exit instead of booting the daemon
A stray `archipelago --version` used to start a full second instance next
to the systemd one (issue #74). Handle -V/--version, -h/--help, and reject
unknown dashed options before any tracing/state init, mirroring the
ceremony subcommand's clean-stdout precedent. Version output matches the
health RPC format: <pkg-version>-<git-hash|dev>.

Fixes #74

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 16:49:12 -04:00
archipelago
9eadec6936 chore: sign v1.7.104-alpha OTA manifest
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m36s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 02:27:55 -04:00
archipelago
6b0a84e710 chore: release v1.7.104-alpha 2026-07-19 02:17:45 -04:00
archipelago
fd361fb35e test(update): serialize the apply_update regression tests
Both tests take the global single-flight UPDATE_OP_LOCK; run concurrently
by the test harness, one saw the other's lock and failed with 'another
update operation is already running' instead of its expected refusal.
A shared test mutex makes them mutually exclusive deterministically.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 02:01:58 -04:00
archipelago
8bb61a51e2 style: cargo fmt update.rs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 00:32:57 -04:00
archipelago
25162ee846 feat(update): auto-rollback OTA binaries that crash-loop before they can self-verify
.198 crash-looped 236x on a truncated OTA binary with a good backup
sitting in update-backup/ — verify_pending_update() can't help when
the new binary never runs. New scripts/ota-crash-guard.sh runs as
root from ExecStartPre: while the post-OTA pending-verify marker
exists it counts start attempts, and after 5 failures restores the
backup binary (atomic rename), replaces the marker with an
update-rolled-back.json tombstone, and lets the service come back
on the previous version.

Wiring: fresh ISOs get the ExecStartPre line in the unit file;
existing nodes get a drop-in installed by apply_update's runtime
component step on their next OTA. '+-' prefix so a missing or
failing guard can never block the service.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:42:54 -04:00
archipelago
837cfdfd1f fix(update): never apply unverified staging — close the OTA truncated-binary race
Root cause of .198 bricking on the 1.7.103 OTA: two concurrent
update.download RPCs shared one staging file, cancel_download wiped
staging mid-flight, a third download began re-filling it, and
apply_update mv'd the 3-second-old 17MB partial of the 49MB binary
into /usr/local/bin -> SEGV boot loop (236 restarts, no rollback).

- Single-flight UPDATE_OP_LOCK across download/apply; concurrent
  callers get an explicit 'already running' error.
- apply_update now requires the .download-complete marker AND
  re-verifies every staged component (size + SHA-256 + BLAKE3)
  against the manifest before touching the system.
- cancel_download only wipes staging when no operation holds the
  lock; otherwise it just flags the in-flight loop to bail.
- Fixed the 'file already complete' path in
  download_component_resumable, which skipped verification and fell
  through to the bogus 'download failed without a captured error'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 19:33:52 -04:00
archipelago
573b469191 chore: sign v1.7.103-alpha OTA manifest
All checks were successful
Demo images / Build & push demo images (push) Successful in 2m53s
Also folds the Cargo.lock version bump that create-release.sh missed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 07:48:56 -04:00
archipelago
401f92a24f chore: release v1.7.103-alpha 2026-07-18 07:30:25 -04:00