Commit Graph
69 Commits
Author SHA1 Message Date
archipelagoandClaude Opus 5 b7e57ca9cf fix(update): stop presenting the same server as two mirrors
Demo images / Build & push demo images (push) Successful in 3m29s
Both default update mirrors resolve to the SAME host — the primary by name over
HTTPS, the fallback by IP over plain HTTP — while SystemUpdate.vue told the
operator "Servers this node checks for updates. The primary is tried first; if
it's slow or unreachable, the next one in the list is tried automatically."

That promises availability redundancy the pair cannot provide: if the origin is
down, both entries are down. Reported by the operator, who read the list and
correctly concluded the fallback made no sense.

The mechanism is fine and deliberate — it recovers a node whose DNS is broken
or whose clock is wrong, both of which fail TLS while plain HTTP still works,
and it is safe because the manifest carries an Ed25519 signature verified
against the pinned release-root anchor, so transport integrity is not what
protects the update. (That last part only became true once Workstream B pinned
the anchor; before then this fallback would have been a real hole.)

So the bug was the labelling, not the design:

- Backend label "Direct (fallback)" -> "Same server, no DNS/TLS", and the
  comment now states plainly that it is the same host, what it recovers, and
  that real redundancy needs a different one.
- UI copy now scopes the redundancy sentence to genuine mirrors and adds a
  paragraph saying the two built-in entries are one server, what the second
  actually recovers, that it does not help if the server is down, why an
  unencrypted fetch is acceptable, and how to get real redundancy.

The relabel reaches existing nodes: force_ovh_update_primary rewrites labels for
the two default URLs on every load, while the merge matches on URL and never on
label — without that rewrite path a renamed default would have sat in the code
and never propagated to a single deployed node. Noted inline so it is not
re-broken.

Verified: 40/40 update tests pass (including the mirror load/merge/strip ones),
vue-tsc clean, build green, and the new copy is present in the freshly built
SystemUpdate chunk. Nothing in the tree pinned the old label string.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 07:08:38 -04:00
archipelagoandClaude Opus 5 44b50a39d4 fix(registry): repair two regressions from the domain migration; port the manifest validator to python
Fixes 4 test failures introduced by 8e814ca0, which I pushed after running
only the container-crate tests while the full suite was still compiling. Both
failures were real defects, not stale assertions.

1. Catalog-driven installs would have failed fleet-wide.
   8e814ca0 dropped the old registry address from TRUSTED_REGISTRIES, but the
   signed catalog still advertises image refs on it — deliberately, since
   rewriting a signed artifact invalidates its signature. Nodes resolve apps
   through the catalog, so every install would have been refused with "not
   from a trusted registry". Reinstated as LEGACY_REGISTRY_HOST, documented
   as transitional and removable only once the catalog is re-signed.

2. The update fallback lost the property it exists for.
   update.rs keeps two mirrors on purpose: the domain as primary, and the
   old IP over plain HTTP as a fallback, because a node whose DNS or clock is
   wrong (both break TLS) must still be able to update itself — the signature,
   not the transport, is what makes either source safe. The bulk rewrite
   pointed both constants at the domain, leaving the escape hatch dependent on
   exactly what it exists to survive. Restored to its original value.

Separately, validate-app-manifest.sh is ported from ruby to python3+PyYAML.

It shelled out to ruby with stderr discarded, so on any machine without ruby
a missing interpreter was reported as "Valid YAML with top-level app block:
FAIL" and every manifest came back REJECTED. This is the first tool an app
developer runs, and it sent them to fix YAML that was never broken. Ruby was
also the odd dependency out — the repo already ships three python scripts.

It now checks for python3 and PyYAML up front and names what is missing, then
parses with PyYAML. Missing keys resolve to an absent-value object that
indexes to itself and prints empty, so call sites lost their per-hop guards:
  (((app["container"] || {})["build"] || {})["context"])
becomes app["container"]["build"]["context"]. Booleans still print as
true/false rather than Python's True/False — call sites compare == "true",
so Python's capitalisation would have silently inverted the readonly_root
and no_new_privileges security checks.

Verified: full rust suite 1148/1148, 0 failed. All 56 app manifests validate
(0 rejected, 0 errored) where previously every one was rejected. No signed
artifact modified.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 12:08:39 -04:00
archipelagoandClaude Opus 5 8e814ca06a feat(registry): move image and OTA references to the public domain
Demo images / Build & push demo images (push) Failing after 2m22s
Replaces the registry host across 86 files: 309 references, covering all 40
app manifests, the orchestrator and container crates, the release and catalog
scripts, both demo-images workflows, the ISO builder, demo-deploy, and the
frontend marketplace data.

Verified the domain actually serves the registry before rewriting anything,
rather than assuming the web host implies the registry:
- TLS verifies clean, HTTP/2 on the web root
- an anonymous token grants a manifest fetch (HTTP 200) with no credentials
- skopeo inspect --no-creds resolves an image and lists its tags

That last check is the one that matters: an outside developer with no account
can now pull, which was the functional blocker for publishing at all.

Plain-HTTP references become HTTPS in the same pass, so OTA downloads stop
crossing the network in the clear.

