#!/bin/bash # Monero node health check — managed by Ansible (roles/monero) # # The exit code is the answer and systemd keeps it: # systemctl is-failed monerod-healthcheck.service # Reporting anywhere else is optional and generic. # # WHAT THIS DELIBERATELY DOES NOT CHECK: whether the node is synchronised. # A full initial sync takes days. A check that required `synchronized: true` # would report DOWN for every one of those days, which is not a monitor, it is # an alarm with the batteries taken out. Answering RPC is the health question; # sync progress is reported in the message so it is visible without being # alertable. RPC_HOST="${MONERO_RPC_HOST:-127.0.0.1}" RPC_PORT="${MONERO_RPC_PORT:-18081}" RPC_USER="${MONERO_RPC_USER:-}" RPC_PASSWORD="${MONERO_RPC_PASSWORD:-}" PUSH_URL="${HEALTHCHECK_PUSH_URL:-}" PUSH_TOKEN="${HEALTHCHECK_PUSH_TOKEN:-}" report() { local status=$1 local msg=$2 # No push URL is normal, not an error: the exit code below is still a # complete answer for anything reading unit state. [ -n "$PUSH_URL" ] || return 0 local encoded_msg="${msg// /%20}" # Gatus external endpoint: a POST with a bearer token. local _ok=false [ "${status}" = "up" ] && _ok=true curl -s --max-time 15 --retry 2 -o /dev/null -X POST \ -H "Authorization: Bearer ${PUSH_TOKEN}" \ "${PUSH_URL}?success=${_ok}&error=${encoded_msg}" || true } # --digest, not --user: monerod's --rpc-login is HTTP Digest auth, so curl has # to answer the 401 challenge rather than send credentials up front. response=$(curl -s --max-time 30 --digest -u "${RPC_USER}:${RPC_PASSWORD}" \ -H 'Content-Type: application/json' \ -d '{"jsonrpc":"2.0","id":"healthcheck","method":"get_info"}' \ "http://${RPC_HOST}:${RPC_PORT}/json_rpc" 2>/dev/null) if [ -z "$response" ]; then report "down" "monerod RPC not responding" exit 1 fi status=$(echo "$response" | jq -r '.result.status // empty' 2>/dev/null) if [ "$status" != "OK" ]; then report "down" "monerod RPC status: ${status:-unparseable}" exit 1 fi height=$(echo "$response" | jq -r '.result.height // 0' 2>/dev/null) target=$(echo "$response" | jq -r '.result.target_height // 0' 2>/dev/null) synced=$(echo "$response" | jq -r '.result.synchronized // false' 2>/dev/null) # monerod reports target_height=0 once it has caught up, so show the height we # actually reached rather than a confusing "of 0". if [ "$target" = "0" ]; then report "up" "OK height=${height} synced=${synced}" else report "up" "OK height=${height}/${target} synced=${synced}" fi exit 0