#!/usr/bin/env python3 """kuma-add-ping.py — add ping monitors to Uptime Kuma by name [#435][#705] Companion to kuma-inventory.py (same connect pattern). Reads a list of hostnames from argv or a file (one per line), skips any that already have a ping monitor with the same name or target, and adds the rest with the fleet-standard ping profile. Print-only summary; never deletes. Env (from ~/.creds/uptime-kuma.env): UPTIME_KUMA_URL, UPTIME_KUMA_TOKEN Usage: python3 kuma-add-ping.py host1.knel.net host2.knel.net python3 kuma-add-ping.py --file hosts.txt """ import argparse import os import time import socketio INTERVAL = 60 # fleet-standard ping cadence (seconds) def kuma_connect(): sio = socketio.Client(reconnection=False) state = {} @sio.on("monitorList") def _ml(d): state["ml"] = d sio.connect( os.environ["UPTIME_KUMA_URL"], transports=["websocket"], wait_timeout=30 ) r = sio.call("loginByToken", os.environ["UPTIME_KUMA_TOKEN"], timeout=30) if not (isinstance(r, dict) and r.get("ok")): raise SystemExit(f"login failed: {r}") sio.emit("monitorList") for _ in range(20): if "ml" in state: break time.sleep(1) return sio, state.get("ml", {}) def main(): ap = argparse.ArgumentParser() ap.add_argument("hosts", nargs="*") ap.add_argument("--file", help="file with one hostname per line") args = ap.parse_args() hosts = list(args.hosts) if args.file: with open(args.file) as fh: hosts += [ln.strip() for ln in fh if ln.strip()] if not hosts: raise SystemExit("no hosts given") sio, mons = kuma_connect() existing_names = {(m.get("name") or "").lower() for m in mons.values()} existing_targets = {(m.get("hostname") or m.get("url") or "").lower() for m in mons.values()} added = skipped = 0 for host in hosts: if host.lower() in existing_names or host.lower() in existing_targets: print(f" = {host} (monitor exists)") skipped += 1 continue res = sio.call("add", { "type": "ping", "name": host, "hostname": host, "interval": INTERVAL, "retryInterval": 60, "maxretries": 2, "notificationIDList": {}, }, timeout=30) if isinstance(res, dict) and res.get("ok"): print(f" + {host}") added += 1 else: print(f" ! {host}: {res}") print(f"done: +{added} added, ={skipped} skipped, " f"{len(hosts) - added - skipped} failed") sio.disconnect() if __name__ == "__main__": main()