X PrimeFlow

Osmosis

Osmosis
Chain ID: osmo-test-5
Block Height:
Network Status: Connecting
Explorer
RPC ยท systemd

Osmosis Node Monitoring

Tenderduty is a third-party option. The executable procedure below is a small BonyNode local-RPC healthcheck: it downloads no code, validates the configured chain ID and bounded height, serializes runs with flock, and stores state outside temporary directories.

OsmosisTestnetosmo-test-5

1. Prerequisites and least-privilege account

The command fails closed if required distribution-provided tools are missing. Install missing packages from your trusted operating-system repository before continuing.

set -euo pipefail

for required in curl jq flock systemctl systemd-analyze getent id install; do
  command -v "$required" >/dev/null || {
    echo "Missing required command: $required" >&2
    exit 1
  }
done
test -x /usr/sbin/nologin || {
  echo "/usr/sbin/nologin is required" >&2
  exit 1
}

if ! getent group bonynode-monitor >/dev/null; then
  sudo groupadd --system bonynode-monitor
fi
if ! id -u bonynode-monitor >/dev/null 2>&1; then
  sudo useradd --system --gid bonynode-monitor --home-dir /var/lib/bonynode-monitor --shell /usr/sbin/nologin bonynode-monitor
fi
test "$(id -gn bonynode-monitor)" = "bonynode-monitor" || {
  echo "Existing bonynode-monitor user has an unexpected primary group" >&2
  exit 1
}

sudo install -d -o root -g root -m 0755 /usr/local/libexec/bonynode
sudo install -d -o root -g root -m 0755 /etc/bonynode-monitor
sudo install -d -o bonynode-monitor -g bonynode-monitor -m 0750 /var/lib/bonynode-monitor/osmosis-testnet

2. Install the reviewed script

The service runs a root-owned mode-0555 copy. The non-root service account cannot replace or edit it. /usr/local/libexec/bonynode/osmosis-testnet-healthcheck

set -euo pipefail
umask 077

script_tmp="$(mktemp)"
trap 'rm -f -- "$script_tmp"' EXIT
cat >"$script_tmp" <<'BONYNODE_MONITOR_SCRIPT'
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077

readonly NETWORK_LABEL='Osmosis'
readonly EXPECTED_CHAIN_ID='osmo-test-5'
readonly RPC_URL='http://127.0.0.1:52657'
readonly STATE_DIR='/var/lib/bonynode-monitor/osmosis-testnet'
readonly HEIGHT_FILE="${STATE_DIR}/last-height"
readonly LOCK_FILE="${STATE_DIR}/check.lock"

if [[ ! -d "${STATE_DIR}" || ! -w "${STATE_DIR}" ]]; then
  echo "State directory is missing or not writable: ${STATE_DIR}" >&2
  exit 1
fi

if [[ ! "${TELEGRAM_BOT_TOKEN:-}" =~ ^[0-9]{5,20}:[A-Za-z0-9_-]{20,128}$ ]]; then
  echo "TELEGRAM_BOT_TOKEN is missing or malformed" >&2
  exit 2
fi
if [[ ! "${TELEGRAM_CHAT_ID:-}" =~ ^-?[0-9]{1,20}$ && ! "${TELEGRAM_CHAT_ID:-}" =~ ^@[A-Za-z][A-Za-z0-9_]{4,31}$ ]]; then
  echo "TELEGRAM_CHAT_ID is missing or malformed" >&2
  exit 2
fi

send_alert() {
  local message="$1"
  curl --fail --silent --show-error     --connect-timeout 5 --max-time 15 --retry 1     --proto '=https' --proto-redir '=https'     --config -     --data-urlencode "chat_id=${TELEGRAM_CHAT_ID}"     --data-urlencode "text=${message}"     >/dev/null <<CURL_CONFIG
url = "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage"
request = "POST"
CURL_CONFIG
}

exec 9>"${LOCK_FILE}"
flock -n 9 || exit 0

status_file="$(mktemp "${STATE_DIR}/status.XXXXXX")"
height_tmp=''
cleanup() {
  rm -f -- "${status_file:-}" "${height_tmp:-}"
}
trap cleanup EXIT

if ! curl --fail --silent --show-error   --connect-timeout 3 --max-time 10 --max-filesize 1048576   --proto '=http' --output "${status_file}" "${RPC_URL}/status"; then
  send_alert "RPC unavailable: ${NETWORK_LABEL} (${EXPECTED_CHAIN_ID})" || true
  exit 1
fi

network="$(jq -er '.result.node_info.network | select(type == "string" and test("^[A-Za-z0-9._+-]{1,128}$"))' "${status_file}")" || {
  send_alert "Invalid RPC status payload: ${NETWORK_LABEL}" || true
  exit 1
}
height="$(jq -er '.result.sync_info.latest_block_height | if type == "number" then tostring elif type == "string" then . else empty end | select(test("^(0|[1-9][0-9]{0,17})$"))' "${status_file}")" || {
  send_alert "Invalid block height from local RPC: ${NETWORK_LABEL}" || true
  exit 1
}
catching_up="$(jq -er '.result.sync_info.catching_up | select(type == "boolean") | tostring' "${status_file}")" || {
  send_alert "Invalid catching_up value from local RPC: ${NETWORK_LABEL}" || true
  exit 1
}

