2025-12-08 10:34:04 +01:00
- name : Build and Deploy Bitcoin Knots from Source
hosts : knots_box_local
become : yes
vars_files :
- ../../infra_vars.yml
- ../../services_config.yml
- ../../infra_secrets.yml
- ./bitcoin_knots_vars.yml
vars :
bitcoin_repo_url : "https://github.com/bitcoinknots/bitcoin.git"
bitcoin_sigs_base_url : "https://raw.githubusercontent.com/bitcoinknots/guix.sigs/knots"
bitcoin_version_major : "{{ bitcoin_knots_version_short | regex_replace('^(\\d+)\\..*', '\\1') }}"
bitcoin_source_tarball_url : "https://bitcoinknots.org/files/{{ bitcoin_version_major }}.x/{{ bitcoin_knots_version_short }}/bitcoin-{{ bitcoin_knots_version_short }}.tar.gz"
uptime_kuma_api_url : "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}"
tasks :
- name : Calculate 90% of system RAM for dbcache
set_fact :
bitcoin_dbcache_mb : "{{ (ansible_memtotal_mb | float * 0.9) | int }}"
changed_when : false
- name : Display calculated dbcache value
debug :
msg : "Setting dbcache to {{ bitcoin_dbcache_mb }} MB (90% of {{ ansible_memtotal_mb }} MB total RAM)"
- name : Install build dependencies
apt :
name :
- build-essential
- libtool
- autotools-dev
- automake
- pkg-config
- bsdmainutils
- python3
- python3-pip
- libevent-dev
- libboost-system-dev
- libboost-filesystem-dev
- libboost-test-dev
- libboost-thread-dev
- libboost-chrono-dev
- libboost-program-options-dev
- libboost-dev
- libssl-dev
- libdb-dev
- libminiupnpc-dev
- libzmq3-dev
- libnatpmp-dev
- libsqlite3-dev
- git
- curl
- wget
- cmake
state : present
update_cache : yes
- name : Create bitcoin group
group :
name : "{{ bitcoin_group }}"
system : yes
state : present
- name : Create bitcoin user
user :
name : "{{ bitcoin_user }}"
group : "{{ bitcoin_group }}"
system : yes
shell : /usr/sbin/nologin
home : "{{ bitcoin_data_dir }}"
create_home : yes
state : present
- name : Create bitcoin-knots directory
file :
path : "{{ bitcoin_knots_dir }}"
state : directory
owner : root
group : root
mode : '0755'
- name : Create bitcoin-knots source directory
file :
path : "{{ bitcoin_knots_source_dir }}"
state : directory
owner : root
group : root
mode : '0755'
- name : Create bitcoin data directory (for config, logs, wallets)
file :
path : "{{ bitcoin_data_dir }}"
state : directory
owner : "{{ bitcoin_user }}"
group : "{{ bitcoin_group }}"
mode : '0750'
- name : Create bitcoin large data directory (for blockchain)
file :
path : "{{ bitcoin_large_data_dir }}"
state : directory
owner : "{{ bitcoin_user }}"
group : "{{ bitcoin_group }}"
mode : '0750'
- name : Create bitcoin config directory
file :
path : "{{ bitcoin_conf_dir }}"
state : directory
owner : root
group : root
mode : '0755'
- 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
2025-12-14 18:52:36 +01:00
failed_when : false # Don't fail here - check for 'Good signature' in next task
2025-12-08 10:34:04 +01:00
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
2025-12-14 18:52:36 +01:00
-DWITH_ZMQ=ON
2025-12-08 10:34:04 +01:00
..
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
2025-12-14 18:52:36 +01:00
- 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
2025-12-08 10:34:04 +01:00
- 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
- name : Create bitcoin.conf configuration file
copy :
dest : "{{ bitcoin_conf_dir }}/bitcoin.conf"
content : |
# Bitcoin Knots Configuration
# Generated by Ansible
# Data directory (blockchain storage)
datadir={{ bitcoin_large_data_dir }}
# RPC Configuration
server=1
rpcuser={{ bitcoin_rpc_user }}
rpcpassword={{ bitcoin_rpc_password }}
rpcbind={{ bitcoin_rpc_bind }}
rpcport={{ bitcoin_rpc_port }}
2025-12-14 18:52:36 +01:00
rpcallowip=0.0.0.0/0
2025-12-08 10:34:04 +01:00
# Network Configuration
listen=1
port={{ bitcoin_p2p_port }}
maxconnections={{ bitcoin_max_connections }}
# Performance
dbcache={{ bitcoin_dbcache_mb }}
# Transaction Index (optional)
{% if bitcoin_enable_txindex %}
txindex=1
{% endif %}
2025-12-14 18:52:36 +01:00
# Logging (to journald via systemd)
2025-12-08 10:34:04 +01:00
logtimestamps=1
2025-12-14 18:52:36 +01:00
printtoconsole=1
# ZMQ Configuration
{% if bitcoin_zmq_enabled | default(false) %}
zmqpubrawblock={{ bitcoin_zmq_bind }}:{{ bitcoin_zmq_port_rawblock }}
zmqpubrawtx={{ bitcoin_zmq_bind }}:{{ bitcoin_zmq_port_rawtx }}
zmqpubhashblock={{ bitcoin_zmq_bind }}:{{ bitcoin_zmq_port_hashblock }}
zmqpubhashtx={{ bitcoin_zmq_bind }}:{{ bitcoin_zmq_port_hashtx }}
{% endif %}
2025-12-08 10:34:04 +01:00
# Security
disablewallet=1
owner : "{{ bitcoin_user }}"
group : "{{ bitcoin_group }}"
mode : '0640'
notify : Restart bitcoind
- name : Create systemd service file for bitcoind
copy :
dest : /etc/systemd/system/bitcoind.service
content : |
[ Unit]
Description=Bitcoin Knots daemon
After=network.target
[ Service]
Type=simple
User={{ bitcoin_user }}
Group={{ bitcoin_group }}
ExecStart={{ bitcoin_build_prefix }}/bin/bitcoind -conf={{ bitcoin_conf_dir }}/bitcoin.conf
Restart=always
RestartSec=10
TimeoutStopSec=600
StandardOutput=journal
StandardError=journal
[ Install]
WantedBy=multi-user.target
owner : root
group : root
mode : '0644'
notify : Restart bitcoind
- name : Reload systemd daemon
systemd :
daemon_reload : yes
- name : Enable and start bitcoind service
systemd :
name : bitcoind
enabled : yes
state : started
- name : Wait for bitcoind RPC to be available
uri :
url : "http://{{ bitcoin_rpc_bind }}:{{ bitcoin_rpc_port }}"
method : POST
body_format : json
body :
jsonrpc : "1.0"
id : "healthcheck"
method : "getblockchaininfo"
params : [ ]
user : "{{ bitcoin_rpc_user }}"
password : "{{ bitcoin_rpc_password }}"
status_code : 200
timeout : 10
register : rpc_check
until : rpc_check.status == 200
retries : 30
delay : 5
ignore_errors : yes
- name : Display RPC connection status
debug :
msg : "Bitcoin Knots RPC is {{ 'available' if rpc_check.status == 200 else 'not yet available' }}"
- name : Create Bitcoin Knots health check and push script
copy :
dest : /usr/local/bin/bitcoin-knots-healthcheck-push.sh
content : |
#!/bin/bash
#
# Bitcoin Knots Health Check and Push to Uptime Kuma
# Checks if bitcoind RPC is responding and pushes status to Uptime Kuma
#
RPC_HOST="{{ bitcoin_rpc_bind }}"
RPC_PORT={{ bitcoin_rpc_port }}
RPC_USER="{{ bitcoin_rpc_user }}"
RPC_PASSWORD="{{ bitcoin_rpc_password }}"
UPTIME_KUMA_PUSH_URL="${UPTIME_KUMA_PUSH_URL}"
# Check if bitcoind RPC is responding
check_bitcoind() {
local response
response=$(curl -s --max-time 5 \
--user "${RPC_USER}:${RPC_PASSWORD}" \
--data-binary '{"jsonrpc":"1.0","id":"healthcheck","method":"getblockchaininfo","params":[]}' \
--header 'Content-Type : application/json' \
"http://${RPC_HOST}:${RPC_PORT}" 2 >&1)
if [ $? -eq 0 ]; then
2025-12-14 18:52:36 +01:00
# Check if response contains a non-null error
# Successful responses have "error": null, failures have "error": {...}
if echo "$response" | grep -q '"error":null\|"error": null'; then
2025-12-08 10:34:04 +01:00
return 0
2025-12-14 18:52:36 +01:00
else
return 1
2025-12-08 10:34:04 +01:00
fi
else
return 1
fi
}
# Push status to Uptime Kuma
push_to_uptime_kuma() {
local status=$1
local msg=$2
if [ -z "$UPTIME_KUMA_PUSH_URL" ]; then
echo "ERROR: UPTIME_KUMA_PUSH_URL not set"
return 1
fi
2025-12-14 18:52:36 +01:00
# URL encode spaces in message
local encoded_msg="${msg// /%20}"
2025-12-08 10:34:04 +01:00
2025-12-14 18:52:36 +01:00
if ! curl -s --max-time 10 --retry 2 -o /dev/null \
"${UPTIME_KUMA_PUSH_URL}?status=${status}&msg=${encoded_msg}&ping=" ; then
echo "ERROR: Failed to push to Uptime Kuma"
return 1
fi
2025-12-08 10:34:04 +01:00
}
# Main health check
if check_bitcoind; then
push_to_uptime_kuma "up" "OK"
exit 0
else
push_to_uptime_kuma "down" "bitcoind RPC not responding"
exit 1
fi
owner : root
group : root
mode : '0755'
- name : Install curl for health check script
apt :
name : curl
state : present
- name : Create systemd timer for Bitcoin Knots health check
copy :
dest : /etc/systemd/system/bitcoin-knots-healthcheck.timer
content : |
[ Unit]
Description=Bitcoin Knots Health Check Timer
Requires=bitcoind.service
[ Timer]
OnBootSec=1min
OnUnitActiveSec=1min
Persistent=true
[ Install]
WantedBy=timers.target
owner : root
group : root
mode : '0644'
- name : Create systemd service for Bitcoin Knots health check
copy :
dest : /etc/systemd/system/bitcoin-knots-healthcheck.service
content : |
[ Unit]
Description=Bitcoin Knots Health Check and Push to Uptime Kuma
After=network.target bitcoind.service
[ Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/bitcoin-knots-healthcheck-push.sh
Environment=UPTIME_KUMA_PUSH_URL=
StandardOutput=journal
StandardError=journal
[ Install]
WantedBy=multi-user.target
owner : root
group : root
mode : '0644'
- name : Reload systemd daemon for health check
systemd :
daemon_reload : yes
- name : Enable and start Bitcoin Knots health check timer
systemd :
name : bitcoin-knots-healthcheck.timer
enabled : yes
state : started
- name : Create Uptime Kuma push monitor setup script for Bitcoin Knots
delegate_to : localhost
become : no
copy :
dest : /tmp/setup_bitcoin_knots_monitor.py
content : |
#!/usr/bin/env python3
import sys
import traceback
import yaml
from uptime_kuma_api import UptimeKumaApi, MonitorType
try :
# Load configs
with open('/tmp/ansible_config.yml', 'r') as f :
config = yaml.safe_load(f)
url = config['uptime_kuma_url']
username = config['username']
password = config['password']
monitor_name = config['monitor_name']
# Connect to Uptime Kuma
api = UptimeKumaApi(url, timeout=30)
api.login(username, password)
# Get all monitors
monitors = api.get_monitors()
# Find or create "services" group
group = next((m for m in monitors if m.get('name') == 'services' and m.get('type') == 'group'), None)
if not group :
group_result = api.add_monitor(type='group', name='services')
# Refresh to get the group with id
monitors = api.get_monitors()
group = next((m for m in monitors if m.get('name') == 'services' and m.get('type') == 'group'), None)
# Check if monitor already exists
existing_monitor = None
for monitor in monitors :
if monitor.get('name') == monitor_name :
existing_monitor = monitor
break
# Get ntfy notification ID
notifications = api.get_notifications()
ntfy_notification_id = None
for notif in notifications :
if notif.get('type') == 'ntfy' :
ntfy_notification_id = notif.get('id')
break
if existing_monitor :
print(f"Monitor '{monitor_name}' already exists (ID: {existing_monitor['id']})")
2025-12-14 18:52:36 +01:00
push_token = existing_monitor.get('pushToken') or existing_monitor.get('push_token')
if not push_token :
raise ValueError("Could not find push token for monitor")
push_url = f"{url}/api/push/{push_token}"
2025-12-08 10:34:04 +01:00
print(f"Push URL: {push_url}")
else :
print(f"Creating push monitor '{monitor_name}'...")
2025-12-14 18:52:36 +01:00
api.add_monitor(
2025-12-08 10:34:04 +01:00
type=MonitorType.PUSH,
name=monitor_name,
parent=group['id'],
interval=60,
maxretries=3,
retryInterval=60,
notificationIDList={ntfy_notification_id: True} if ntfy_notification_id else {}
)
monitors = api.get_monitors()
new_monitor = next((m for m in monitors if m.get('name') == monitor_name), None)
if new_monitor :
2025-12-14 18:52:36 +01:00
push_token = new_monitor.get('pushToken') or new_monitor.get('push_token')
if not push_token :
raise ValueError("Could not find push token for new monitor")
push_url = f"{url}/api/push/{push_token}"
2025-12-08 10:34:04 +01:00
print(f"Push URL: {push_url}")
api.disconnect()
print("SUCCESS")
except Exception as e :
error_msg = str(e) if str(e) else repr(e)
print(f"ERROR: {error_msg}", file=sys.stderr)
traceback.print_exc(file=sys.stderr)
sys.exit(1)
mode : '0755'
- name : Create temporary config for monitor setup
delegate_to : localhost
become : no
copy :
dest : /tmp/ansible_config.yml
content : |
uptime_kuma_url : "{{ uptime_kuma_api_url }}"
username : "{{ uptime_kuma_username }}"
password : "{{ uptime_kuma_password }}"
monitor_name : "Bitcoin Knots"
mode : '0644'
- name : Run Uptime Kuma push monitor setup
command : python3 /tmp/setup_bitcoin_knots_monitor.py
delegate_to : localhost
become : no
register : monitor_setup
changed_when : "'SUCCESS' in monitor_setup.stdout"
ignore_errors : yes
- name : Extract push URL from monitor setup output
set_fact :
uptime_kuma_push_url : "{{ monitor_setup.stdout | regex_search('Push URL: (https?://[^\\s]+)', '\\1') | first | default('') }}"
delegate_to : localhost
become : no
when : monitor_setup.stdout is defined
- name : Display extracted push URL
debug :
msg : "Uptime Kuma Push URL: {{ uptime_kuma_push_url }}"
when : uptime_kuma_push_url | default('') != ''
- name : Set push URL in systemd service environment
lineinfile :
path : /etc/systemd/system/bitcoin-knots-healthcheck.service
regexp : '^Environment=UPTIME_KUMA_PUSH_URL='
line : "Environment=UPTIME_KUMA_PUSH_URL={{ uptime_kuma_push_url }}"
state : present
insertafter : '^\[Service\]'
when : uptime_kuma_push_url | default('') != ''
- name : Reload systemd daemon after push URL update
systemd :
daemon_reload : yes
when : uptime_kuma_push_url | default('') != ''
- name : Restart health check timer to pick up new environment
systemd :
name : bitcoin-knots-healthcheck.timer
state : restarted
when : uptime_kuma_push_url | default('') != ''
- name : Clean up temporary files
delegate_to : localhost
become : no
file :
path : "{{ item }}"
state : absent
loop :
- /tmp/setup_bitcoin_knots_monitor.py
- /tmp/ansible_config.yml
handlers :
- name : Restart bitcoind
systemd :
name : bitcoind
state : restarted
2025-12-15 23:33:08 +01:00
- name : Setup public Bitcoin P2P forwarding on vipy via systemd-socket-proxyd
hosts : vipy
become : yes
vars_files :
- ../../infra_vars.yml
- ../../services_config.yml
- ../../infra_secrets.yml
- ./bitcoin_knots_vars.yml
vars :
bitcoin_tailscale_hostname : "knots-box"
uptime_kuma_api_url : "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}"
tasks :
- name : Create Bitcoin P2P proxy socket unit
copy :
dest : /etc/systemd/system/bitcoin-p2p-proxy.socket
content : |
[ Unit]
Description=Bitcoin P2P Proxy Socket
[ Socket]
ListenStream={{ bitcoin_p2p_port }}
[ Install]
WantedBy=sockets.target
owner : root
group : root
mode : '0644'
notify : Restart bitcoin-p2p-proxy socket
- name : Create Bitcoin P2P proxy service unit
copy :
dest : /etc/systemd/system/bitcoin-p2p-proxy.service
content : |
[ Unit]
Description=Bitcoin P2P Proxy to {{ bitcoin_tailscale_hostname }}
Requires=bitcoin-p2p-proxy.socket
After=network.target
[ Service]
Type=notify
ExecStart=/lib/systemd/systemd-socket-proxyd {{ bitcoin_tailscale_hostname }}:{{ bitcoin_p2p_port }}
owner : root
group : root
mode : '0644'
- name : Reload systemd daemon
systemd :
daemon_reload : yes
- name : Enable and start Bitcoin P2P proxy socket
systemd :
name : bitcoin-p2p-proxy.socket
enabled : yes
state : started
- name : Allow Bitcoin P2P port through UFW
ufw :
rule : allow
port : "{{ bitcoin_p2p_port | string }}"
proto : tcp
comment : "Bitcoin P2P public access"
- name : Verify connectivity to knots-box via Tailscale
wait_for :
host : "{{ bitcoin_tailscale_hostname }}"
port : "{{ bitcoin_p2p_port }}"
timeout : 10
ignore_errors : yes
- name : Display public endpoint
debug :
msg : "Bitcoin P2P public endpoint: {{ ansible_host }}:{{ bitcoin_p2p_port }}"
# ===========================================
# Uptime Kuma TCP Monitor for Public P2P
# ===========================================
- name : Create Uptime Kuma TCP monitor setup script for Bitcoin P2P
delegate_to : localhost
become : no
copy :
dest : /tmp/setup_bitcoin_p2p_tcp_monitor.py
content : |
#!/usr/bin/env python3
import sys
import traceback
import yaml
from uptime_kuma_api import UptimeKumaApi, MonitorType
try :
with open('/tmp/ansible_bitcoin_p2p_config.yml', 'r') as f :
config = yaml.safe_load(f)
url = config['uptime_kuma_url']
username = config['username']
password = config['password']
monitor_host = config['monitor_host']
monitor_port = config['monitor_port']
monitor_name = config['monitor_name']
api = UptimeKumaApi(url, timeout=30)
api.login(username, password)
monitors = api.get_monitors()
# Find or create "services" group
group = next((m for m in monitors if m.get('name') == 'services' and m.get('type') == 'group'), None)
if not group :
api.add_monitor(type='group', name='services')
monitors = api.get_monitors()
group = next((m for m in monitors if m.get('name') == 'services' and m.get('type') == 'group'), None)
# Check if monitor already exists
existing = next((m for m in monitors if m.get('name') == monitor_name), None)
# Get ntfy notification ID
notifications = api.get_notifications()
ntfy_notification_id = None
for notif in notifications :
if notif.get('type') == 'ntfy' :
ntfy_notification_id = notif.get('id')
break
if existing :
print(f"Monitor '{monitor_name}' already exists (ID: {existing['id']})")
print("Skipping - monitor already configured")
else :
print(f"Creating TCP monitor '{monitor_name}'...")
api.add_monitor(
type=MonitorType.PORT,
name=monitor_name,
hostname=monitor_host,
port=monitor_port,
parent=group['id'],
interval=60,
maxretries=3,
retryInterval=60,
notificationIDList={ntfy_notification_id: True} if ntfy_notification_id else {}
)
api.disconnect()
print("SUCCESS")
except Exception as e :
print(f"ERROR: {str(e)}", file=sys.stderr)
traceback.print_exc(file=sys.stderr)
sys.exit(1)
mode : '0755'
- name : Create temporary config for TCP monitor setup
delegate_to : localhost
become : no
copy :
dest : /tmp/ansible_bitcoin_p2p_config.yml
content : |
uptime_kuma_url : "{{ uptime_kuma_api_url }}"
username : "{{ uptime_kuma_username }}"
password : "{{ uptime_kuma_password }}"
monitor_host : "{{ ansible_host }}"
monitor_port : {{ bitcoin_p2p_port }}
monitor_name : "Bitcoin Knots P2P Public"
mode : '0644'
- name : Run Uptime Kuma TCP monitor setup
command : python3 /tmp/setup_bitcoin_p2p_tcp_monitor.py
delegate_to : localhost
become : no
register : tcp_monitor_setup
changed_when : "'SUCCESS' in tcp_monitor_setup.stdout"
ignore_errors : yes
- name : Display TCP monitor setup output
debug :
msg : "{{ tcp_monitor_setup.stdout_lines }}"
when : tcp_monitor_setup.stdout is defined
- name : Clean up TCP monitor temporary files
delegate_to : localhost
become : no
file :
path : "{{ item }}"
state : absent
loop :
- /tmp/setup_bitcoin_p2p_tcp_monitor.py
- /tmp/ansible_bitcoin_p2p_config.yml
handlers :
- name : Restart bitcoin-p2p-proxy socket
systemd :
name : bitcoin-p2p-proxy.socket
state : restarted