A bitcoin-node privacy machine in the template #14

Closed
opened 2026-10-11 15:52:53 +01:00 by vnprc-agent · 4 comments

A pruned Bitcoin Core node as a template privacy machine: Tor-only after its first sync, on a data disk, running a verified binary, so a fork gets a node by declaring one.

Primary goals:

  • A bitcoin-node privacy profile — bitcoind with prune, blockfilterindex, a data disk at the inventory's data_disk path, dbcache and the read/write caps sized for a 4-8 GB guest; Tor-only egress enforced in nftables, not in bitcoin.conf.
  • The one-way clearnet exception — a first sync over clearnet, then a marker file that disables the exception for every later boot; the chain stays empty while output still jumps to it, so the node is Tor-only by topology.
  • A verified binary — the node runs a Bitcoin Core build verified against the release attestations (a Guix build pinned as a flake input); the template never compiles Bitcoin Core.
  • Facts in one file — paths, unit names, ports and the stop timeout in a facts.nix the profile and the operator command both read, so nothing is spelled twice.

Current state and specifics

A private deployment runs this profile today (synced, Tor-only across reboots, every live witness passed). The public template has data_disk support in inventory and provision-vm, and the privacy archetype; the profile, its facts file and the clearnet switch are what move. Sizing numbers from the private node at height 970k, pruned with the filter index: chainstate 19 GB, filter index 13 GB, blocks under 1 GB; the data disk is sized from those, never from the block count.

Scope

In: the profile directory under hosts/privacy/bitcoin-node/, facts.nix, the inventory example entry with its data disk. Out: the operator command (allod/nexus), any wallet, any clearnet mode as a standing option.

A pruned Bitcoin Core node as a template privacy machine: Tor-only after its first sync, on a data disk, running a verified binary, so a fork gets a node by declaring one. Primary goals: - **A `bitcoin-node` privacy profile** — `bitcoind` with `prune`, `blockfilterindex`, a data disk at the inventory's `data_disk` path, `dbcache` and the read/write caps sized for a 4-8 GB guest; Tor-only egress enforced in nftables, not in `bitcoin.conf`. - **The one-way clearnet exception** — a first sync over clearnet, then a marker file that disables the exception for every later boot; the chain stays empty while `output` still jumps to it, so the node is Tor-only by topology. - **A verified binary** — the node runs a Bitcoin Core build verified against the release attestations (a Guix build pinned as a flake input); the template never compiles Bitcoin Core. - **Facts in one file** — paths, unit names, ports and the stop timeout in a `facts.nix` the profile and the operator command both read, so nothing is spelled twice. ### Current state and specifics A private deployment runs this profile today (synced, Tor-only across reboots, every live witness passed). The public template has `data_disk` support in inventory and `provision-vm`, and the `privacy` archetype; the profile, its facts file and the clearnet switch are what move. Sizing numbers from the private node at height 970k, pruned with the filter index: chainstate 19 GB, filter index 13 GB, blocks under 1 GB; the data disk is sized from those, never from the block count. ### Scope In: the profile directory under `hosts/privacy/bitcoin-node/`, `facts.nix`, the inventory example entry with its data disk. Out: the operator command (`allod/nexus`), any wallet, any clearnet mode as a standing option.
Author

Implementation is ready on branch agent/bitcoin-node-profile, one commit, relayed to the owner for the pull request. Proposed PR body follows.


This adds a bitcoin-node privacy machine definition to the template: a pruned Bitcoin Core node that syncs once over clearnet, then switches itself to Tor-only and stays there across reboots, with its chain on a data disk. Nothing changes for any machine the template already builds: no inventory entry selects the new definition yet, so the example fleet rebuilds to the same closures (the deploy gate with this branch substituted passes every step unchanged). What proves it is the NixOS VM test this change adds, which boots the real profile with a stand-in bitcoind and checks the nftables topology, the one-way switch, the marker's ownership, and the data-volume-absent case. If it is wrong, revert the commit: it creates no state anywhere.

