916 lines
31 KiB
YAML
916 lines
31 KiB
YAML
- 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
|
|
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
|
|
|
|
- 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 }}
|
|
rpcallowip=0.0.0.0/0
|
|
|
|
# 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 %}
|
|
|
|
# Logging (to journald via systemd)
|
|
logtimestamps=1
|
|
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 %}
|
|
|
|
# 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 30 \
|
|
--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
|
|
# 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
|
|
return 0
|
|
else
|
|
return 1
|
|
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
|
|
|
|
# URL encode spaces in message
|
|
local encoded_msg="${msg// /%20}"
|
|
|
|
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
|
|
}
|
|
|
|
# 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']})")
|
|
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}"
|
|
print(f"Push URL: {push_url}")
|
|
else:
|
|
print(f"Creating push monitor '{monitor_name}'...")
|
|
api.add_monitor(
|
|
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:
|
|
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}"
|
|
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
|
|
|
|
|
|
- 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
|