From ba4463c4d0a0de951140af5aaff9b7a51eb4bd34 Mon Sep 17 00:00:00 2001 From: reachableceo Date: Thu, 3 Sep 2026 14:34:44 -0500 Subject: [PATCH] fix(oam): kuma inventory target-field bug + add-ping tooling [#343][#435] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kuma-inventory.py printed the junk url field (https://) instead of the hostname for ping monitors — hid ~100 monitors from FQDN matching. kuma-add-ping.py: idempotent ping-monitor adds (exists-check by name and target). Matrix published to Discourse t/309 (canonical, #343). Detail: https://projects.knownelement.com/issues/343 --- scripts/kuma-add-ping.py | 92 +++++++++++++++++++++++++++++++++++++++ scripts/kuma-inventory.py | 3 +- 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 scripts/kuma-add-ping.py diff --git a/scripts/kuma-add-ping.py b/scripts/kuma-add-ping.py new file mode 100644 index 0000000..c2c95d3 --- /dev/null +++ b/scripts/kuma-add-ping.py @@ -0,0 +1,92 @@ +#!/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() diff --git a/scripts/kuma-inventory.py b/scripts/kuma-inventory.py index 34fd1f5..c4f1342 100644 --- a/scripts/kuma-inventory.py +++ b/scripts/kuma-inventory.py @@ -63,7 +63,8 @@ def main(): mtype = m.get("type") or "?" active = "on" if m.get("active", True) else "PAUSED" name = (m.get("name") or "?").replace("\t", " ") - target = (m.get("hostname") if m.get("type") == "dns" else None) or m.get("url") or m.get("hostname") or "" + # hostname first: ping monitors carry a junk default url ("https://") + target = m.get("hostname") or m.get("url") or "" target = str(target).replace("\t", " ") rows.append((mid, parent, mtype, active, name, target)) rows.sort(key=lambda r: (r[1] == "-", r[1], r[4].lower()))