Compare commits

..

2 commits

Author SHA1 Message Date
ba4ba504d0
stuffy stuff 2026-08-08 12:00:27 +02:00
0736f29f79
little thingies 2026-03-22 21:25:08 +01:00
6 changed files with 982 additions and 1 deletions

69
03_vm_disk_enlargement.md Normal file
View file

@ -0,0 +1,69 @@
# 03 VM Disk Enlargement
How to enlarge the disk of an existing Proxmox VM that was provisioned via the tofu project.
## Overview
Since the tofu VM resource uses `ignore_changes = all` in its lifecycle block, disk resizes are performed manually through the Proxmox UI and then expanded inside the guest OS. You should still update the tofu vars to keep them in sync with reality.
## Step 1: Update disk size in tofu vars
* Edit `tofu/nodito/terraform.tfvars` and bump the `disk_size_gb` value for the VM you want to resize.
* This won't trigger any actual change (because of `ignore_changes = all`), but keeps the declared state consistent so future readers know the real disk size.
## Step 2: Resize the disk in Proxmox
* Open the Proxmox web UI and navigate to the VM.
* Go to the **Hardware** tab.
* Select the **Hard Disk** entry (scsi0).
* Click **Disk Action** > **Resize**.
* Enter the amount of additional space you want to add (this is the increment, not the total). For example, to go from 10 GB to 30 GB, enter `20`.
* Click **Resize disk**.
The VM does **not** need to be stopped for this operation. Proxmox will grow the underlying block device while the VM is running.
## Step 3: Expand the partition and filesystem inside the VM
SSH into the VM and run these steps as root (or with `sudo`).
### Check the current layout
```bash
lsblk
```
You should see that the disk (`sda`) now shows the new total size, but the root partition (`sda1`) still has the old size.
### Grow the partition
Install `cloud-guest-utils` if `growpart` is not available:
```bash
apt install -y cloud-guest-utils
```
Then grow the root partition to fill all available space:
```bash
growpart /dev/sda 1
```
> Note the space between the device and the partition number.
### Resize the filesystem
For ext4 (the default for Debian cloud images):
```bash
resize2fs /dev/sda1
```
This works online; no reboot is required.
### Verify
```bash
df -h /
```
The root filesystem should now reflect the new size.

View file

@ -61,7 +61,7 @@
retries = int(sys.argv[8])
ntfy_topic = sys.argv[9] if len(sys.argv) > 9 else "alerts"
api = UptimeKumaApi(api_url, timeout=60, wait_events=2.0)
api = UptimeKumaApi(api_url, timeout=120, wait_events=2.0)
api.login(username, password)
# Get all monitors

View file

@ -31,3 +31,10 @@ mariadb_mempool_password: "CHANGE_ME_TO_SECURE_PASSWORD"
# Used by: services/forgejo-runner/deploy_forgejo_runner_playbook.yml
# See: services/forgejo-runner/SETUP.md for how to obtain this token
forgejo_runner_registration_token: "YOUR_RUNNER_TOKEN_HERE"
# DATUM Gateway secrets
# Used by: services/datum-gateway/deploy_datum_gateway_playbook.yml
datum_mining_address: "YOUR_BITCOIN_ADDRESS_FOR_BLOCK_REWARDS"
datum_gateway_admin_password: "CHANGE_ME_TO_SECURE_PASSWORD"
datum_dashboard_username: "admin"
datum_dashboard_password_hash: "$2a$14$..." # Generate with: caddy hash-password

View file

