71 lines
2.5 KiB
Text
71 lines
2.5 KiB
Text
|
|
#!/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
|