Deliberately NOT rewritten:
- The public FIPS anchor on port 8444. It is a functional network endpoint
  every node dials to bootstrap the mesh — closer to Bitcoin Core's hardcoded
  seeds than to leaked infrastructure. The domain does resolve to the same
  host, so it could become a hostname, but that adds a DNS dependency to the
  path used precisely when things are broken. Worth a deliberate decision,
  not a side effect of this change.
- The companion APK on port 2100. The domain returns 404 for that path, so
  rewriting it would swap a working URL for a broken one. The Releases page
  does serve (200), which is where the plan already wants those binaries.
- releases/app-catalog.json, releases/manifest.json and release-manifest.json.
  These carry `signature` and `signed_by`; editing their contents invalidates
  the signature and the fleet refuses artifacts that fail verification. They
  were rewritten in a first pass and reverted — they must be regenerated and
  re-signed through the signing ceremony instead, which needs the mnemonic.

So the catalog still advertises the old host until that ceremony runs. Nodes
resolve images through the signed catalog, not the on-disk manifests, so this
commit alone does not change what a node pulls.

Verified: archipelago-container 75/75; every manifest still parses with a
top-level app block; no signed artifact modified.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 11:31:20 -04:00
archipelagoandClaude Fable 5 0a374c80a6 style: rustfmt the merged PR #125 hunks and the mirror test; sync Cargo.lock
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 15:24:31 -04:00
archipelagoandClaude Fable 5 91bbe4faa1 fix: portainer pin, bitcoin conf tolerance, gate login UI, named OTA origin
Portainer: nodes have been running :latest — which is 2.39.1 — while the
manifest pinned 2.19.4 from two years ago. The port migration recreated the
container onto that old pin and Portainer refused to start: it migrates a
database forward, never backward, so an existing install died with 'schema
version does not align' and My Apps showed 'app is not responding'
(100.82.34.38). 2.39.1 published as an immutable tag and pinned forward, so
existing databases keep working and older ones migrate up.

Bitcoin: complements PR #131. That removes the code which kept writing a
datadir bitcoin.conf; -allowignoredconf=1 additionally makes an existing
one non-fatal, so a node already carrying the file recovers on restart
instead of crash-looping until something reinstalls it.

App gate login: rebuilt against the dashboard's own design — rotating
intro backgrounds served from the gate, the glass panel, the Archipelago
mark in its gradient ring, the app's icon as a My Apps tile, and the glass
button. Crucially it no longer sends X-Frame-Options: DENY, which made
every gated app render as unreachable inside My Apps' embedded frame;
frame-ancestors expresses 'only this node may frame me', which
X-Frame-Options cannot.

OTA origin: primary mirror is now source.archipelago-foundation.org over
TLS instead of a bare IP on plaintext. The IP stays as an automatic
fallback for nodes whose DNS or clock is broken — both break TLS, and the
signature, not the transport, is what establishes trust.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 12:44:57 -04:00
archipelagoandClaude Opus 5 37d293be59 style: cargo fmt — fix formatting drift blocking the release gate
Whitespace-only reflow in storage.rs/seed.rs/update.rs (rustfmt line-
wrapping rules) and app_ports.rs (array literal reflow after the port
list grew). No logic change. tests/release/run.sh's cargo-fmt --check
stage was failing on this before v1.7.119-alpha could be cut.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-31 12:15:19 -04:00
archipelagoandClaude Fable 5 49ec294dea fix(update): concurrent apply reads as progress, not failure; idle-IO extraction
Demo images / Build & push demo images (push) Successful in 5m18s
"Another update operation is already running" surfaced as a scary
failure while the update was in fact applying fine (OptiPlex, v1.7.118
rollout). The apply path now joins the in-flight install — same
overlay, same wait-for-new-version polling — and a concurrent download
attempt shows a calm in-progress note (EN+ES strings added). The
backend's tarball extractions run under ionice -c3 nice -n10 so a
200MB update can't starve podman/status calls into multi-minute
timeouts on small disks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 10:38:11 -04:00
DorianandClaude Fable 5 70bcbc4acc fix(boot): signal systemd READY before heavy boot recovery + Restart=always
Root cause of 'server starting up' forever / crash-on-install
(framework-pt, v1.7.114->115, 2026-07-26): on a node with many stacks,
the synchronous boot recovery (recover + start_stopped_containers) runs
BEFORE sd_notify(Ready), so the unit sits in 'activating' for minutes.
Anything touching the service in that window — a superseding
start/restart, an install-time reconcile churn — killed a half-started
instance; it exits 0 on SIGTERM and Restart=on-failure then never
restarts it. Node dead behind 'server starting up'.

Fixes:
- signal READY (+ start the watchdog keepalive) BEFORE boot recovery, so
  the unit reaches 'active' in seconds; recovery/reconcile/listener
  continue after. No more minutes-long activating window.
- Restart=always (was on-failure): a clean-exit SIGTERM must still bring
  the daemon back. Manual  is still honored.
- OTA restart via a PID1-owned transient timer (systemd-run --on-active=2)
  instead of a tokio-sleep child of the process being stopped, whose
  start-half was being lost.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 00:31:54 +01:00