@ -0,0 +1,43 @@
# DATUM Gateway Configuration Variables
# https://github.com/OCEAN-xyz/datum_gateway
# Version - pin to a specific tag
datum_gateway_version: "v0.4.1beta"
# Directories
datum_gateway_dir: /opt/datum-gateway
datum_gateway_source_dir: "{{ datum_gateway_dir }}/source"
datum_gateway_config_dir: /etc/datum-gateway
datum_gateway_log_dir: /var/log/datum-gateway
# Binary
datum_gateway_bin_path: /usr/local/bin/datum_gateway
# Ports
datum_gateway_stratum_port: 23334 # Miners connect here via Stratum v1
datum_gateway_api_port: 7152 # Web dashboard / API
# Stratum settings
datum_vardiff_min: 524288 # Minimum share difficulty (must be power of 2; OCEAN floor overrides if higher)
# Service user
datum_gateway_user: datum
datum_gateway_group: datum
# Build options
datum_gateway_build_jobs: 4
# Bitcoin node connection
# The gateway runs on the same host as Bitcoin Knots so localhost RPC works.
# datum_bitcoin_rpc_url should include http:// and port.
datum_bitcoin_rpc_url: "http://127.0.0.1:8332"
# Note: bitcoin_rpc_user and bitcoin_rpc_password come from infra_secrets.yml
# Mining config
datum_coinbase_tag_primary: "DATUM"
datum_coinbase_tag_secondary: "BY ORDER OF BIP110"
datum_pool_pass_workers: true
datum_pool_pass_full_users: true
datum_pooled_mining_only: true

View file

