bitcoin-knots: convert to a role, de-Uptime-Kuma the health check
892-line playbook becomes 40 lines plus a role with install/build/configure/
service/healthcheck phases, five templates and one handler. bitcoin_knots_vars.yml
is deleted; its content is the role's defaults.
Verified after a real run: bitcoind still active since 2026-08-19 (NO restart),
chain at 966844 blocks / 875 GB, DATUM config intact, dbcache still 200, health
check timer firing again. changed=3, all health-check. vipy changed=0.
⚠ THE BIG ONE: the playbook would have deleted the mining integration.
bitcoin.conf on the node carries a section that was hand-added and was missing
from the template entirely:
blockmaxsize=3985000
blockmaxweight=3985000
blocknotify=killall -USR1 datum_gateway
maxmempool=1000
blockreconstructionextratxn=1000000
blocknotify is how datum_gateway learns a new block landed. Running the old
playbook would have stripped all of it and solo mining would have carried on
against a stale template - a silent failure that costs money rather than raising
an error. Also dbcache 200 -> 3528 (hand-tuned down; the calculation wants 90% of
RAM) and logging moved off the file. All now reconciled, dbcache behind
bitcoin_dbcache_mb_override.
bitcoin-knots and datum-gateway are ONE SYSTEM. Noted in the README.
AND MY OWN FIX MADE IT MORE DANGEROUS. The `Restart bitcoind` handler was guarded
by uptime_kuma_enabled, so it had been inert: bitcoin.conf and the systemd unit
both notify it and neither could restart anything - a config change applied to
disk, reported success, and never took effect. Ungating that is right, but it
converts "wrong config sitting inertly on disk" into "node restarted onto a
config that breaks mining". The ungating had to land WITH the template
reconciliation, not before it.
It also raises the bar permanently: any residual template/live difference now
restarts a Bitcoin node on every run. Four rounds of --check --diff to reach
changed=0 - the DATUM section, an explanatory comment that was rendering into the
deployed config (now a {# #} Jinja comment), a "# Pruning (optional)" comment the
live file had, and one trailing blank line.
The build path is 32 tasks all guarded by `not bitcoind_binary_exists.stat.exists`,
so a converged host skips the 30-60 minute compile and both `state: absent`
deletions. Those target /opt/bitcoin-knots/{source,bitcoin-<version>}; the chain
is in /mnt/knots_data and is never touched. Signature-verification tasks copied
verbatim.
The health check timer had last fired 2026-08-09 while reporting active/enabled -
same OnBootSec + OnUnitActiveSec dead chain as fulcrum. The role runs the check
once after enabling to supply the reference the timer schedules from.
Ownership parity checked mechanically against `git show HEAD:`, keyed by TASK
NAME rather than path - keying by path gave a false positive, because
bitcoin_knots_source_dir is created with ownership and later removed with
state: absent, so whichever task comes last wins and that differs between one
file and five. 13/13 match.
bitcoin_p2p_port and the tailscale hostname moved to services_config.yml for the
socket-proxy play on the edge host.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e83191c029
commit
d26dc78b3c
19 changed files with 1163 additions and 1250 deletions
222
ansible/roles/bitcoin_knots/tasks/build.yml
Normal file
222
ansible/roles/bitcoin_knots/tasks/build.yml
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
---
|
||||
# Every task here is guarded by `when: not bitcoind_binary_exists.stat.exists`,
|
||||
# so on a host that already has the binary the whole download / verify / build
|
||||
# sequence skips — including the two `state: absent` deletions, which target
|
||||
# /opt/bitcoin-knots/{source,bitcoin-<version>} and never the chain data in
|
||||
# /mnt/knots_data.
|
||||
- name: Check if bitcoind binary already exists
|
||||
stat:
|
||||
path: "{{ bitcoin_build_prefix }}/bin/bitcoind"
|
||||
register: bitcoind_binary_exists
|
||||
changed_when: false
|
||||
|
||||
- name: Install gnupg for signature verification
|
||||
apt:
|
||||
name: gnupg
|
||||
state: present
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
|
||||
- name: Import Luke Dashjr's Bitcoin Knots signing key
|
||||
command: gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys 90C8019E36C2E964
|
||||
register: key_import
|
||||
changed_when: "'already in secret keyring' not in key_import.stdout and 'already in public keyring' not in key_import.stdout"
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
failed_when: key_import.rc != 0
|
||||
|
||||
- name: Display imported key fingerprint
|
||||
command: gpg --fingerprint 90C8019E36C2E964
|
||||
register: key_fingerprint
|
||||
changed_when: false
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
|
||||
- name: Download SHA256SUMS file
|
||||
get_url:
|
||||
url: "https://bitcoinknots.org/files/{{ bitcoin_version_major }}.x/{{ bitcoin_knots_version_short }}/SHA256SUMS"
|
||||
dest: "/tmp/bitcoin-knots-{{ bitcoin_knots_version_short }}-SHA256SUMS"
|
||||
mode: '0644'
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
|
||||
- name: Download SHA256SUMS.asc signature file
|
||||
get_url:
|
||||
url: "https://bitcoinknots.org/files/{{ bitcoin_version_major }}.x/{{ bitcoin_knots_version_short }}/SHA256SUMS.asc"
|
||||
dest: "/tmp/bitcoin-knots-{{ bitcoin_knots_version_short }}-SHA256SUMS.asc"
|
||||
mode: '0644'
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
|
||||
- name: Verify PGP signature on SHA256SUMS file
|
||||
command: gpg --verify /tmp/bitcoin-knots-{{ bitcoin_knots_version_short }}-SHA256SUMS.asc /tmp/bitcoin-knots-{{ bitcoin_knots_version_short }}-SHA256SUMS
|
||||
register: sha256sums_verification
|
||||
changed_when: false
|
||||
failed_when: false # Don't fail here - check for 'Good signature' in next task
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
|
||||
|
||||
- name: Display SHA256SUMS verification result
|
||||
debug:
|
||||
msg: "{{ sha256sums_verification.stdout_lines + sha256sums_verification.stderr_lines }}"
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
|
||||
- name: Fail if SHA256SUMS signature verification failed
|
||||
fail:
|
||||
msg: "SHA256SUMS signature verification failed. Aborting build."
|
||||
when: not bitcoind_binary_exists.stat.exists and ('Good signature' not in sha256sums_verification.stdout and 'Good signature' not in sha256sums_verification.stderr)
|
||||
|
||||
- name: Remove any existing tarball to force fresh download
|
||||
file:
|
||||
path: /tmp/bitcoin-{{ bitcoin_knots_version_short }}.tar.gz
|
||||
state: absent
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
|
||||
- name: Download Bitcoin Knots source tarball
|
||||
get_url:
|
||||
url: "{{ bitcoin_source_tarball_url }}"
|
||||
dest: "/tmp/bitcoin-{{ bitcoin_knots_version_short }}.tar.gz"
|
||||
mode: '0644'
|
||||
validate_certs: yes
|
||||
force: yes
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
|
||||
- name: Calculate SHA256 checksum of downloaded tarball
|
||||
command: sha256sum /tmp/bitcoin-{{ bitcoin_knots_version_short }}.tar.gz
|
||||
register: tarball_checksum
|
||||
changed_when: false
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
|
||||
- name: Extract expected checksum from SHA256SUMS file
|
||||
shell: grep "bitcoin-{{ bitcoin_knots_version_short }}.tar.gz" /tmp/bitcoin-knots-{{ bitcoin_knots_version_short }}-SHA256SUMS | awk '{print $1}'
|
||||
register: expected_checksum
|
||||
changed_when: false
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
failed_when: expected_checksum.stdout == ""
|
||||
|
||||
- name: Display checksum comparison
|
||||
debug:
|
||||
msg:
|
||||
- "Expected: {{ expected_checksum.stdout | trim }}"
|
||||
- "Actual: {{ tarball_checksum.stdout.split()[0] }}"
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
|
||||
- name: Verify tarball checksum matches SHA256SUMS
|
||||
fail:
|
||||
msg: "Tarball checksum mismatch! Expected {{ expected_checksum.stdout | trim }}, got {{ tarball_checksum.stdout.split()[0] }}"
|
||||
when: not bitcoind_binary_exists.stat.exists and expected_checksum.stdout | trim != tarball_checksum.stdout.split()[0]
|
||||
|
||||
- name: Remove existing source directory if it exists (to force fresh extraction)
|
||||
file:
|
||||
path: "{{ bitcoin_knots_source_dir }}"
|
||||
state: absent
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
|
||||
- name: Remove extracted directory if it exists (from previous runs)
|
||||
file:
|
||||
path: "{{ bitcoin_knots_dir }}/bitcoin-{{ bitcoin_knots_version_short }}"
|
||||
state: absent
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
|
||||
- name: Extract verified source tarball
|
||||
unarchive:
|
||||
src: /tmp/bitcoin-{{ bitcoin_knots_version_short }}.tar.gz
|
||||
dest: "{{ bitcoin_knots_dir }}"
|
||||
remote_src: yes
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
|
||||
- name: Check if extracted directory exists
|
||||
stat:
|
||||
path: "{{ bitcoin_knots_dir }}/bitcoin-{{ bitcoin_knots_version_short }}"
|
||||
register: extracted_dir_stat
|
||||
changed_when: false
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
|
||||
- name: Rename extracted directory to expected name
|
||||
command: mv "{{ bitcoin_knots_dir }}/bitcoin-{{ bitcoin_knots_version_short }}" "{{ bitcoin_knots_source_dir }}"
|
||||
when: not bitcoind_binary_exists.stat.exists and extracted_dir_stat.stat.exists
|
||||
|
||||
- name: Check if CMakeLists.txt exists
|
||||
stat:
|
||||
path: "{{ bitcoin_knots_source_dir }}/CMakeLists.txt"
|
||||
register: cmake_exists
|
||||
changed_when: false
|
||||
when: not bitcoind_binary_exists.stat.exists
|
||||
|
||||
- name: Create CMake build directory
|
||||
file:
|
||||
path: "{{ bitcoin_knots_source_dir }}/build"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
when: not bitcoind_binary_exists.stat.exists and cmake_exists.stat.exists | default(false)
|
||||
|
||||
- name: Configure Bitcoin Knots build with CMake
|
||||
command: >
|
||||
cmake
|
||||
-DCMAKE_INSTALL_PREFIX={{ bitcoin_build_prefix }}
|
||||
-DBUILD_BITCOIN_WALLET=OFF
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
-DWITH_ZMQ=ON
|
||||
..
|
||||
args:
|
||||
chdir: "{{ bitcoin_knots_source_dir }}/build"
|
||||
when: not bitcoind_binary_exists.stat.exists and cmake_exists.stat.exists | default(false)
|
||||
register: configure_result
|
||||
changed_when: true
|
||||
|
||||
- name: Verify CMake enabled ZMQ
|
||||
shell: |
|
||||
set -e
|
||||
cd "{{ bitcoin_knots_source_dir }}/build"
|
||||
cmake -LAH .. | grep -iE 'ZMQ|WITH_ZMQ|ENABLE_ZMQ|USE_ZMQ'
|
||||
when: not bitcoind_binary_exists.stat.exists and cmake_exists.stat.exists | default(false)
|
||||
register: zmq_check
|
||||
changed_when: false
|
||||
|
||||
- name: Fail if CMakeLists.txt not found
|
||||
fail:
|
||||
msg: "CMakeLists.txt not found in {{ bitcoin_knots_source_dir }}. Cannot build Bitcoin Knots."
|
||||
when: not bitcoind_binary_exists.stat.exists and not (cmake_exists.stat.exists | default(false))
|
||||
|
||||
- name: Build Bitcoin Knots with CMake (this may take 30-60+ minutes)
|
||||
command: cmake --build . -j{{ bitcoin_build_jobs }}
|
||||
args:
|
||||
chdir: "{{ bitcoin_knots_source_dir }}/build"
|
||||
when: not bitcoind_binary_exists.stat.exists and cmake_exists.stat.exists | default(false)
|
||||
async: 3600
|
||||
poll: 0
|
||||
register: build_result
|
||||
changed_when: true
|
||||
|
||||
- name: Check build status
|
||||
async_status:
|
||||
jid: "{{ build_result.ansible_job_id }}"
|
||||
register: build_job_result
|
||||
until: build_job_result.finished
|
||||
retries: 120
|
||||
delay: 60
|
||||
when: not bitcoind_binary_exists.stat.exists and build_result.ansible_job_id is defined
|
||||
|
||||
- name: Fail if build failed
|
||||
fail:
|
||||
msg: "Bitcoin Knots build failed: {{ build_job_result.msg }}"
|
||||
when: not bitcoind_binary_exists.stat.exists and build_result.ansible_job_id is defined and build_job_result.failed | default(false)
|
||||
|
||||
- name: Install Bitcoin Knots binaries
|
||||
command: cmake --install .
|
||||
args:
|
||||
chdir: "{{ bitcoin_knots_source_dir }}/build"
|
||||
when: not bitcoind_binary_exists.stat.exists and cmake_exists.stat.exists | default(false)
|
||||
changed_when: true
|
||||
|
||||
- name: Verify bitcoind binary exists
|
||||
stat:
|
||||
path: "{{ bitcoin_build_prefix }}/bin/bitcoind"
|
||||
register: bitcoind_installed
|
||||
changed_when: false
|
||||
|
||||
- name: Verify bitcoin-cli binary exists
|
||||
stat:
|
||||
path: "{{ bitcoin_build_prefix }}/bin/bitcoin-cli"
|
||||
register: bitcoin_cli_installed
|
||||
changed_when: false
|
||||
|
||||
- name: Fail if binaries not found
|
||||
fail:
|
||||
msg: "Bitcoin Knots binaries not found after installation"
|
||||
when: not bitcoind_installed.stat.exists or not bitcoin_cli_installed.stat.exists
|
||||
Loading…
Add table
Add a link
Reference in a new issue