archipelagoandClaude Fable 5 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
archipelagoandClaude Fable 5 8bb61a51e2 style: cargo fmt update.rs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 00:32:57 -04:00
archipelagoandClaude Fable 5 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
archipelagoandClaude Fable 5 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
archipelagoandClaude Fable 5 db4de0ac96 style: cargo fmt the workspace (release-gate cargo-fmt was red)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 15:38:32 -04:00
DorianandClaude Fable 5 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
archipelagoandClaude Fable 5 ceb319c292 feat(catalog): reload manifest overlay when a refreshed catalog changes
load_manifests() only ran at startup, so a manifest published via the
signed catalog sat dormant until the next service restart (found while
shipping the strfry manifest fixes: package.check-updates refreshed the
cache but the orchestrator kept rendering the old overlay).

- refresh_catalog now reports whether the cached bytes actually changed
  (write_cache skips identical rewrites), so unchanged hourly polls
  don't churn the manifest map.
- New ContainerOrchestrator::reload_manifests (default no-op); prod
  delegates to load_manifests — the rebuild is atomic under the state
  write lock, and the reconciler's durable user-stopped/uninstalled
  marker filters make a mid-session reload equivalent to the restart
  path that already runs on every boot.
- package.check-updates reloads on change and reports catalog_changed +
  manifests_reloaded; the hourly update-scheduler tick (and its startup
  refresh) do the same.

Tests: app_catalog 5/5 (new write_cache changed-detection test),
reconcile 16/16, knows_app 1/1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 18:54:00 -04:00
archipelagoandClaude Fable 5 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
archipelagoandClaude Fable 5 01cbec27ed fix(robustness): surface swallowed persistence-write failures + federation tombstone durability
§C of the 1.8.0 hardening plan: persistence writes whose Results were
silently dropped now log a warn/error with context (mesh contact
blocklist, scheduler state, content catalog, container registry,
update state, bitcoin relay, package install markers, server shutdown
state). §I: federation tombstones are now flushed durably in
storage/sync so cleared peers can't resurrect after a crash.

Tracker updated with shas in docs/1.8.0-RELEASE-HARDENING-PLAN.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 21:02:54 -04:00
archipelagoandClaude Fable 5 51647b21cd feat(trust): verify release-root signature on the OTA manifest
check_for_updates now fetches the manifest as raw JSON and runs
trust::verify_detached before parsing: a tampered or wrong-signer
signature rejects the mirror outright, and unsigned manifests are
offered for MANUAL apply only — the 3 AM auto-apply scheduler refuses
them, closing the unattended remote-root hole (§A of the 1.8.0
hardening plan). UpdateState gains manifest_signed so the UI can
surface authenticity.