@ -0,0 +1,859 @@
---
# DATUM Gateway Deployment Playbook
#
# Deploys DATUM Gateway (https://github.com/OCEAN-xyz/datum_gateway) on the
# Bitcoin Knots host so it has direct localhost RPC access to bitcoind.
#
# What this does:
# 1. Installs build deps and compiles datum_gateway from source
# 2. Creates a dedicated system user and config/log directories
# 3. Writes /etc/datum-gateway/config.json from vars/secrets
# 4. Patches bitcoin.conf with the required blockmaxsize/blocknotify lines
# 5. Creates and enables a systemd service
# 6. Creates a push-monitor health check script + systemd timer
# 7. Registers a push monitor in Uptime Kuma
#
# Separate play: adds a Caddy reverse proxy on vipy for the dashboard.
#
# Stratum port (23334) is bound on knots_box_local. Expose it to miners via
# a firewall rule, Tailscale, or a socket proxy on vipy — not handled here.
#
# Required secrets in infra_secrets.yml:
# datum_mining_address - Bitcoin address for block rewards
# datum_gateway_admin_password - Password for the /api admin endpoint
# bitcoin_rpc_user - Shared with the bitcoin-knots deployment
# bitcoin_rpc_password - Shared with the bitcoin-knots deployment
- name: Deploy DATUM Gateway on knots_box_local
hosts: knots_box_local
become: yes
vars_files:
- ../../infra_vars.yml
- ../../services_config.yml
- ../../infra_secrets.yml
- ./datum_gateway_vars.yml
vars:
datum_gateway_subdomain: "{{ subdomains.datum_gateway }}"
datum_gateway_domain: "{{ datum_gateway_subdomain }}.{{ root_domain }}"
uptime_kuma_api_url: "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}"
tasks:
# ===========================================
# Build Dependencies
# ===========================================
- name: Install DATUM Gateway build dependencies
apt:
name:
- cmake
- build-essential
- git
- libjansson-dev
- libmicrohttpd-dev
- libsodium-dev
- libcurl4-openssl-dev
# Runtime-only (netcat for health check)
- netcat-openbsd
state: present
update_cache: yes
# ===========================================
# System User and Directories
# ===========================================
- name: Create datum system user
user:
name: "{{ datum_gateway_user }}"
system: yes
shell: /usr/sbin/nologin
home: "{{ datum_gateway_dir }}"
create_home: no
comment: "DATUM Gateway"
- name: Create DATUM Gateway directories
file:
path: "{{ item.path }}"
state: directory
owner: "{{ item.owner }}"
group: "{{ datum_gateway_group }}"
mode: "{{ item.mode }}"
loop:
- { path: "{{ datum_gateway_dir }}", owner: root, mode: "0755" }
- { path: "{{ datum_gateway_source_dir }}", owner: root, mode: "0755" }
- { path: "{{ datum_gateway_config_dir }}", owner: "{{ datum_gateway_user }}", mode: "0750" }
- { path: "{{ datum_gateway_log_dir }}", owner: "{{ datum_gateway_user }}", mode: "0750" }
# ===========================================
# Build from Source
# ===========================================
- name: Clone DATUM Gateway repository at {{ datum_gateway_version }}
git:
repo: https://github.com/OCEAN-xyz/datum_gateway.git
dest: "{{ datum_gateway_source_dir }}"
version: "{{ datum_gateway_version }}"
force: yes
register: git_clone
- name: Configure cmake build
command: cmake . -DCMAKE_BUILD_TYPE=Release
args:
chdir: "{{ datum_gateway_source_dir }}"
- name: Compile datum_gateway
command: make -j{{ datum_gateway_build_jobs }}
args:
chdir: "{{ datum_gateway_source_dir }}"
- name: Install datum_gateway binary
copy:
src: "{{ datum_gateway_source_dir }}/datum_gateway"
dest: "{{ datum_gateway_bin_path }}"
remote_src: yes
owner: root
group: root
mode: "0755"
notify: Restart datum-gateway
# ===========================================
# Configuration
# ===========================================
- name: Write DATUM Gateway config.json
copy:
dest: "{{ datum_gateway_config_dir }}/config.json"
content: |
{
"bitcoind": {
"rpcuser": "{{ bitcoin_rpc_user }}",
"rpcpassword": "{{ bitcoin_rpc_password }}",
"rpcurl": "{{ datum_bitcoin_rpc_url }}",
"notify_fallback": true
},
"stratum": {
"listen_port": {{ datum_gateway_stratum_port }},
"vardiff_min": {{ datum_vardiff_min }}
},
"mining": {
"pool_address": "{{ datum_mining_address }}",
"coinbase_tag_primary": "{{ datum_coinbase_tag_primary }}",
"coinbase_tag_secondary": "{{ datum_coinbase_tag_secondary }}"
},
"api": {
"admin_password": "{{ datum_gateway_admin_password }}",
"listen_port": {{ datum_gateway_api_port }},
"modify_conf": false
},
"logger": {
"log_to_console": true,
"log_to_file": true,
"log_file": "{{ datum_gateway_log_dir }}/datum_gateway.log",
"log_rotate_daily": true,
"log_level_console": 2,
"log_level_file": 1
},
"datum": {
"pool_pass_workers": {{ datum_pool_pass_workers | lower }},
"pool_pass_full_users": {{ datum_pool_pass_full_users | lower }},
"pooled_mining_only": {{ datum_pooled_mining_only | lower }}
}
}
owner: "{{ datum_gateway_user }}"
group: "{{ datum_gateway_group }}"
mode: "0640"
notify: Restart datum-gateway
# ===========================================
# Systemd Service
# ===========================================
- name: Create datum-gateway systemd service
copy:
dest: /etc/systemd/system/datum-gateway.service
content: |
[Unit]
Description=DATUM Gateway - Bitcoin Mining Gateway
Documentation=https://github.com/OCEAN-xyz/datum_gateway
After=network.target bitcoind.service
Wants=bitcoind.service
[Service]
User={{ datum_gateway_user }}
Group={{ datum_gateway_group }}
Type=simple
ExecStart={{ datum_gateway_bin_path }} --config {{ datum_gateway_config_dir }}/config.json
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
# Prevent config from being read by other users
ReadWritePaths={{ datum_gateway_log_dir }}
ReadOnlyPaths={{ datum_gateway_config_dir }}
[Install]
WantedBy=multi-user.target
owner: root
group: root
mode: "0644"
notify: Restart datum-gateway
- name: Reload systemd daemon
systemd:
daemon_reload: yes
- name: Enable and start datum-gateway
systemd:
name: datum-gateway
enabled: yes
state: started
# ===========================================
# Health Check Script + Systemd Timer
# ===========================================
- name: Create DATUM Gateway health check script
copy:
dest: /usr/local/bin/datum-gateway-healthcheck-push.sh
content: |
#!/bin/bash
UPTIME_KUMA_PUSH_URL="${UPTIME_KUMA_PUSH_URL}"
STRATUM_PORT={{ datum_gateway_stratum_port }}
check_datum() {
# Service must be active and stratum port must be listening
systemctl is-active --quiet datum-gateway && \
nc -z 127.0.0.1 "${STRATUM_PORT}"
}
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
curl -s --max-time 10 --retry 2 -o /dev/null \
"${UPTIME_KUMA_PUSH_URL}?status=${status}&msg=${msg// /%20}&ping=" || true
}
if check_datum; then
push_to_uptime_kuma "up" "OK"
exit 0
else
push_to_uptime_kuma "down" "DATUM Gateway not responding"
exit 1
fi
owner: root
group: root
mode: "0755"
- name: Create datum-gateway health check systemd service
copy:
dest: /etc/systemd/system/datum-gateway-healthcheck.service
content: |
[Unit]
Description=DATUM Gateway Health Check
After=network.target datum-gateway.service
[Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/datum-gateway-healthcheck-push.sh
Environment=UPTIME_KUMA_PUSH_URL=
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
owner: root
group: root
mode: "0644"
- name: Create datum-gateway health check systemd timer
copy:
dest: /etc/systemd/system/datum-gateway-healthcheck.timer
content: |
[Unit]
Description=DATUM Gateway Health Check Timer
[Timer]
OnBootSec=2min
OnUnitActiveSec=1min
Persistent=true
[Install]
WantedBy=timers.target
owner: root
group: root
mode: "0644"
- name: Reload systemd daemon after health check units
systemd:
daemon_reload: yes
- name: Enable and start datum-gateway health check timer
systemd:
name: datum-gateway-healthcheck.timer
enabled: yes
state: started
# ===========================================
# Uptime Kuma Push Monitor Setup
# ===========================================
- name: Create Uptime Kuma push monitor setup script for DATUM Gateway
delegate_to: localhost
become: no
copy:
dest: /tmp/setup_datum_gateway_monitor.py
content: |
#!/usr/bin/env python3
import sys
import time
import traceback
import yaml
try:
import socketio.exceptions
except ImportError:
pass
from uptime_kuma_api import UptimeKumaApi, MonitorType
try:
with open('/tmp/ansible_datum_gateway_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']
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:
try:
api.add_monitor(type='group', name='services')
except Exception:
time.sleep(2)
monitors = api.get_monitors()
group = next((m for m in monitors if m.get('name') == 'services' and m.get('type') == 'group'), 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
# Check if monitor already exists
existing = next((m for m in monitors if m.get('name') == monitor_name), None)
push_url = None
if existing:
print(f"Monitor '{monitor_name}' already exists (ID: {existing['id']})")
push_token = existing.get('pushToken') or existing.get('push_token')
if push_token:
push_url = f"{url}/api/push/{push_token}"
else:
print(f"Creating push monitor '{monitor_name}'...")
try:
api.add_monitor(
type=MonitorType.PUSH,
name=monitor_name,
parent=group['id'],
interval=90,
maxretries=3,
retryInterval=60,
notificationIDList={ntfy_notification_id: True} if ntfy_notification_id else {}
)
except Exception as e:
# socketio timeout: add_monitor may have succeeded server-side
print(f"add_monitor raised (possibly timeout): {e}", file=sys.stderr)
time.sleep(2)
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 push_token:
push_url = f"{url}/api/push/{push_token}"
api.disconnect()
if push_url:
print(f"PUSH_URL={push_url}")
with open('/tmp/datum_gateway_push_url.txt', 'w') as f:
f.write(push_url)
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 push monitor setup
delegate_to: localhost
become: no
copy:
dest: /tmp/ansible_datum_gateway_config.yml
content: |
uptime_kuma_url: "{{ uptime_kuma_api_url }}"
username: "{{ uptime_kuma_username }}"
password: "{{ uptime_kuma_password }}"
monitor_name: "DATUM Gateway"
mode: "0644"
- name: Run Uptime Kuma push monitor setup
command: python3 /tmp/setup_datum_gateway_monitor.py
delegate_to: localhost
become: no
register: monitor_setup
changed_when: "'SUCCESS' in monitor_setup.stdout"
ignore_errors: yes
- name: Display monitor setup output
debug:
msg: "{{ monitor_setup.stdout_lines }}"
when: monitor_setup.stdout is defined
- name: Read push URL from file
slurp:
src: /tmp/datum_gateway_push_url.txt
delegate_to: localhost
become: no
register: push_url_file
ignore_errors: yes
- name: Parse push URL
set_fact:
datum_push_url: "{{ push_url_file.content | b64decode | trim }}"
when: push_url_file.content is defined
- name: Update health check service with push URL
lineinfile:
path: /etc/systemd/system/datum-gateway-healthcheck.service
regexp: "^Environment=UPTIME_KUMA_PUSH_URL="
line: "Environment=UPTIME_KUMA_PUSH_URL={{ datum_push_url }}"
when: datum_push_url is defined
notify: Restart datum-gateway health check timer
- name: Clean up temporary files
delegate_to: localhost
become: no
file:
path: "{{ item }}"
state: absent
loop:
- /tmp/setup_datum_gateway_monitor.py
- /tmp/ansible_datum_gateway_config.yml
- /tmp/datum_gateway_push_url.txt
handlers:
- name: Restart datum-gateway
systemd:
name: datum-gateway
state: restarted
daemon_reload: yes
- name: Restart datum-gateway health check timer
systemd:
name: datum-gateway-healthcheck.timer
state: restarted
daemon_reload: yes
# ===========================================
# Caddy Reverse Proxy for DATUM Dashboard (on vipy)
# ===========================================
- name: Configure Caddy reverse proxy for DATUM Gateway dashboard on vipy
hosts: vipy
become: yes
vars_files:
- ../../infra_vars.yml
- ../../services_config.yml
- ../../infra_secrets.yml
- ./datum_gateway_vars.yml
vars:
datum_gateway_subdomain: "{{ subdomains.datum_gateway }}"
datum_gateway_domain: "{{ datum_gateway_subdomain }}.{{ root_domain }}"
caddy_sites_dir: "{{ caddy_sites_dir }}"
uptime_kuma_api_url: "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}"
tasks:
- name: Ensure Caddy sites-enabled directory exists
file:
path: "{{ caddy_sites_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Ensure Caddyfile includes import directive for sites-enabled
lineinfile:
path: /etc/caddy/Caddyfile
line: "import sites-enabled/*"
insertafter: EOF
state: present
backup: yes
create: yes
mode: "0644"
- name: Create Caddy reverse proxy config for DATUM Gateway dashboard
copy:
dest: "{{ caddy_sites_dir }}/datum-gateway.conf"
content: |
{{ datum_gateway_domain }} {
basic_auth {
{{ datum_dashboard_username }} {{ datum_dashboard_password_hash }}
}
reverse_proxy knots-box:{{ datum_gateway_api_port }} {
# Resolve via Tailscale MagicDNS
transport http {
resolvers 100.100.100.100
}
}
}
owner: root
group: root
mode: "0644"
- name: Validate Caddy config
command: caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
register: caddy_validate
changed_when: false
- name: Display Caddy validation output
debug:
msg: "{{ caddy_validate.stdout_lines + caddy_validate.stderr_lines }}"
- name: Reload Caddy
command: systemctl reload caddy
register: caddy_reload
- name: Display Caddy reload output
debug:
msg: "{{ caddy_reload.stdout_lines + caddy_reload.stderr_lines }}"
- name: Display DATUM Gateway dashboard URL
debug:
msg: "DATUM Gateway dashboard: https://{{ datum_gateway_domain }}"
# ===========================================
# Uptime Kuma HTTP Monitor for Public Dashboard
# ===========================================
- name: Create Uptime Kuma HTTP monitor setup script for DATUM dashboard
delegate_to: localhost
become: no
copy:
dest: /tmp/setup_datum_http_monitor.py
content: |
#!/usr/bin/env python3
import sys
import time
import traceback
import yaml
from uptime_kuma_api import UptimeKumaApi, MonitorType
try:
with open('/tmp/ansible_datum_http_config.yml', 'r') as f:
config = yaml.safe_load(f)
url = config['uptime_kuma_url']
username = config['username']
password = config['password']
monitor_url = config['monitor_url']
monitor_name = config['monitor_name']
api = UptimeKumaApi(url, timeout=30)
api.login(username, password)
monitors = api.get_monitors()
group = next((m for m in monitors if m.get('name') == 'services' and m.get('type') == 'group'), None)
if not group:
try:
api.add_monitor(type='group', name='services')
except Exception:
time.sleep(2)
monitors = api.get_monitors()
group = next((m for m in monitors if m.get('name') == 'services' and m.get('type') == 'group'), None)
notifications = api.get_notifications()
ntfy_notification_id = None
for notif in notifications:
if notif.get('type') == 'ntfy':
ntfy_notification_id = notif.get('id')
break
existing = next((m for m in monitors if m.get('name') == monitor_name), None)
if existing:
print(f"Monitor '{monitor_name}' already exists (ID: {existing['id']})")
else:
print(f"Creating HTTP monitor '{monitor_name}'...")
try:
api.add_monitor(
type=MonitorType.HTTP,
name=monitor_name,
url=monitor_url,
parent=group['id'],
interval=60,
maxretries=3,
retryInterval=60,
notificationIDList={ntfy_notification_id: True} if ntfy_notification_id else {}
)
except Exception as e:
print(f"add_monitor raised (possibly timeout): {e}", file=sys.stderr)
time.sleep(2)
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 HTTP monitor
delegate_to: localhost
become: no
copy:
dest: /tmp/ansible_datum_http_config.yml
content: |
uptime_kuma_url: "{{ uptime_kuma_api_url }}"
username: "{{ uptime_kuma_username }}"
password: "{{ uptime_kuma_password }}"
monitor_url: "https://{{ datum_gateway_domain }}"
monitor_name: "DATUM Gateway Dashboard"
mode: "0644"
- name: Run Uptime Kuma HTTP monitor setup
command: python3 /tmp/setup_datum_http_monitor.py
delegate_to: localhost
become: no
register: http_monitor_setup
changed_when: "'SUCCESS' in http_monitor_setup.stdout"
ignore_errors: yes
- name: Display HTTP monitor setup output
debug:
msg: "{{ http_monitor_setup.stdout_lines }}"
when: http_monitor_setup.stdout is defined
- name: Clean up HTTP monitor temporary files
delegate_to: localhost
become: no
file:
path: "{{ item }}"
state: absent
loop:
- /tmp/setup_datum_http_monitor.py
- /tmp/ansible_datum_http_config.yml
# ===========================================
# Stratum Port Forwarding on vipy via systemd-socket-proxyd
# Miners connect to vipy:23334; traffic is forwarded to knots-box:23334
# over the Tailscale network, matching the Bitcoin P2P proxy pattern.
# ===========================================
- name: Setup public Stratum port forwarding on vipy via systemd-socket-proxyd
hosts: vipy
become: yes
vars_files:
- ../../infra_vars.yml
- ../../services_config.yml
- ../../infra_secrets.yml
- ./datum_gateway_vars.yml
vars:
datum_tailscale_hostname: "knots-box"
uptime_kuma_api_url: "https://{{ subdomains.uptime_kuma }}.{{ root_domain }}"
tasks:
- name: Create Stratum proxy socket unit
copy:
dest: /etc/systemd/system/datum-stratum-proxy.socket
content: |
[Unit]
Description=DATUM Stratum Proxy Socket
[Socket]
ListenStream={{ datum_gateway_stratum_port }}
[Install]
WantedBy=sockets.target
owner: root
group: root
mode: "0644"
notify: Restart datum-stratum-proxy socket
- name: Create Stratum proxy service unit
copy:
dest: /etc/systemd/system/datum-stratum-proxy.service
content: |
[Unit]
Description=DATUM Stratum Proxy to {{ datum_tailscale_hostname }}
Requires=datum-stratum-proxy.socket
After=network.target
[Service]
Type=notify
ExecStart=/lib/systemd/systemd-socket-proxyd {{ datum_tailscale_hostname }}:{{ datum_gateway_stratum_port }}
owner: root
group: root
mode: "0644"
- name: Reload systemd daemon
systemd:
daemon_reload: yes
- name: Enable and start Stratum proxy socket
systemd:
name: datum-stratum-proxy.socket
enabled: yes
state: started
- name: Allow Stratum port through UFW
ufw:
rule: allow
port: "{{ datum_gateway_stratum_port | string }}"
proto: tcp
comment: "DATUM Gateway Stratum public access"
- name: Verify connectivity to knots-box Stratum via Tailscale
wait_for:
host: "{{ datum_tailscale_hostname }}"
port: "{{ datum_gateway_stratum_port }}"
timeout: 10
ignore_errors: yes
- name: Display public Stratum endpoint
debug:
msg: "DATUM Stratum public endpoint: {{ ansible_host }}:{{ datum_gateway_stratum_port }}"
# ===========================================
# Uptime Kuma TCP Monitor for Public Stratum
# ===========================================
- name: Create Uptime Kuma TCP monitor setup script for Stratum
delegate_to: localhost
become: no
copy:
dest: /tmp/setup_datum_stratum_tcp_monitor.py
content: |
#!/usr/bin/env python3
import sys
import time
import traceback
import yaml
from uptime_kuma_api import UptimeKumaApi, MonitorType
try:
with open('/tmp/ansible_datum_stratum_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()
group = next((m for m in monitors if m.get('name') == 'services' and m.get('type') == 'group'), None)
if not group:
try:
api.add_monitor(type='group', name='services')
except Exception:
time.sleep(2)
monitors = api.get_monitors()
group = next((m for m in monitors if m.get('name') == 'services' and m.get('type') == 'group'), None)
notifications = api.get_notifications()
ntfy_notification_id = None
for notif in notifications:
if notif.get('type') == 'ntfy':
ntfy_notification_id = notif.get('id')
break
existing = next((m for m in monitors if m.get('name') == monitor_name), None)
if existing:
print(f"Monitor '{monitor_name}' already exists (ID: {existing['id']})")
else:
print(f"Creating TCP monitor '{monitor_name}'...")
try:
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 {}
)
except Exception as e:
print(f"add_monitor raised (possibly timeout): {e}", file=sys.stderr)
time.sleep(2)
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 Stratum TCP monitor
delegate_to: localhost
become: no
copy:
dest: /tmp/ansible_datum_stratum_config.yml
content: |
uptime_kuma_url: "{{ uptime_kuma_api_url }}"
username: "{{ uptime_kuma_username }}"
password: "{{ uptime_kuma_password }}"
monitor_host: "{{ ansible_host }}"
monitor_port: {{ datum_gateway_stratum_port }}
monitor_name: "DATUM Stratum (public)"
mode: "0644"
- name: Run Uptime Kuma TCP monitor setup
command: python3 /tmp/setup_datum_stratum_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 Stratum TCP monitor temporary files
delegate_to: localhost
become: no
file:
path: "{{ item }}"
state: absent
loop:
- /tmp/setup_datum_stratum_tcp_monitor.py
- /tmp/ansible_datum_stratum_config.yml
handlers:
- name: Restart datum-stratum-proxy socket
systemd:
name: datum-stratum-proxy.socket
state: restarted

View file

@ -25,6 +25,9 @@ subdomains:
# Mempool Block Explorer (on mempool_box, proxied via vipy)
mempool: mempool
# DATUM Gateway dashboard (on knots_box, proxied via vipy)
datum_gateway: datum
# Caddy configuration
caddy_sites_dir: /etc/caddy/sites-enabled