To use it, a deployment declares a bitcoin-node inventory entry with a data_disk and a privacy identity in secrets; the README now shows the entry in the template's addressing. The template's own inventory and secrets do not get one in this change, so the example fleet does not grow a machine until a follow-up adds both.

What moves

  • hosts/privacy/bitcoin-node/configuration.nix, facts.nix, home.nix: the profile that runs on a private deployment today (synced, Tor-only across reboots), carried over unchanged except the fix round below.
  • checks/bitcoin-node.nix, wired as checks.<system>.bitcoin-node-machine on x86_64-linux and aarch64-linux: the VM test, eight subtests.
  • flake.nix: the privacy.bitcoin-node definition. The flake keeps nixpkgs as its only input.
  • README.md: a Bitcoin node section with the inventory example, the data-disk sizing rule, and the by-hand bitcoin-cli line.

Decisions a reviewer should know

  • The binary is stock pkgs.bitcoind, not a Guix-build flake input. The issue names a bitcoind-gunix input. The deployment that passed every live witness runs nixpkgs' cached binary and never builds Bitcoin Core locally, and this flake's contract is nixpkgs alone, so that is what is promoted. The rule that travels is the comment on bitcoindPkg: never override it into a source build. A verified Guix binary is a separate change if the owner wants one.
  • facts.nix is the one file the profile and the operator command (allod bitcoin, allod/nexus#93) both read. An assertion now fails evaluation if nodeUser drifts from the account the nixpkgs module creates.

Fix round (one cold cross-family review, gpt-5.6-sol)

Four findings taken: the documented bitcoin-cli line lacked -datadir and could not find the RPC cookie; the data-volume top is now forced to root:root 0755 by the data-dir unit on every start, so a preformatted volume cannot leave the marker deletable by the node's account (the existing test assertion witnesses it); the account name is read from facts.nodeUser everywhere and asserted against the module; the VM check is exported only on the two systems the NixOS test driver builds on, since nix flake show --all-systems failed on powerpc64le-linux before. Four comment findings discarded as traps that stay.

Risk

R3: a privacy boundary (Tor-only egress) and persistent state on a data disk. Residual after validation: the VM test uses a stand-in daemon, so the real bitcoind's behaviour against the config is witnessed by the private deployment, not here. Rollback is a revert.

Validation

Branch agent/bitcoin-node-profile, one commit (a11c209). Run on the integrator's machine against that commit unless noted:

  • nix flake check -L .: passes. The VM test bitcoin-node-machine built fresh after the fix round (test script finished in 449.13s), all eight subtests: marker absent / switch with no node / switch order / marker present / idempotent switch / after a reboot / each predicate alone / data volume absent. The README-only amend after it reuses the same test derivation.
  • nix flake show --all-systems: exit 0 (failed on powerpc64le-linux before the fix round).
  • allod check --override-input profiles <this worktree> in the template deploy flake: All 7 steps passed (checks: 1 built), with profiles resolved to a11c209. Every existing machine still evaluates; none selects the new definition.
  • nix eval .#lib.profileDefinitions --json | jq '.privacy | keys': ["bitcoin-node","privacy-1"].
  • Leak scan over the diff for private hostnames, paths, orgs and address ranges: empty.
  • Cold review: one cross-family pass (gpt-5.6-sol via pi), eight findings, four taken, four discarded with the reason stated above.

Closes allod/profiles#14.

Implementation is ready on branch `agent/bitcoin-node-profile`, one commit, relayed to the owner for the pull request. Proposed PR body follows. --- This adds a `bitcoin-node` privacy machine definition to the template: a pruned Bitcoin Core node that syncs once over clearnet, then switches itself to Tor-only and stays there across reboots, with its chain on a data disk. Nothing changes for any machine the template already builds: no inventory entry selects the new definition yet, so the example fleet rebuilds to the same closures (the deploy gate with this branch substituted passes every step unchanged). What proves it is the NixOS VM test this change adds, which boots the real profile with a stand-in `bitcoind` and checks the nftables topology, the one-way switch, the marker's ownership, and the data-volume-absent case. If it is wrong, revert the commit: it creates no state anywhere. To use it, a deployment declares a `bitcoin-node` inventory entry with a `data_disk` and a privacy identity in `secrets`; the README now shows the entry in the template's addressing. The template's own inventory and secrets do not get one in this change, so the example fleet does not grow a machine until a follow-up adds both. ## What moves - `hosts/privacy/bitcoin-node/configuration.nix`, `facts.nix`, `home.nix`: the profile that runs on a private deployment today (synced, Tor-only across reboots), carried over unchanged except the fix round below. - `checks/bitcoin-node.nix`, wired as `checks.<system>.bitcoin-node-machine` on `x86_64-linux` and `aarch64-linux`: the VM test, eight subtests. - `flake.nix`: the `privacy.bitcoin-node` definition. The flake keeps `nixpkgs` as its only input. - `README.md`: a Bitcoin node section with the inventory example, the data-disk sizing rule, and the by-hand `bitcoin-cli` line. ## Decisions a reviewer should know - **The binary is stock `pkgs.bitcoind`, not a Guix-build flake input.** The issue names a `bitcoind-gunix` input. The deployment that passed every live witness runs nixpkgs' cached binary and never builds Bitcoin Core locally, and this flake's contract is `nixpkgs` alone, so that is what is promoted. The rule that travels is the comment on `bitcoindPkg`: never override it into a source build. A verified Guix binary is a separate change if the owner wants one. - `facts.nix` is the one file the profile and the operator command (`allod bitcoin`, allod/nexus#93) both read. An assertion now fails evaluation if `nodeUser` drifts from the account the nixpkgs module creates. ## Fix round (one cold cross-family review, gpt-5.6-sol) Four findings taken: the documented `bitcoin-cli` line lacked `-datadir` and could not find the RPC cookie; the data-volume top is now forced to `root:root 0755` by the data-dir unit on every start, so a preformatted volume cannot leave the marker deletable by the node's account (the existing test assertion witnesses it); the account name is read from `facts.nodeUser` everywhere and asserted against the module; the VM check is exported only on the two systems the NixOS test driver builds on, since `nix flake show --all-systems` failed on `powerpc64le-linux` before. Four comment findings discarded as traps that stay. ## Risk R3: a privacy boundary (Tor-only egress) and persistent state on a data disk. Residual after validation: the VM test uses a stand-in daemon, so the real `bitcoind`'s behaviour against the config is witnessed by the private deployment, not here. Rollback is a revert. ## Validation Branch `agent/bitcoin-node-profile`, one commit (`a11c209`). Run on the integrator's machine against that commit unless noted: - `nix flake check -L .`: passes. The VM test `bitcoin-node-machine` built fresh after the fix round (`test script finished in 449.13s`), all eight subtests: marker absent / switch with no node / switch order / marker present / idempotent switch / after a reboot / each predicate alone / data volume absent. The README-only amend after it reuses the same test derivation. - `nix flake show --all-systems`: exit 0 (failed on `powerpc64le-linux` before the fix round). - `allod check --override-input profiles <this worktree>` in the template deploy flake: `All 7 steps passed (checks: 1 built)`, with `profiles` resolved to `a11c209`. Every existing machine still evaluates; none selects the new definition. - `nix eval .#lib.profileDefinitions --json | jq '.privacy | keys'`: `["bitcoin-node","privacy-1"]`. - Leak scan over the diff for private hostnames, paths, orgs and address ranges: empty. - Cold review: one cross-family pass (gpt-5.6-sol via pi), eight findings, four taken, four discarded with the reason stated above. Closes allod/profiles#14.
Author

Updated: the template now ships the machine itself (allod/inventory#26, allod/secrets#45, tracked in allod/strategy#68), so the body above this comment is superseded. Branch agent/bitcoin-node-profile at d03e92a, one commit, relayed to the owner. Proposed PR body follows.


This adds a bitcoin-node privacy machine definition to the template: a pruned Bitcoin Core node that syncs once over clearnet, then switches itself to Tor-only and stays there across reboots, with its chain on a data disk. Nothing changes for any machine the template already builds; the machine itself arrives in the same lock bump through allod/inventory#26 and allod/secrets#45 (allod/strategy#68 tracks the four), and the framework's machine-key assertion refuses the data entries without this definition, so the three land together. What proves it is the NixOS VM test this change adds, which boots the real profile with a stand-in bitcoind and checks the nftables topology, the one-way switch, the marker's ownership, and the data-volume-absent case. If it is wrong, revert the commit: it creates no state anywhere.

On a fresh install provision-vm bitcoin-node is then the whole of it. A fork that will never host a node leaves it unprovisioned; a switch to take a template machine out of the build entirely is allod/strategy#67.

What moves

  • hosts/privacy/bitcoin-node/configuration.nix, facts.nix, home.nix: the profile that runs on a private deployment today (synced, Tor-only across reboots), carried over unchanged except the fix round below.
  • checks/bitcoin-node.nix, wired as checks.<system>.bitcoin-node-machine on x86_64-linux and aarch64-linux: the VM test, eight subtests.
  • flake.nix: the privacy.bitcoin-node definition. The flake keeps nixpkgs as its only input.
  • README.md: a Bitcoin node section: what the template declares for it, the data-disk sizing rule, and the by-hand bitcoin-cli line.

Decisions a reviewer should know

  • The binary is stock pkgs.bitcoind, not a Guix-build flake input. The issue names a bitcoind-gunix input. The deployment that passed every live witness runs nixpkgs' cached binary and never builds Bitcoin Core locally, and this flake's contract is nixpkgs alone, so that is what is promoted. The rule that travels is the comment on bitcoindPkg: never override it into a source build. A verified Guix binary is a separate change if the owner wants one.
  • facts.nix is the one file the profile and the operator command (allod bitcoin, allod/nexus#93) both read. An assertion now fails evaluation if nodeUser drifts from the account the nixpkgs module creates.

Fix round (one cold cross-family review, gpt-5.6-sol)

Four findings taken: the documented bitcoin-cli line lacked -datadir and could not find the RPC cookie; the data-volume top is now forced to root:root 0755 by the data-dir unit on every start, so a preformatted volume cannot leave the marker deletable by the node's account (the existing test assertion witnesses it); the account name is read from facts.nodeUser everywhere and asserted against the module; the VM check is exported only on the two systems the NixOS test driver builds on, since nix flake show --all-systems failed on powerpc64le-linux before. Four comment findings discarded as traps that stay.

Risk

R3: a privacy boundary (Tor-only egress) and persistent state on a data disk. Residual after validation: the VM test uses a stand-in daemon, so the real bitcoind's behaviour against the config is witnessed by the private deployment, not here. Rollback is a revert.

Validation

Branch agent/bitcoin-node-profile, one commit (d03e92a). Run on the integrator's machine against that commit unless noted:

  • nix flake check -L .: passes. The VM test bitcoin-node-machine built fresh after the fix round (test script finished in 449.13s), all eight subtests: marker absent / switch with no node / switch order / marker present / idempotent switch / after a reboot / each predicate alone / data volume absent. The README-only amend after it reuses the same test derivation.
  • nix flake show --all-systems: exit 0 (failed on powerpc64le-linux before the fix round).
  • allod check --override-input profiles <this worktree> in the template deploy flake: All 7 steps passed (checks: 1 built), with profiles resolved to this branch. Every existing machine still evaluates. The full composition with the inventory and secrets branches substituted as well is recorded on allod/strategy#68.
  • nix eval .#lib.profileDefinitions --json | jq '.privacy | keys': ["bitcoin-node","privacy-1"].
  • Leak scan over the diff for private hostnames, paths, orgs and address ranges: empty.
  • Cold review: one cross-family pass (gpt-5.6-sol via pi), eight findings, four taken, four discarded with the reason stated above.

Closes allod/profiles#14.

Updated: the template now ships the machine itself (allod/inventory#26, allod/secrets#45, tracked in allod/strategy#68), so the body above this comment is superseded. Branch `agent/bitcoin-node-profile` at `d03e92a`, one commit, relayed to the owner. Proposed PR body follows. --- This adds a `bitcoin-node` privacy machine definition to the template: a pruned Bitcoin Core node that syncs once over clearnet, then switches itself to Tor-only and stays there across reboots, with its chain on a data disk. Nothing changes for any machine the template already builds; the machine itself arrives in the same lock bump through allod/inventory#26 and allod/secrets#45 (allod/strategy#68 tracks the four), and the framework's machine-key assertion refuses the data entries without this definition, so the three land together. What proves it is the NixOS VM test this change adds, which boots the real profile with a stand-in `bitcoind` and checks the nftables topology, the one-way switch, the marker's ownership, and the data-volume-absent case. If it is wrong, revert the commit: it creates no state anywhere. On a fresh install `provision-vm bitcoin-node` is then the whole of it. A fork that will never host a node leaves it unprovisioned; a switch to take a template machine out of the build entirely is allod/strategy#67. ## What moves - `hosts/privacy/bitcoin-node/configuration.nix`, `facts.nix`, `home.nix`: the profile that runs on a private deployment today (synced, Tor-only across reboots), carried over unchanged except the fix round below. - `checks/bitcoin-node.nix`, wired as `checks.<system>.bitcoin-node-machine` on `x86_64-linux` and `aarch64-linux`: the VM test, eight subtests. - `flake.nix`: the `privacy.bitcoin-node` definition. The flake keeps `nixpkgs` as its only input. - `README.md`: a Bitcoin node section: what the template declares for it, the data-disk sizing rule, and the by-hand `bitcoin-cli` line. ## Decisions a reviewer should know - **The binary is stock `pkgs.bitcoind`, not a Guix-build flake input.** The issue names a `bitcoind-gunix` input. The deployment that passed every live witness runs nixpkgs' cached binary and never builds Bitcoin Core locally, and this flake's contract is `nixpkgs` alone, so that is what is promoted. The rule that travels is the comment on `bitcoindPkg`: never override it into a source build. A verified Guix binary is a separate change if the owner wants one. - `facts.nix` is the one file the profile and the operator command (`allod bitcoin`, allod/nexus#93) both read. An assertion now fails evaluation if `nodeUser` drifts from the account the nixpkgs module creates. ## Fix round (one cold cross-family review, gpt-5.6-sol) Four findings taken: the documented `bitcoin-cli` line lacked `-datadir` and could not find the RPC cookie; the data-volume top is now forced to `root:root 0755` by the data-dir unit on every start, so a preformatted volume cannot leave the marker deletable by the node's account (the existing test assertion witnesses it); the account name is read from `facts.nodeUser` everywhere and asserted against the module; the VM check is exported only on the two systems the NixOS test driver builds on, since `nix flake show --all-systems` failed on `powerpc64le-linux` before. Four comment findings discarded as traps that stay. ## Risk R3: a privacy boundary (Tor-only egress) and persistent state on a data disk. Residual after validation: the VM test uses a stand-in daemon, so the real `bitcoind`'s behaviour against the config is witnessed by the private deployment, not here. Rollback is a revert. ## Validation Branch `agent/bitcoin-node-profile`, one commit (`d03e92a`). Run on the integrator's machine against that commit unless noted: - `nix flake check -L .`: passes. The VM test `bitcoin-node-machine` built fresh after the fix round (`test script finished in 449.13s`), all eight subtests: marker absent / switch with no node / switch order / marker present / idempotent switch / after a reboot / each predicate alone / data volume absent. The README-only amend after it reuses the same test derivation. - `nix flake show --all-systems`: exit 0 (failed on `powerpc64le-linux` before the fix round). - `allod check --override-input profiles <this worktree>` in the template deploy flake: `All 7 steps passed (checks: 1 built)`, with `profiles` resolved to this branch. Every existing machine still evaluates. The full composition with the inventory and secrets branches substituted as well is recorded on allod/strategy#68. - `nix eval .#lib.profileDefinitions --json | jq '.privacy | keys'`: `["bitcoin-node","privacy-1"]`. - Leak scan over the diff for private hostnames, paths, orgs and address ranges: empty. - Cold review: one cross-family pass (gpt-5.6-sol via pi), eight findings, four taken, four discarded with the reason stated above. Closes allod/profiles#14.
Author

Follow-up to the node definition: branch agent/nexus-bitcoin-wiring at 80d3b99, one commit, relayed to the owner. Proposed PR body follows.


This gives the template's hypervisor the allod bitcoin command, so a fresh install has both the node and the way to drive it. Nothing else changes: the only new file sets two Home Manager options from the node profile's own facts file, and the hypervisor's package list gains exactly one entry, allod-bitcoin. What proves it is the deploy gate with this branch substituted (all 8 steps) and an evaluation of the composed hypervisor's packages showing that one name. If it is wrong, revert the commit; the command holds no state.

What changes

  • hosts/hypervisor/nexus/home.nix: nexus.bitcoinNode.facts imported from hosts/privacy/bitcoin-node/facts.nix, nexus.bitcoinNode.machines = [ "bitcoin-node" ].
  • flake.nix: the hypervisor.nexus definition gains that home module.
  • README.md: one sentence in the Bitcoin node section.

Risk

R1: an operator command installed on the hypervisor behind options the template now sets; the command's own tests live in allod/nexus. Rollback is a revert.

Validation

  • nix flake check .: passes.
  • allod check --override-input profiles <branch> in the template deploy flake: All 8 steps passed (checks: 1 built).
  • nixosConfigurations.nexus.config.home-manager.users.allod.home.packages filtered to allod-bitcoin with that override: [ "allod-bitcoin" ].
  • Leak scan over the diff: empty.
  • No external reviewer (R1).

Refs allod/profiles#14. Part of allod/strategy#68.

Follow-up to the node definition: branch `agent/nexus-bitcoin-wiring` at `80d3b99`, one commit, relayed to the owner. Proposed PR body follows. --- This gives the template's hypervisor the `allod bitcoin` command, so a fresh install has both the node and the way to drive it. Nothing else changes: the only new file sets two Home Manager options from the node profile's own facts file, and the hypervisor's package list gains exactly one entry, `allod-bitcoin`. What proves it is the deploy gate with this branch substituted (all 8 steps) and an evaluation of the composed hypervisor's packages showing that one name. If it is wrong, revert the commit; the command holds no state. ## What changes - `hosts/hypervisor/nexus/home.nix`: `nexus.bitcoinNode.facts` imported from `hosts/privacy/bitcoin-node/facts.nix`, `nexus.bitcoinNode.machines = [ "bitcoin-node" ]`. - `flake.nix`: the `hypervisor.nexus` definition gains that home module. - `README.md`: one sentence in the Bitcoin node section. ## Risk R1: an operator command installed on the hypervisor behind options the template now sets; the command's own tests live in allod/nexus. Rollback is a revert. ## Validation - `nix flake check .`: passes. - `allod check --override-input profiles <branch>` in the template deploy flake: `All 8 steps passed (checks: 1 built)`. - `nixosConfigurations.nexus.config.home-manager.users.allod.home.packages` filtered to `allod-bitcoin` with that override: `[ "allod-bitcoin" ]`. - Leak scan over the diff: empty. - No external reviewer (R1). Refs allod/profiles#14. Part of allod/strategy#68.
Member

Landed on master in commit cc6eb42: hosts/privacy/bitcoin-node profile and VM test, with the hypervisor wiring in 826f409. Verified against the files on master today; closing as completed.

Landed on master in commit cc6eb42: hosts/privacy/bitcoin-node profile and VM test, with the hypervisor wiring in 826f409. Verified against the files on master today; closing as completed.
allod-agent 2026-10-21 23:32:35 +01:00
  • closed this issue
  • removed the
    landed?
    label
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
allod/profiles#14
No description provided.