Files
archy/docs/bitcoin-multi-version-design.md
T
archipelagoandClaude Opus 5 cc00884b98 docs: repair links left dangling by the operations-doc removal
Untracking the ops docs broke every reference to them. Repoints or removes
those references across README, architecture, ROADMAP, the archive index,
lifecycle TESTING, the hardening plan, and the security docs — pointing at
the issue tracker where a live task list was meant, and dropping the entry
entirely where it only existed to link an internal file.

Also fixes two pre-existing broken links found by validating every relative
link in the tracked docs:
- README linked docs/OPEN_SOURCE_READINESS.md, which never existed
  (underscores vs hyphens).
- reticulum-daemon/README.md linked a local Claude session plan at
  ../../.claude/plans/enchanted-strolling-rocket.md — outside the repo, and
  a path that would have shipped publicly pointing at nothing.

All relative links in tracked markdown now resolve: 0 broken.
audit-secrets.sh still 5/5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 10:10:25 -04:00

17 KiB
Raw Blame History

Bitcoin Multi-Version Support — Design

Status: design (2026-06-22) Goal: let a user choose which version of Bitcoin Core / Bitcoin Knots to install (latest pre-selected, older versions in a dropdown), and later switch versions or opt into auto-update — all manifest/catalog-driven, all served from our signed registry, rootless, with zero data loss across version changes.

See also: docs/registry-manifest-design.md (catalog distribution + signing this builds on), the production test gate (which must be green first), MEMORY → project_decoupled_app_updates, MEMORY → project_manifest_driven_north_star.

Scheduling: this is net-new scope. It lands after the production test gate (tests/lifecycle/run-20x.sh) is green on .228 + .198. The data- preservation invariant (downgrade vs. chainstate) is the highest risk here.


1. Where we are today

Image source / build

Thing Today
apps/bitcoin-core/Dockerfile FROM bitcoin/bitcoin:24.0 — a community image, stale (manifest says 28.4), no project-official Docker image exists
apps/bitcoin-knots/ no Dockerfile:latest is built/pushed by hand
Registry scripts/image-versions.shARCHY_REGISTRY="146.59.87.168:3000/lfg2025"; only BITCOIN_KNOTS_IMAGE=…/bitcoin-knots:latest pinned, no Core pin
Tags in registry one tag per image. No historical versions.

Version pinning

  • apps/bitcoin-core/manifest.yml…/bitcoin:28.4 (pinned).
  • apps/bitcoin-knots/manifest.yml…/bitcoin-knots:latest (floating — a liability for reproducibility and for "switch back to the version I had").
  • core/archipelago/src/container/app_catalog.rs + app-catalog/catalog.json: signed, hourly-fetched, carries version (badge text) + image. catalog_image_override() overrides the manifest image only if same-repo. available_update_for_app() already ignores floating tags for update detection.

Install path

  • prod_orchestrator.rs::install_fresh() resolves the image as manifest image → catalog override → pull. There is no per-install version parameterorchestrator.install(app_id) takes only the id.
  • RPC package.install (api/rpc/package/install.rs) accepts dockerImage / version params but for orchestrator-managed apps (bitcoin-core / bitcoin-knots are allowlisted) it ignores them and lets the orchestrator resolve.
  • Conflict guard (prod_orchestrator.rs ~13061325): core and knots may not run simultaneously. Must be preserved by everything below.

UI

  • Install is one-click, no modal (MarketplaceAppDetails.vue::installApp()).
  • Update badge + "Update to X" already exist (appDetails/AppHeroSection.vue, RPC package.update).
  • No Bitcoin-specific settings panel; all apps share AppSidebar.vue.
  • Per-app config persisted only at install time as containerConfig/var/lib/archipelago/app-configs/<id>.json. No post-install set-config RPC.

2. Source-of-truth decision: official upstream → our registry

We use the official releases as upstream provenance, but nodes only ever pull from our registry. Nodes do not fetch bitcoin.org / GitHub at install time — that would break rootless/offline installs and the signed-registry trust model, and neither project publishes an official Docker image anyway.

Official sources (verified):

Impl Index Per-version asset pattern
Bitcoin Core bitcoincore.org/en/releases · github bitcoin/bitcoin https://bitcoincore.org/bin/bitcoin-core-<ver>/bitcoin-<ver>-x86_64-linux-gnu.tar.gz + SHA256SUMS + SHA256SUMS.asc
Bitcoin Knots github bitcoinknots/bitcoin · bitcoinknots.org/files https://bitcoinknots.org/files/<maj>.x/<ver>/bitcoin-<ver>-x86_64-linux-gnu.tar.gz (<ver> e.g. 29.3.knots20260508)

Both ship signed binary tarballs with multi-builder Guix attestations (SHA256SUMS.asc). The build pipeline verifies these once, at build; our DHT Phase 0 registry signature then carries provenance to the fleet.

Knots version strings embed a build date (29.3.knots20260508). Treat the full string as the tag; surface a friendly 29.3 + date in the UI.


3. Design

Phase 0 — Reproducible, verified image pipeline (prerequisite)