Publisher side: create-release.sh signs the manifest during the
release (ceremony, mnemonic via TTY/env only), publish-release-assets
hard-refuses to ship an unsigned manifest (grep + new 'ceremony
verify' cryptographic gate), and scripts/sign-manifest.sh covers
re-signing outside a release run.

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

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

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

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

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 18:46:17 -04:00
archipelagoandClaude Opus 4.8 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
archipelagoandClaude Opus 4.8 7c458ede8e Merge agent-trust-wip (DHT Phases 0–4) into main
Integrates the DHT/peer-distribution line with the v1.7.98-alpha release
fixes:
- Phase 0 signed-catalog trust + release-root key (KAT-pinned)
- Phase 1 BLAKE3 content addressing alongside SHA-256
- Phase 2 swarm-assist fetch seam (origin always wins) + iroh-blobs
  provider — heavy iroh deps stay behind the off-by-default `iroh-swarm`
  feature, so the default build/deploy is unaffected
- Phase 3 signed Nostr seed-advertisement + discovery glue + paid swarm
  serving + "Networking Profits" Settings page
- Phase 4 paid swarm streaming (cross-mint ecash, Shape-A paid ALPN,
  streaming.prepare-payment), also iroh-swarm-gated

Conflicts resolved: seed.rs (kept release-root KAT tests), update.rs
(comment-only, OTA logic identical), Cargo.lock (regenerated against the
merged Cargo.toml). Default-feature build is clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 07:50:06 -04:00
archipelagoandClaude Opus 4.8 be3ebd7fe0 feat(dht): Phase 3 discovery glue + paid swarm serving
Phase 3 wiring (task #12):
- NostrSeedDiscovery: async ProviderDiscovery that queries relays for signed
  seed adverts and parses endpoint ids (swarm/iroh_provider.rs, seed_advert.rs).
- seed_and_advertise publish path; dep-free fetch/publish helpers reuse the
  node's Nostr identity (build_nostr_client/load_or_create_nostr_keys made
  pub(crate)).
- swarm::init builds the IrohProvider once into a OnceLock runtime; providers()
  returns it; announce_held_blob() is called from update.rs after a release
  component passes both hash gates.
- config swarm_enabled (ARCHIPELAGO_SWARM_ENABLED, default off); server.rs init.

Paid swarm serving (Phase 4 step F):
- swarm/paid.rs gates the iroh-blobs provider through streaming::gate,
  intercepting connect + GET (peer push hard-disabled). Free by default
  (content-download service disabled); denies unpaid peers when enabled;
  fails open on internal error so a payment fault never blocks distribution.
  Wired into IrohProvider::new.

All iroh code behind the iroh-swarm feature; the default build is inert.
Default build clean; --features iroh-swarm: 11/11 swarm tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 04:47:18 -04:00
archipelagoandClaude Opus 4.8 2523c9e3dd feat(dht): Phase 2 — swarm-assist fetch seam, origin always wins
Lands the transport/swarm orchestration layer (the iroh engine attaches
later, behind a flag). The seam is fully exercised today with the origin
HTTP path; with no swarm providers registered the behaviour is byte-for-byte
identical to before.

- swarm/mod.rs: BlobProvider trait + fetch_content_addressed() — tries each
  provider in order, VERIFIES peer-sourced bytes against the content digest
  before accepting (untrusted seeds can't inject tampered bytes), falls back
  to the origin closure if none serve. Returns Swarm|Origin.
- Cargo: iroh-swarm feature (off by default; heavy QUIC dep tree attaches
  here). providers() is empty until enabled → every fetch hits origin.
- update.rs: components with a BLAKE3 digest route through the seam, using
  the existing resumable HTTP downloader as the origin fallback; a swarm hit
  is re-checked against the mandatory SHA-256 manifest gate (re-fetch from
  origin on any disagreement). Components without blake3 take the original
  path untouched.

44/44 swarm/update/content_hash/blobs tests pass (incl. swarm hit/miss,
tampered-bytes-rejected→origin, fall-through ordering).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:38:19 -04:00
archipelagoandClaude Opus 4.8 f0cb91ed76 feat(dht): Phase 1 — BLAKE3 content addressing alongside SHA-256
Adds the iroh-native, range-verifiable hash next to the incumbent SHA-256
so the swarm can later fetch/verify by BLAKE3 with the registry/origin as
fallback. Non-breaking: SHA-256 stays the mandatory gate; BLAKE3 is verified
only when present.

- content_hash.rs: HashAlg + ContentDigest (parse/verify '<alg>:<hex>'
  multihash strings), blake3_hex/sha256_hex; BLAKE3 known-answer test
- update.rs: ComponentUpdate.blake3 (serde-default); verified ALONGSIDE
  SHA-256 in the resumable download loop, re-download on mismatch
- blobs.rs: BlobMeta.blake3 computed on put (on-disk path stays
  SHA-256-keyed for back-compat; advertises the future swarm address)

Drive-by: fix a pre-existing stale test (test_save_and_load_state_roundtrip)
that never wrote the .download-complete marker #26 requires, so load_state's
self-heal cleared update_in_progress. Unrelated to BLAKE3 — surfaced by
running the full update:: suite.

40/40 content_hash/update/blobs tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:05:27 -04:00
archipelagoandClaude Opus 4.8 7e84434ff6 test(update): stage .download-complete marker in roundtrip test
The #26 fix makes has_staged_update require the .download-complete
marker, so the state self-heal treats a marker-less staging dir as a
partial download and clears update_in_progress. The roundtrip test
staged a binary file but not the marker, so it began failing. Write
the marker to simulate a *complete* staged update.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 12:41:18 -04:00
archipelago 981a86cc26 style: cargo fmt (update.rs has_staged_update + #16/#36 changes) 2026-06-16 11:30:51 -04:00
archipelagoandClaude Opus 4.8 82cfc8ccba fix(update): failed download returns to Download, not Install (#26)
A resumable-but-failed download leaves partial component files in update-staging.
has_staged_update() treated ANY staged file as "install-ready", so the state
self-heal kept update_in_progress=true and the UI showed Install instead of
Download (no clean retry).

- update.rs: write a .download-complete marker only after EVERY component
  downloads+verifies; has_staged_update() now checks that marker. Partial/failed
  downloads (no marker) correctly read as not-staged → self-heal clears
  update_in_progress → UI shows Download. Resume still works (partial files kept).
- SystemUpdate.vue: on a genuine download failure, reset downloaded/in_progress
  and re-sync, so the user lands back on Download immediately.

cargo check + vue-tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 10:31:12 -04:00
archipelagoandClaude Opus 4.8 aa9e0f02b7 fix(cloud): pin peer file-card filename + action buttons to the bottom (#11)
Make each peer file card a flex column filling its grid cell (flex flex-col
h-full) and pin the body row (filename + Play/Download) with mt-auto, so cards
with a media preview and cards without line their footers up across the row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 09:27:29 -04:00
archipelagoandClaude Opus 4.8 c800293f1f fix: bitcoin receive, AIUI pointer input, electrs self-heal, OTA timeout
- LND wallet: request correct address type so receive-address generation
  no longer 400s
- AIUI/app session: on-screen pointer can click + type into app content
  (incl. app store search); "open in new tab" opens the phone browser;
  mobile credential modal centered instead of full-height
  (remote-relay.ts, AppSession.vue, AppSessionFrame.vue, AppIconGrid.vue,
  openExternal.ts, WebViewScreen.kt) + remote-relay tests
- health_monitor: electrs auto-recovers from a corrupt index and shows a
  percent/block-height progress screen while reindexing (useElectrsSync.ts)
- update.rs: drop retired tx1138 secondary mirror (one-time migration);
  longer download timeout for slow connections
- CHANGELOG: v1.7.90-alpha notes
- tests/release/run.sh: harness tweaks

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 04:49:32 -04:00
archipelagoandClaude Opus 4.8 c49e8fcacd fix: harden OTA updates, AIUI desktop gap, LND no-proxy
- update.rs: post-OTA probe falls back to http://127.0.0.1/ on connect
  error (nginx binds :80, not :443) so good updates are no longer rolled
  back; recover stuck update_in_progress; avoid ETXTBSY on running binary
- LND: REST client bypasses proxy, GET newaddress p2wkh, wallet
  readiness/unlock after restart
- Dashboard.vue: chat route back to plain h-full (desktop bottom-gap fix)
- vite.config.ts: dev-only /aiui proxy
- tests/release/run.sh: release gate harness (static+frontend+backend)
- CHANGELOG: v1.7.89-alpha notes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 01:23:32 -04:00
archipelago d6f108d818 chore: snapshot release workspace 2026-06-12 03:00:15 -04:00
Dorian 835c525218 chore(release): stage v1.7.55-alpha 2026-05-13 15:09:22 -04:00
archipelago 05e6c2e738 fix: release v1.7.51-alpha install hardening 2026-05-01 05:02:39 -04:00
archipelago be9f9528c3 fix: release v1.7.50-alpha OTA runtime repair 2026-05-01 03:14:07 -04:00
archipelagoandClaude Opus 4.7 4ec6ca98c1 chore: release v1.7.45-alpha
Resilience-validated release. Three full sweeps of the new resilience
harness against .228 confirm no shipstoppers.

Big user-visible:
- Bitcoin RPC auth durably correct via host-rendered nginx.conf bind-mount,
  replaces fragile post-start exec that failed under restricted-cap rootless
  podman ("crun: write cgroup.procs: Permission denied")
- Multi-container stack installs (indeedhub, immich, btcpay, mempool) now
  emit phase events at every boundary so the progress bar advances
- Apps no longer vanish from the dashboard mid-install (absent-scanner skips
  packages in transitional states)
- Indeedhub fresh installs work end-to-end (was 8500+ restart loop): five
  missing env vars (DATABASE_PORT, QUEUE_HOST, QUEUE_PORT,
  S3_PRIVATE_BUCKET_NAME, AES_MASTER_SECRET) added to install code
- Tailscale install fixed: --entrypoint string was being passed as a single
  shell-line arg; switched to custom_args array
- Catalog cleaned of broken entries (dwn, endurain, ollama removed; nextcloud
  restored on docker.io)
- Bitcoin Core update path uses correct image (was looking for nonexistent
  lfg2025/bitcoin:28.4)
- ISO installs now allocate swap on the encrypted data partition

Infra:
- New resilience harness (scripts/resilience/) — black-box state-machine
  tester, every app × every transition. Run before each release.

Sweep #3 final: PASS 107 / FAIL 12 / SKIP 14. The 12 fails are 1 cosmetic
(homeassistant trusted_hosts), 8 harness/timing false-positives, and 3
non-shipstopper tracked items. Down from 23 in baseline sweep #1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:31:45 -04:00
archipelago 8f83b37d51 feat(orchestrator): complete container migration and release hardening 2026-04-28 15:00:58 -04:00
archipelago 4bf35f95e6 test: repair stale test fixtures across identity, mesh, update, wallet, fips
Several tests had drifted from the current production behavior:

- identity_manager: create() already auto-provisions a Nostr key, so the
  explicit create_nostr_key() call failed with "already exists". Rewrite
  the test to assert on record.nostr_npub from create() directly.
- mesh/protocol: test_build_app_start read the app name from frame[4..]
  but the v2 layout is [0:marker][1-2:len][3:cmd][4:version][5..:name].
  test_identity_broadcast_roundtrip expected input DID = output DID but
  the v2 decoder derives DID from the ed25519 pubkey, so the roundtrip
  compares against did_key_from_pubkey_hex(&pub) now.
- mesh/bitcoin_relay: test_build_block_header_announcement asserted
  sig.is_some(), but the builder intentionally emits an unsigned envelope
  to fit the 160-byte LoRa limit; assert sig.is_none(). Also widen
  placeholder hashes to the required 64 hex chars (32 bytes).
- update: load_mirrors() now merges default mirrors post-migration, so
  the roundtrip test must assert the custom mirror survives alongside
  the defaults rather than strict equality.
- wallet/cashu: test_proof_c_as_pubkey used hex that is not on the curve;
  replace with the secp256k1 generator point G so parsing succeeds.
- fips: test_status_reports_no_key_pre_onboarding asserted npub.is_none(),
  which fails on dev boxes where the fips daemon is already running. Keep
  the !key_present assertion and drop the npub one.
2026-04-23 13:02:45 -04:00
archipelago 28e38a36a9 fix(config): auto-purge decommissioned .23 VPS from saved registry/mirror configs
load_registries + load_mirrors normally only ADD missing defaults to
the persisted JSON — explicit removals stick. After retiring the .23
Hetzner VPS we need the opposite: existing nodes have .23 baked into
their saved configs and would spend seconds per install/update timing
out against a dead host until the operator manually removes it via
the Settings UI.

Add a targeted one-time migration in both loaders: if any saved entry
has 23.182.128.160 in its URL, drop it on load and rewrite the file.
This is an exception to the usual "explicit removals stick" rule —
the user never chose to add this mirror, it was a default.

Narrow-scope migration (one hardcoded IP match, no schema version)
because the cost/benefit of a general migration system isn't worth
it for a single decommissioned host. Future retirements can follow
the same pattern.
2026-04-23 08:51:26 -04:00
archipelago d9d5fa65e5 chore: retire .23 VPS mirror, promote .168 OVH to primary
The Hetzner VPS at 23.182.128.160 was decommissioned. Replace it
everywhere with the OVH VPS at 146.59.87.168, which was previously
the tertiary mirror.

  - update.rs: drop DEFAULT_TERTIARY_MIRROR_URL, promote .168 into
    the secondary slot as "Server 1 (OVH)"; tx1138 becomes Server 2.
    Default mirror list shrinks from 3 to 2.
  - container/registry.rs: default RegistryConfig drops .23, promotes
    .168 to Server 1 / priority 0, tx1138 stays Server 2 / priority 10.
  - api/rpc/package/config.rs: trusted-registry allowlist swaps .23
    for .168.
  - api/handler/mod.rs: app-catalog fallback URL uses .168.
  - neode-ui/views/marketplace/marketplaceData.ts: REGISTRY uses .168.
  - scripts/image-versions.sh: ARCHY_REGISTRY_FALLBACK uses .168.
  - image-recipe/build-auto-installer-iso.sh: installer ISO registries
    use .168 (both podman registries.conf and backend registries.json).

Tests updated to assert on the new 2-entry default lists (registry +
mirror). URL-parser fixture tests in update.rs retain .23 strings —
they exercise string-parsing logic, not mirror policy.

Git remotes: dropped `gitea-vps` and the .23 push URL on the `origin`
multi-push alias (not part of this commit — pure working-copy change).
2026-04-23 08:22:32 -04:00
archipelago d1bcf271f9 release(v1.7.41-alpha): post-OTA auto-rollback so a bad release cannot strand the fleet
Closes failure mode FM5 from docs/bulletproof-containers.md: the v1.7.38 +
v1.7.39 rollouts left every affected node on an unreachable UI (nginx 500)
with no recovery path short of SSH. This release adds a self-check
guardrail to the update flow.

What changed:
- apply_update() writes a pending-verify marker with old+new version and
  a 150s deadline immediately before scheduling the service restart.
- verify_pending_update() runs from main.rs startup. If the marker is
  present and within its freshness window, the new binary waits 15s for
  nginx + backend to settle, then probes https://127.0.0.1/ every 5s for
  up to 90s (self-signed certs accepted).
- On any probe success within the window, the marker is cleared and
  nothing else happens.
- On window-exhaust, the new binary:
    1. Moves the broken /opt/archipelago/web-ui to web-ui.failed.<ts>
       (quarantined, not deleted, so we can post-mortem).
    2. Restores web-ui.bak on top of web-ui.
    3. Calls rollback_update() to restore the previous binary.
    4. Updates state.current_version to reflect the rollback.
    5. systemctl --no-block restart archipelago so the OLD binary boots.
- Markers older than 10 minutes are treated as stale and cleared without
  probing, so a crashed-during-startup marker from weeks ago cannot
  spontaneously roll back a healthy node on a later reboot.
- rollback_update() binary copy now goes through host_sudo instead of
  tokio::fs::copy, so it escapes the service's ProtectSystem=strict
  mount namespace. Without this, the rollback silently failed with
  EROFS on /usr/local/bin and orphaned the rollback - the exact
  opposite of what auto-rollback is for.

Tests: 4 new unit tests in update::tests covering marker round-trip,
absent-marker noop, no-panic on verify_pending_update with nothing to
verify, and an invariant assert that the 90s probe window stays below
the 600s stale threshold. All passing.

Side fix: scripts/create-release-manifest.sh was dying with exit 141
(SIGPIPE from tar tvzf pipe head pipe awk) under set -euo pipefail.
Replaced with a single awk NR==1 that doesn't short-circuit the upstream
pipe, so the release-build flow is idempotent again.
2026-04-22 16:14:35 -04:00
DorianandClaude Opus 4.7 b8d084368e release(v1.7.39-alpha): hotfix web-ui perms after OTA (nginx 500) + startup self-heal
v1.7.38 shipped with an OTA bug: the tar-extracted staging dir inherited 700
perms and nginx (www-data) returned 500/403 on every request after the swap.
.116 hit this on rollout; had to chmod by hand to recover.

- update.rs: after extraction, explicitly chmod 755 dirs + 644 files on the
  new staging dir before the mv into place, so nginx can stat/serve them.
- main.rs: self-heal on startup — if /opt/archipelago/web-ui is not
  world-readable, run `sudo chmod -R u=rwX,go=rX` to repair. This is what
  rescues nodes upgrading from v1.7.37/v1.7.38, since their extractor
  (running on the old binary) doesn't have the chmod fix yet — the new
  binary's first boot fixes the mess before nginx serves a single request.

Everything v1.7.38 shipped is still in this release:
- auth.rs auto-heals is_onboarding_complete() from setup_complete +
  password_hash so nodes don't bounce back to /onboarding/intro after
  browser clear / reboot / update
- useOnboarding tri-state: backend-unreachable no longer defaults to intro
- login sounds gated by isFirstInstallPhase() — silent after onboarding,
  typing sounds unaffected
- FIPS app / Nostr Relay / Nostr VPN / Routstr / Penpot removed from
  catalog + frontend + Rust + docker + icons; 15 image versions deleted
  from tx1138, .168, gitea-local
- AIUI baked into release tarball via demo/aiui/
- prebuild hook syncs app-catalog/catalog.json → public/catalog.json

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 13:26:54 -04:00
DorianandClaude Opus 4.7 a7048f6d8e release(v1.7.35-alpha): rootless-netns self-heal + app update button + bitcoin-core 28.4 + Node DID unification
- core/archipelago/src/bootstrap.rs (NEW): embed scripts/container-doctor.sh
  and image-recipe/configs/archipelago-doctor.{service,timer} via
  include_str! and sync to disk + enable the timer on every archipelago
  startup. Idempotent (content-hash compare), dev-box symlink guard keeps
  the git checkout untouched, best-effort (warn-only on failure) so
  bootstrap never blocks server readiness. Wired in main.rs as a
  background tokio task.
- scripts/container-doctor.sh: add fix_rootless_netns_egress(). Detects
  when the rootless-netns has lost its pasta tap (container-to-container
  still works but outbound DNS/TCP fails) via an nsenter probe into
  aardvark-dns; with a two-probe 10s debounce to rule out transients and
  a host-precheck that bails out if the host itself is offline. When the
  rootless-netns is truly broken, does a graceful podman stop --all /
  start --all so pasta + aardvark-dns rebuild the netns from scratch.
  Bitcoin-knots and every other outbound container recover in one cycle.
- core/archipelago/src/update.rs: host_sudo → pub(crate) so bootstrap.rs
  can reuse the existing systemd-run escape hatch.
- apps/bitcoin-core/manifest.yml: bump app version 24.0.0 → 28.4.0 and
  image bitcoin/bitcoin:24.0 → bitcoin/bitcoin:28.4. Resources aligned
  with the real container-specs.sh large-disk tune (4 GiB memory cap,
  cpu_limit: 0 so bitcoind can run -par=auto across every core).
- neode-ui/src/views/apps/AppCard.vue + Apps.vue: add an Update button
  + Updating spinner to every app card that has available-update set.
  Wires through serverStore.updatePackage(id) — the same RPC the detail
  view already calls. common.update / common.updating i18n keys added in
  en.json and es.json.
- core/archipelago/src/identity_manager.rs: add create_from_signing_key()
  that mirrors an existing Ed25519 key as a manager-level identity with
  a deterministic id (`node-<pubkey16>`). Idempotent across restarts,
  gets the hex-SVG master avatar.
- core/archipelago/src/server.rs: the auto-create path on first boot now
  mirrors the node's own signing_key (seed-derived on onboarded installs)
  as a "Node" identity instead of generating a random "Default" keypair.
  Once this ships, the DID on the Web5 DID Status card (via node.did
  RPC), the Node entry on the Identities page (via identity.list), and
  the DID used for peer-to-peer connects (via server_info.pubkey) all
  resolve to the same seed-derived pubkey.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 08:29:56 -04:00
DorianandClaude Opus 4.7 682b93f2d6 release(v1.7.31-alpha): idempotent IndeedHub install + auto-merge default mirrors/registries + 3rd OVH update mirror
- Backend: install.rs registry reachability probe now strips the
  `host[:port]/namespace` suffix before appending `/v2/` (the Docker
  V2 API lives at the host root, not under the namespace) and accepts
  HTTP 405 in addition to 200/401 as "registry daemon alive". This
  fixes false "unreachable" reports on the Test button for Gitea and
  other registries that protect their /v2/ endpoint.
- Backend: stacks.rs install_indeedhub_stack now force-removes any
  leftover indeedhub-* containers and indeedhub-net before creating
  the stack. A partial install (or the old first-boot stub racing the
  installer) used to leave containers around that blocked re-install
  with "name already in use". Re-running the App Store install now
  self-heals.
- Backend: registry.rs load_registries auto-merges any default
  registry URLs missing from the saved config (appended with priority
  max+10+i, persisted). Lets new default mirrors (e.g. Server 3 OVH)
  roll out to existing nodes without manual config edits. Explicit
  removals still stick — URLs absent from disk AND absent from
  defaults stay gone.
- Backend: update.rs adds DEFAULT_TERTIARY_MIRROR_URL at
  http://146.59.87.168:3000/ (Server 3 OVH) to default_mirrors, with
  the same auto-merge-on-load behavior as registries. Test updated
  for 3-mirror default (.160, tx1138, .168).
- Scripts: dropped the first-boot IndeedHub stub (~38 lines in
  first-boot-containers.sh §8b). It predated the proper stack
  installer, raced it, and was the main source of the name-conflict
  mess the stacks.rs cleanup above now also guards against.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 03:26:09 -04:00
DorianandClaude Opus 4.7 2664074210 release(v1.7.28-alpha): reboot progress overlay + VPS default primary
- New reboot progress overlay: full-screen black with the screensaver's
  pulsing ring, rebooting → reconnecting → back-online → stalled stages,
  elapsed counter, auto-reload on health-check success, manual reload
  button at 3 min stall. Mirrors the existing update overlay.
- Ring extracted from Screensaver.vue into a reusable ScreensaverRing
  component so the reboot overlay reuses the same animation.
- default_mirrors() now puts the VPS as Server 1 (primary) and tx1138 as
  Server 2 — new nodes fetch manifests from VPS first; existing nodes
  keep whatever mirror order they've customized.
- What's New entry prepended for v1.7.28-alpha.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 15:06:37 -04:00
DorianandClaude Opus 4.7 9868991900 release(v1.7.27-alpha): mirror transparency — served-by line + one-click test button
- New "Served by {mirror}" line on the System Update page so operators can see
  which mirror actually served the available manifest (vs. which is configured
  primary). Backend threads the served URL through UpdateState.manifest_mirror.
- New update.test-mirror RPC + per-row lightning-bolt button that pings a
  mirror and renders reachable/latency or error inline under the URL.
- UI polish on the mirrors section: Set Primary, Remove, and the new Test
  action are compact icon buttons; add-mirror form moved into a dialog.
- "What's New" block prepended for v1.7.27-alpha.

21/21 update module tests pass. vue-tsc + vite build clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:05:42 -04:00
DorianandClaude Opus 4.7 0d15ca588a release(v1.7.26-alpha): mirror list + origin-relative download URLs
Adds a multi-mirror manifest fetch. `check_for_updates` walks a
configurable list (data_dir/update-mirrors.json) in priority order
and falls through to the next mirror on any HTTP / parse / timeout
failure. Two defaults bake in: Server 1 (git.tx1138.com) and Server 2
(23.182.128.160:3000).

Critical fix: after parsing a manifest, rewrite every component's
`download_url` so its origin matches the manifest URL we fetched.
Before this, the manifest hard-coded absolute URLs pointing at one
specific server — so even when a node fetched the manifest from a
faster mirror, the actual 200MB download went back to the slow
original. Now the faster mirror wins end-to-end.

New RPCs: update.list-mirrors, update.add-mirror, update.remove-mirror,
update.set-primary-mirror. New UI section on the System Update page
for operator management. 5 new unit tests for origin parsing and
manifest rewriting (21/21 green).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 10:09:28 -04:00
DorianandClaude Opus 4.7 40f76013dc release(v1.7.20-alpha): stop auto-apply scheduler killing the service
The 3AM auto-update path called std::process::exit(0) immediately
after apply_update returned. apply_update had already spawned a 2s-
delayed systemctl restart, but exit(0) killed the runtime before that
spawned task could run — and the unit's Restart=on-failure does not
trigger on a clean exit 0, so the service stayed dead until someone
SSH'd in and started it manually (.253 hit this today).

Scheduler now returns from the task without killing the process;
apply_update's existing restart path (same one the UI's Install
Update button uses) brings the new version up cleanly.

Also hardens the ISO CI: the AIUI inclusion step now falls back to
extracting from the newest release tarball if the runner's cached
/opt/archipelago/web-ui/aiui path is missing, so a reprovisioned
runner can't silently ship a frontend tarball without AIUI. The ISO
build step also sanity-checks the binary exists before invoking the
builder.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 04:33:11 -04:00
DorianandClaude Opus 4.7 4e2c6d210b release(v1.7.19-alpha): kill stale available_update + numeric version compare
load_state now drops any stored available_update whenever the running
binary version differs from what's on disk — the old migration only
cleared it when the stale entry happened to match the new version, so
skipping releases (e.g. sideloading 1.7.16 → 1.7.18 without 1.7.17)
left a pointer to an intermediate version as the "update available",
which the UI then offered as a downgrade prompt.

check_for_updates also uses a numeric version comparator so a stale or
cached manifest with an older version can't offer itself as an
update, and 1.7.10 correctly outranks 1.7.9 past the single-digit
patch boundary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 04:04:20 -04:00
DorianandClaude Opus 4.7 7d8ddcccef release(v1.7.18-alpha): transitive peers default Trusted + update-flow logs
Flip transitively-discovered federation peers to Trusted instead of
Observer. Hints are already only ingested from peers we trust and only
peers we trust are re-exported via build_local_state, so the chain of
trust is already vetted end-to-end — making the user promote each
newcomer by hand was friction with no security win.

Backend:
- federation/sync.rs: merge_transitive_peers now inserts TrustLevel::Trusted
  (doc comment updated to explain the transitive-trust rationale)
- update.rs: info! log at download start (version, components, total_bytes,
  staging path), cancel (staging wiped?, marker cleared?), and apply (backup
  path) so journalctl reveals where a stuck update actually is

Frontend:
- SystemUpdate What's New block gets a v1.7.18-alpha entry

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:20:36 -04:00
DorianandClaude Opus 4.7 f853d14421 release(v1.7.17-alpha): cancel download + stall detection
Add Cancel Download button + stall detection so a wedged download can
be recovered instead of leaving the UI stuck on a frozen progress bar.

Backend:
- update.rs: DOWNLOAD_CANCEL AtomicBool + DOWNLOAD_PROGRESS_AT AtomicU64
- download loop checks cancel between chunks and during retry backoff
  (500ms slices instead of one exponential sleep, so Cancel wakes fast)
- cancel_download() wipes staging + clears update_in_progress
- update.status exposes download_progress.stalled (30s no-progress)
- RPC: update.cancel-download + dispatcher entry

Frontend:
- SystemUpdate.vue: Cancel Download button, amber stall styling,
  stalled copy, cancel-download confirm branch in modal
- i18n keys (en + es) for cancel/stall flow
- v1.7.17-alpha What's New block in AccountInfoSection

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 19:10:34 -04:00