55 http monitors added under "Cloud Systems" (blue/green: canary first, verified green); 9 root-level strays re-homed into founder's groups (root now zero). New tools: inventory (read-only dump), cloudron-sync (idempotent diff/add from committed app list), regroup (lib-based moves; raw editMonitor times out on this build). fleet-sync now requires --group-id so it can never place monitors at root again. Results: https://projects.knownelement.com/issues/435#note-4
70 lines
2.5 KiB
Python
70 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""kuma-regroup.py — move Uptime Kuma monitors into groups [#435]
|
|
|
|
Founder ruling 2026-09-02: no monitors at the root; use the existing
|
|
groups. Uses the uptime-kuma-api library — raw socket.io editMonitor with
|
|
a hand-built payload times out on this Kuma build (missing form fields
|
|
the server requires before acking; the lib sends the full UI-shaped
|
|
payload). dns_resolve_type="A" is passed to satisfy lib-side validation
|
|
for monitors where the field is None (harmless for non-dns types).
|
|
|
|
Env (from ~/.creds/uptime-kuma.env):
|
|
UPTIME_KUMA_URL, UPTIME_KUMA_TOKEN
|
|
|
|
Usage (host — needs python-socketio-compatible env via lib):
|
|
python3 kuma-regroup.py --dry-move 211:48 213:13
|
|
python3 kuma-regroup.py --move 211:48 213:13
|
|
id:groupid pairs. --move executes; --dry-move prints the plan only.
|
|
|
|
Verify afterwards with kuma-inventory.py (the lib's get_monitors does
|
|
not map the parent field — trust the raw monitorList view).
|
|
"""
|
|
import argparse
|
|
import os
|
|
import sys
|
|
|
|
from uptime_kuma_api import UptimeKumaApi
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--move", nargs="+", default=[], metavar="ID:GROUPID")
|
|
ap.add_argument("--dry-move", nargs="+", default=[], metavar="ID:GROUPID")
|
|
args = ap.parse_args()
|
|
|
|
pairs = [(int(a), int(b)) for a, b in (p.split(":") for p in (args.move or args.dry_move))]
|
|
dry = bool(args.dry_move)
|
|
|
|
api = UptimeKumaApi(os.environ["UPTIME_KUMA_URL"], timeout=30)
|
|
rc = 0
|
|
try:
|
|
api.login_by_token(os.environ["UPTIME_KUMA_TOKEN"])
|
|
monitors = {m["id"]: m for m in api.get_monitors()}
|
|
for mid, gid in pairs:
|
|
m = monitors.get(mid)
|
|
if not m:
|
|
print(f"SKIP {mid}: not found")
|
|
rc = 1
|
|
continue
|
|
g = monitors.get(gid)
|
|
gname = g["name"] if g else "?"
|
|
if dry:
|
|
print(f"DRY {mid} '{m['name']}' -> group {gid} '{gname}'")
|
|
continue
|
|
try:
|
|
r = api.edit_monitor(mid, parent=gid, dns_resolve_type="A")
|
|
ok = isinstance(r, dict) and ("Saved" in str(r.get("msg", "")))
|
|
print(f"MOVE {mid} '{m['name']}' -> {gid} '{gname}': {r.get('msg') if ok else r}")
|
|
if not ok:
|
|
rc = 1
|
|
except Exception as e: # noqa: BLE001 — per-move isolation, batch continues
|
|
print(f"MOVE {mid} FAIL: {str(e)[:120]}")
|
|
rc = 1
|
|
finally:
|
|
api.disconnect()
|
|
return rc
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|