New scripts/build-bitcoin-image.sh <impl> <version> that, per version:

  1. Downloads the official tarball + SHA256SUMS(.asc) (GitHub release assets are an identical mirror → fallback).
  2. Verifies SHA256 and the Guix/builder GPG signatures. Fail closed.
  3. Builds a minimal rootless image: pin a small base, unpack bitcoind/bitcoin-cli. Keep the existing entrypoint probe (command -v bitcoind || find /opt -path '*/bin/bitcoind') so per-version layout differences don't break startup.
  4. Tags + pushes :<version> and updates the default pin (:latest / :28.4-style) to the registry.

Curate, don't mirror everything. Publish a bounded set (proposal: current + last ~3 majors), e.g. Core 31.0, 30.0, 29.3, 28.4, 27.2 and Knots 29.3.knots…, 28.1.knots…, 27.1.knots…. log / document dropped versions — silent truncation reads as "all versions supported" when it isn't.

Also fixes existing debt: replaces the stale community FROM bitcoin/bitcoin:24.0 and gives Knots a real Dockerfile + non-floating tags.

Phase 1 — Version catalog (signed, registry-distributed)

Extend AppCatalogEntry (forward-compatible — no deny_unknown_fields, old nodes ignore it):

"bitcoin-core": {
  "version": "31.0",                 // default / latest (existing field)
  "image": "…/bitcoin:31.0",         // existing
  "versions": [                      // NEW
    { "version": "31.0", "image": "…/bitcoin:31.0", "default": true },
    { "version": "30.0", "image": "…/bitcoin:30.0" },
    { "version": "28.4", "image": "…/bitcoin:28.4", "deprecated": true, "eol": "2026-...." }
  ]
}

Published to releases/app-catalog.json, signed by the existing release-root mechanism. This is the single source of truth the UI reads for "what can I install / switch to," and third-party-registry apps inherit the capability for free. version/image stay as the default for back-compat.

Phase 2 — Install-time version selection

  • Orchestrator: add install_with_image(app_id, Option<image_tag>) (or an optional arg on install). When a tag is supplied, validate same-repo against the manifest (reuse image_without_registry_or_tag()), then override in install_fresh(). Default path unchanged. Preserve the core/knots conflict guard.
  • RPC: thread the selected version/image from package.install into the orchestrator for the allowlisted apps (the param is already received — just not forwarded).
  • UI: the first install modal in the app — latest pre-selected, dropdown of versions[], deprecated/EOL badges on old entries. On confirm, pass the chosen version to package.install.

Phase 3 — In-app version switch + auto-update toggle

  • UI: a Bitcoin "Version & Updates" card (conditional in AppSidebar.vue for bitcoin-core / bitcoin-knots): current version, a switch dropdown, and an auto-update-to-latest toggle.
  • Switch = controlled re-pull/recreate reusing the package.update machinery but targeting an arbitrary (incl. older) tag → effectively package.set-version.
  • Persistence: new package.set-config RPC writing the existing app-configs/<id>.json ({ pinnedVersion, autoUpdate }).
  • Auto-update: the existing hourly catalog check, when autoUpdate:true, triggers package.update to the catalog default. A pinned version suppresses the update badge.

4. Invariants & safety rails

  • Rootless only. Pipeline images and run path stay rootless; no Docker-socket, no privileged.
  • No data loss across version change. Preserve /var/lib/archipelago/bitcoin, secrets (bitcoin-rpc-password, …-rpcauth), ports, and the adoption container name on every install / switch / update.
  • ⚠️ Downgrade vs. chainstate (highest risk). Bitcoin Core refuses to start on a chainstate written by a newer version unless reindexed (expensive, or data loss on a pruned node). The UI must warn loudly on downgrade; the orchestrator should gate/confirm it and never silently wipe. Pruned nodes can't simply -reindex.
  • Core ⇄ Knots switch stays governed by the existing conflict guard; treat an impl switch as distinct from a version switch.
  • Floating tags (latest) are never advertised as a selectable "version" and never counted as an available update (already handled by available_update_for_app).
  • Verify on a real node (.228 then .198) and pass run-20x before any tag.

5. Files / seams (no code yet)

Concern File
Image build/push new scripts/build-bitcoin-image.sh; apps/bitcoin-core/Dockerfile; new apps/bitcoin-knots/Dockerfile; scripts/image-versions.sh
Catalog schema core/archipelago/src/container/app_catalog.rs; releases/app-catalog.json (+ app-catalog/catalog.json)
Install override core/archipelago/src/container/prod_orchestrator.rs (install / install_fresh); api/rpc/package/install.rs; api/rpc/dispatcher.rs
Switch / set-config RPC api/rpc/package/update.rs; new package.set-config handler; app-configs/<id>.json
Install modal neode-ui/src/views/MarketplaceAppDetails.vue; new …/marketplace/AppInstallModal.vue
Version & Updates card neode-ui/src/views/appDetails/AppSidebar.vue; neode-ui/src/api/rpc-client.ts; neode-ui/src/types/api.ts

6. Open questions

  1. Curated version set — how many majors back do we host, and storage budget on the registry?
  2. Multi-arch — fleet is x86_64 today; do any nodes need arm64 images?
  3. Pruned-node downgrade policy — block outright, or allow with an explicit "this will require re-sync / may lose pruned data" confirmation?
  4. Auto-update default — off (opt-in) for a consensus-critical app like Bitcoin? (Recommended: off, explicit opt-in.)
  5. Knots date-suffix UX — how to display 29.3.knots20260508 cleanly.

Sources