feat(apps): package Cuprate, an alternative Monero node #141

Closed
ssmithx wants to merge 0 commits from cuprate-archyapp into main
Collaborator

Packages Cuprate (an alternative Monero node implementation in Rust) as an Archipelago app.

What's in it

  • apps/cuprate/manifest.yml publishes Monero P2P on 18183 and restricted RPC on host 18090 (mapped to Cuprate's canonical in-container 18089). 18089 remains assigned to the live Penpot service.
  • Unrestricted RPC is not published. Cuprate has no RPC authentication, so leaving this full-control surface container-loopback-only prevents any host or peer-container access.
  • neode-ui/public/assets/img/app-icons/cuprate.svg is normalized with scripts/normalize-app-icon.py.
  • core/container/src/manifest.rs updates the reviewed unauthenticated-port canary for Cuprate P2P and restricted RPC.

Verification

  • bash scripts/validate-app-manifest.sh apps/cuprate/manifest.yml passes with no port conflicts.
  • cargo test -p archipelago-container passes: 76 tests.
  • Pulled and inspected source.archipelago-foundation.org/lfg2025/cuprate:0.1.0-preview-18-g618ff14 successfully; it is available to fresh nodes, runs as 1000:1000, and has /usr/local/bin/cuprated as its entrypoint.
  • The upstream image/config were previously smoke-tested; this node cannot run the full install because only 191 GiB is free while Cuprate correctly declares 300 GiB storage required.

Merge status

The former registry-image blocker and the Penpot 18089 conflict are resolved. The remaining release gate is a full lifecycle test on a node with at least 300 GiB available storage, per tests/lifecycle/TESTING.md.

Packages [Cuprate](https://github.com/Cuprate/cuprate) (an alternative Monero node implementation in Rust) as an Archipelago app. ## What's in it - `apps/cuprate/manifest.yml` publishes Monero P2P on `18183` and restricted RPC on host `18090` (mapped to Cuprate's canonical in-container `18089`). `18089` remains assigned to the live Penpot service. - Unrestricted RPC is not published. Cuprate has no RPC authentication, so leaving this full-control surface container-loopback-only prevents any host or peer-container access. - `neode-ui/public/assets/img/app-icons/cuprate.svg` is normalized with `scripts/normalize-app-icon.py`. - `core/container/src/manifest.rs` updates the reviewed unauthenticated-port canary for Cuprate P2P and restricted RPC. ## Verification - `bash scripts/validate-app-manifest.sh apps/cuprate/manifest.yml` passes with no port conflicts. - `cargo test -p archipelago-container` passes: 76 tests. - Pulled and inspected `source.archipelago-foundation.org/lfg2025/cuprate:0.1.0-preview-18-g618ff14` successfully; it is available to fresh nodes, runs as `1000:1000`, and has `/usr/local/bin/cuprated` as its entrypoint. - The upstream image/config were previously smoke-tested; this node cannot run the full install because only 191 GiB is free while Cuprate correctly declares 300 GiB storage required. ## Merge status The former registry-image blocker and the Penpot `18089` conflict are resolved. The remaining release gate is a full lifecycle test on a node with at least 300 GiB available storage, per `tests/lifecycle/TESTING.md`.
ssmithx added 1 commit 2026-08-21 14:48:57 +00:00
Full-node daemon: P2P + Monero's own restricted RPC (the safe-for-public
subset wallets use as a "remote node") are auth:none like bitcoin/electrumx's
equivalents; unrestricted RPC (full node control) stays gated auth:local.
readonly_root works cleanly since the upstream image is FROM scratch with
ownership fixed at build time — no runtime chown/setuid needed, unlike
bitcoin-knots/core.

Verified locally end-to-end before committing: built the upstream Dockerfile,
confirmed the generated Cuprated.toml against `cuprated --generate-config`/
`--dry-run`, and ran the real image with the manifest's exact ports/volumes —
including discovering that cuprated's own 127.0.0.1-default RPC bind is
unreachable through a published host port and needs to bind 0.0.0.0
internally with ports[].bind:127.0.0.1 doing the actual restriction, the
same pattern bitcoin-knots' RPC port already uses in this repo.

Bumps the unauthenticated_ports_are_all_accounted_for canary (26 -> 28) for
cuprate's two auth:none ports, per that test's own review-before-updating
contract.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
chaum added 1 commit 2026-08-22 07:11:30 +00:00
The manifest bound cuprated's unrestricted RPC (full node control) to
0.0.0.0 inside the container with
i_know_what_im_doing_allow_public_unrestricted_rpc = true, relying on
ports[].bind: 127.0.0.1 to keep it private. That only restricts the HOST
side. Verified live on archi-dev-box 2026-08-22: a peer container got a
valid unauthenticated get_info off container port 18081 — and still did
after cuprate was moved to its own network, because podman bridges route
to each other unless created with --opt isolate=true, which the
orchestrator's auto-create does not pass. Every app on the node could
therefore drive full node control with no credential.

The PR justified this as the pattern bitcoin-knots already uses, but
knots writes rpcuser/rpcpassword from generated secrets, so a 0.0.0.0
bind there still is not control without credentials. cuprated has no RPC
authentication at all, so the two are not equivalent.

Unrestricted RPC is now left at cuprated's own default — container
loopback only, published nowhere, reachable by nothing — which is what
upstream intends by refusing a non-local bind without an explicit
override. Restricted RPC (the safe-for-public subset wallets use) and p2p
are unchanged, and health_check moves to 18089 since 18184 is gone.

Re-verified after the change: peer container gets connection refused on
18081 (exit 7), restricted RPC and the health endpoint still answer, the
node still syncs, validator APPROVED, 76/76 container tests pass
including the unauthenticated-port canary (still 28 — an auth: local
port was removed, not an auth: none one).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
chaum added 1 commit 2026-08-23 05:43:11 +00:00
Collaborator

Live lifecycle result (2026-08-23)

Tested on archi-dev-box after freeing 1.08 TiB.

Passed:

  • production rootless Quadlet install
  • generated Cuprated.toml and UID mapping
  • read-only root, drop-all capabilities, no-new-privileges
  • P2P host port 18183 and restricted RPC host port 18090
  • unrestricted RPC remained container-loopback-only
  • active mainnet block sync with real peers
  • stop, start, restart
  • preserve-data uninstall and reinstall
  • Archipelago daemon restart survival with unchanged container ID

Blocking failure:

  • delete-data uninstall removed /var/lib/archipelago/cuprate and reported success, but left the restartable Cuprate Quadlet/legacy state behind. This recreated Cuprate against missing/default configuration.
  • Root cause found in core/archipelago/src/api/rpc/package/runtime.rs: every orchestrator.remove(...) error is discarded before generic container/data cleanup.
  • A local patch now makes declarative teardown a hard precondition and passes the exact regression test plus archipelago-container 76/76, but the full clean rerun exposed stale installed-state recovery from the pre-fix failure. The node was restored cleanly with Cuprate absent.

PR remains blocked until the uninstall fix is committed and a fresh-state install/delete-data-uninstall/reinstall cycle passes.

## Live lifecycle result (2026-08-23) Tested on `archi-dev-box` after freeing 1.08 TiB. Passed: - production rootless Quadlet install - generated `Cuprated.toml` and UID mapping - read-only root, drop-all capabilities, no-new-privileges - P2P host port `18183` and restricted RPC host port `18090` - unrestricted RPC remained container-loopback-only - active mainnet block sync with real peers - stop, start, restart - preserve-data uninstall and reinstall - Archipelago daemon restart survival with unchanged container ID Blocking failure: - delete-data uninstall removed `/var/lib/archipelago/cuprate` and reported success, but left the restartable Cuprate Quadlet/legacy state behind. This recreated Cuprate against missing/default configuration. - Root cause found in `core/archipelago/src/api/rpc/package/runtime.rs`: every `orchestrator.remove(...)` error is discarded before generic container/data cleanup. - A local patch now makes declarative teardown a hard precondition and passes the exact regression test plus `archipelago-container` 76/76, but the full clean rerun exposed stale installed-state recovery from the pre-fix failure. The node was restored cleanly with Cuprate absent. PR remains blocked until the uninstall fix is committed and a fresh-state install/delete-data-uninstall/reinstall cycle passes.
chaum added 1 commit 2026-08-23 11:59:43 +00:00
Collaborator

Clean lifecycle rerun: PASS (2026-08-23)

Retested PR tip c188d9de from a genuinely clean state on archi-dev-box with 1.08 TiB free.

Passed:

  • staged PR manifest loaded by production orchestrator
  • rootless Quadlet install
  • restricted RPC 18090 and P2P 18183
  • UID 1000:1000, read-only root, no-new-privileges, dropped capabilities
  • generated config ownership and content
  • delete-data uninstall removed the Quadlet file/unit, container, data directory, and installed marker; set the user-uninstalled marker
  • clean reinstall cleared user-uninstalled, restored installed marker, and ran as Quadlet
  • active mainnet synchronization advanced height 48697 -> 87993 in 60 seconds
  • Archipelago backend remained healthy

The uninstall safety fix in c188d9de closes the blocker found in the first live run. Cuprate is intentionally left installed and syncing for continued soak observation.

## Clean lifecycle rerun: PASS (2026-08-23) Retested PR tip `c188d9de` from a genuinely clean state on `archi-dev-box` with 1.08 TiB free. Passed: - staged PR manifest loaded by production orchestrator - rootless Quadlet install - restricted RPC `18090` and P2P `18183` - UID `1000:1000`, read-only root, no-new-privileges, dropped capabilities - generated config ownership and content - delete-data uninstall removed the Quadlet file/unit, container, data directory, and installed marker; set the user-uninstalled marker - clean reinstall cleared user-uninstalled, restored installed marker, and ran as Quadlet - active mainnet synchronization advanced height `48697 -> 87993` in 60 seconds - Archipelago backend remained healthy The uninstall safety fix in `c188d9de` closes the blocker found in the first live run. Cuprate is intentionally left installed and syncing for continued soak observation.
Collaborator

Merged to main as 698e915d (merge of the branch's four commits d6b48ce0 / f1b5d2d2 / 37a82fd2 / c188d9de; no conflicts — files disjoint from the current release line).

Verification on merge:

  • cargo fmt clean
  • Backend suite for -p archipelago -p archipelago-container: 1560 passed / 0 failed (includes the declarative-uninstall abort test from c188d9de)
  • Registry image confirmed present: source.archipelago-foundation.org/lfg2025/cuprate:0.1.0-preview-18-g618ff14 (manifest verified via the v2 API)

Still pending before the app reaches catalog-covered nodes (tracked under #69):

  1. Regenerate + re-sign the app catalog with the cuprate entry (release-root signing ceremony)
  2. Production lifecycle gate on a real node (required after the orchestrator change in c188d9de)

Closing the PR — the remaining work is release-process, not code.

Merged to main as `698e915d` (merge of the branch's four commits d6b48ce0 / f1b5d2d2 / 37a82fd2 / c188d9de; no conflicts — files disjoint from the current release line). Verification on merge: - cargo fmt clean - Backend suite for `-p archipelago -p archipelago-container`: 1560 passed / 0 failed (includes the declarative-uninstall abort test from c188d9de) - Registry image confirmed present: source.archipelago-foundation.org/lfg2025/cuprate:0.1.0-preview-18-g618ff14 (manifest verified via the v2 API) Still pending before the app reaches catalog-covered nodes (tracked under #69): 1. Regenerate + re-sign the app catalog with the cuprate entry (release-root signing ceremony) 2. Production lifecycle gate on a real node (required after the orchestrator change in c188d9de) Closing the PR — the remaining work is release-process, not code.
chaum closed this pull request 2026-08-30 18:45:43 +00:00

Pull request closed

Please reopen this pull request to perform a merge.
Sign in to join this conversation.