2026-08-08 12:00:27 +02:00
|
|
|
---
|
|
|
|
|
# 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
|
2026-09-11 21:56:15 +02:00
|
|
|
hosts: bitcoin
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
|
|
|
|
# ===========================================
|
2026-09-11 22:43:55 +02:00
|
|
|
# ═════════════════════════════════════════════════════════════════════════
|
|
|
|
|
# DEPRECATED — Uptime Kuma was decommissioned on 2026-09-11.
|
|
|
|
|
#
|
|
|
|
|
# Every task below is inert: uptime_kuma_enabled is false in
|
|
|
|
|
# group_vars/all/main.yml, so they all skip and the deployment above still
|
|
|
|
|
# runs normally. Kept because the health-check logic is the durable part —
|
|
|
|
|
# when a replacement exists, rewire the push transport and flip the flag.
|
|
|
|
|
#
|
|
|
|
|
# What was being monitored: archive/uptime_kuma/MONITORS.md
|
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════
|
2026-08-08 12:00:27 +02:00
|
|
|
- name: Create DATUM Gateway health check script
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 21:56:15 +02:00
|
|
|
hosts: edge
|
2026-08-08 12:00:27 +02:00
|
|
|
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:
|
ntfy, datum-gateway, headscale: use the caddy_site role
Completes Stage 3. No hand-rolled Caddy plumbing remains anywhere:
`grep sites-enabled` outside roles/ returns nothing, and so does
`grep "systemctl reload caddy"`.
ntfy uses caddy_site_body for its plain-HTTP listener and @httpget redirect.
Verified ok/unchanged against watchtower; the one other changed task is a
pre-existing "Update APT cache".
datum-gateway keeps a whole-Caddyfile validate after the role call. The role
validates its own fragment, but only a whole-file validate catches a conflict
between two sites, and this playbook was the only one that ever had it. Its
two debug tasks that echoed command output are gone with the commands.
headscale is the one that mattered. Its playbook wrote
`reverse_proxy localhost:8080`, but spacey is actually running a /admin*
route in front of Headplane behind Caddy basic auth. Running that playbook
would have deleted the admin route and its auth - a hazard that predates this
work. It now renders the config that is really there, verified ok/unchanged
via --start-at-task (the play cannot reach Caddy in check mode: "Install
headscale package" fails because the .deb is not really downloaded, before
and after this edit alike).
Supporting changes for headscale:
- headscale_ui_password_hash added to infra_secrets.yml and the identical
group_vars/all/vault.yml, read from the live config on spacey. The vault
already had headscale_ui_username (= counterweight, confirmed) and
headscale_ui_password; I did not verify the password is the plaintext of
this hash.
- headplane_port added to headscale_vars.yml.
- The role's handler now sets become: true. Handlers do not inherit become
from the task that notified them, and this play runs become: no.
- The include uses `apply: become: yes`; `become:` on an include_role is
rejected outright.
All 14 site files on all 3 hosts still byte-identical.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 23:37:38 +02:00
|
|
|
- name: Publish the DATUM Gateway dashboard through Caddy
|
|
|
|
|
ansible.builtin.include_role:
|
|
|
|
|
name: caddy_site
|
|
|
|
|
vars:
|
|
|
|
|
caddy_site_name: datum-gateway
|
|
|
|
|
caddy_site_domain: "{{ datum_gateway_domain }}"
|
|
|
|
|
caddy_site_upstream: "knots-box:{{ datum_gateway_api_port }}"
|
|
|
|
|
caddy_site_resolvers: "100.100.100.100"
|
|
|
|
|
caddy_site_basic_auth:
|
|
|
|
|
- user: "{{ datum_dashboard_username }}"
|
|
|
|
|
hash: "{{ datum_dashboard_password_hash }}"
|
|
|
|
|
|
|
|
|
|
# The role validates the site fragment on its own. This re-validates the
|
|
|
|
|
# whole assembled Caddyfile, which is the only thing that catches a
|
|
|
|
|
# conflict between this site and another. Kept from the hand-rolled
|
|
|
|
|
# version; the other nine services never had it.
|
|
|
|
|
- name: Validate the assembled Caddyfile
|
|
|
|
|
ansible.builtin.command: caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
|
2026-08-08 12:00:27 +02:00
|
|
|
changed_when: false
|
|
|
|
|
|
|
|
|
|
- name: Display DATUM Gateway dashboard URL
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 21:56:15 +02:00
|
|
|
hosts: edge
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|
2026-09-11 22:43:55 +02:00
|
|
|
when: uptime_kuma_enabled | default(false)
|
2026-08-08 12:00:27 +02:00
|
|
|
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
|