if [[ "${network}" != "${EXPECTED_CHAIN_ID}" ]]; then
  send_alert "Chain ID mismatch for ${NETWORK_LABEL}: expected ${EXPECTED_CHAIN_ID}, received ${network}" || true
  exit 1
fi

height_number=$((10#${height}))
if [[ -s "${HEIGHT_FILE}" ]]; then
  IFS= read -r last_height < "${HEIGHT_FILE}"
  if [[ ! "${last_height}" =~ ^(0|[1-9][0-9]{0,17})$ ]]; then
    send_alert "Invalid persisted height for ${NETWORK_LABEL}; manual review required" || true
    exit 1
  fi
  last_height_number=$((10#${last_height}))
  if (( height_number <= last_height_number )); then
    send_alert "Node height did not advance for ${NETWORK_LABEL}: ${height}" || true
    exit 1
  fi
fi

if [[ "${catching_up}" == 'true' ]]; then
  send_alert "Node is catching up: ${NETWORK_LABEL} at height ${height}"
fi

height_tmp="$(mktemp "${HEIGHT_FILE}.tmp.XXXXXX")"
printf '%s
' "${height}" > "${height_tmp}"
chmod 0600 "${height_tmp}"
mv -f -- "${height_tmp}" "${HEIGHT_FILE}"
height_tmp=''
BONYNODE_MONITOR_SCRIPT

# Root-owned and non-writable by the service account.
sudo install -o root -g root -m 0555 "$script_tmp" /usr/local/libexec/bonynode/osmosis-testnet-healthcheck
test "$(stat -c '%a:%U:%G' /usr/local/libexec/bonynode/osmosis-testnet-healthcheck)" = '555:root:root'
rm -f -- "$script_tmp"
trap - EXIT

3. Create the root-only Telegram environment file

TELEGRAM_BOT_TOKEN=replace_with_botfather_token
TELEGRAM_CHAT_ID=replace_with_numeric_chat_id_or_channel_username
set -euo pipefail

# Create the secret file first; enter credentials only through sudoedit.
sudo install -o root -g root -m 0600 /dev/null /etc/bonynode-monitor/osmosis-testnet.env
sudoedit /etc/bonynode-monitor/osmosis-testnet.env
test "$(sudo stat -c '%a:%U:%G' /etc/bonynode-monitor/osmosis-testnet.env)" = '600:root:root'

4. Verify and enable the hardened systemd timer

Activation requires a successful one-shot check first. Invalid RPC identity, JSON, height, permissions or Telegram configuration prevents the timer from being enabled.

set -euo pipefail
umask 077

service_tmp="$(mktemp)"
timer_tmp="$(mktemp)"
trap 'rm -f -- "$service_tmp" "$timer_tmp"' EXIT

cat >"$service_tmp" <<'BONYNODE_MONITOR_SERVICE'
[Unit]
Description=Osmosis local RPC healthcheck
After=network-online.target
Wants=network-online.target
ConditionPathIsExecutable=/usr/local/libexec/bonynode/osmosis-testnet-healthcheck

[Service]
Type=oneshot
User=bonynode-monitor
Group=bonynode-monitor
EnvironmentFile=/etc/bonynode-monitor/osmosis-testnet.env
WorkingDirectory=/var/lib/bonynode-monitor/osmosis-testnet
ExecStart=/usr/local/libexec/bonynode/osmosis-testnet-healthcheck
UMask=0077
TimeoutStartSec=45
NoNewPrivileges=true
PrivateDevices=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectHostname=true
ProtectClock=true
RestrictSUIDSGID=true
RestrictNamespaces=true
RestrictRealtime=true
LockPersonality=true
MemoryDenyWriteExecute=true
RemoveIPC=true
CapabilityBoundingSet=
AmbientCapabilities=
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
ReadWritePaths=/var/lib/bonynode-monitor/osmosis-testnet

[Install]
WantedBy=multi-user.target
BONYNODE_MONITOR_SERVICE

cat >"$timer_tmp" <<'BONYNODE_MONITOR_TIMER'
[Unit]
Description=Run Osmosis local RPC healthcheck every five minutes

[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
RandomizedDelaySec=30s
Persistent=true
Unit=bonynode-monitor-osmosis-testnet.service

[Install]
WantedBy=timers.target
BONYNODE_MONITOR_TIMER

sudo install -o root -g root -m 0644 "$service_tmp" /etc/systemd/system/bonynode-monitor-osmosis-testnet.service
sudo install -o root -g root -m 0644 "$timer_tmp" /etc/systemd/system/bonynode-monitor-osmosis-testnet.timer
rm -f -- "$service_tmp" "$timer_tmp"
trap - EXIT

sudo systemd-analyze verify /etc/systemd/system/bonynode-monitor-osmosis-testnet.service /etc/systemd/system/bonynode-monitor-osmosis-testnet.timer
sudo systemctl daemon-reload

# A successful one-shot run is required before enabling the timer.
sudo systemctl start bonynode-monitor-osmosis-testnet.service
sudo systemctl enable --now bonynode-monitor-osmosis-testnet.timer
sudo systemctl status bonynode-monitor-osmosis-testnet.timer --no-pager
systemd

Reversible decommission

This stops scheduling without deleting the script, secrets, state or unit files.

sudo systemctl disable --now bonynode-monitor-osmosis-testnet.timer
echo "Timer disabled. Script, secret file, units and state were retained for review."