feat: absorb cloudron kuma monitoring from PFVCluster [#727]

monitoring/ = cloudron-apps.txt (55-app inventory, #435) +
kuma-cloudron-sync.py (coverage checker, path re-rooted).
Removed from PFVCluster in the paired commit there.
Meat: https://projects.knownelement.com/issues/727#note-3913
This commit is contained in:
2026-09-02 18:51:04 -05:00
parent 0e67f17112
commit 4806e6f24d
4 changed files with 191 additions and 0 deletions
+6
View File
@@ -5,3 +5,9 @@
# When issued, store the material value in ~/.creds/cloudron.env as: # When issued, store the material value in ~/.creds/cloudron.env as:
# CLOUDRON_API_TOKEN=<token> # CLOUDRON_API_TOKEN=<token>
CLOUDRON_API_TOKEN= CLOUDRON_API_TOKEN=
# Uptime Kuma (monitoring/kuma-cloudron-sync.py) — material value in ~/.creds/uptime-kuma.env
# UPTIME_KUMA_URL=
# UPTIME_KUMA_TOKEN=
# Uptime Kuma (monitoring/kuma-cloudron-sync.py) — material value in ~/.creds/uptime-kuma.env
# UPTIME_KUMA_URL=
# UPTIME_KUMA_TOKEN=
+1
View File
@@ -12,6 +12,7 @@ Postgres/MySQL/mail containers, monitored by Uptime Kuma (Cloudron group).
| Path | What | | Path | What |
|------|------| |------|------|
| `perf/` | [#685](https://projects.knownelement.com/issues/685) perf work: memory census, pg role-cap SQL, applied sysctl, planned docker daemon.json | | `perf/` | [#685](https://projects.knownelement.com/issues/685) perf work: memory census, pg role-cap SQL, applied sysctl, planned docker daemon.json |
| `monitoring/` | Uptime Kuma Cloudron app coverage — 55-app inventory + sync checker (moved from PFVCluster, #435/#685/#727) |
| `ansible/` | Reserved — AWX/Ansible IaC convergence ([#454](https://projects.knownelement.com/issues/454)); playbooks land here as manual changes are codified | | `ansible/` | Reserved — AWX/Ansible IaC convergence ([#454](https://projects.knownelement.com/issues/454)); playbooks land here as manual changes are codified |
| `scripts/` | Rule engine + git hooks (enforcement layer, ADOPTING pattern) | | `scripts/` | Rule engine + git hooks (enforcement layer, ADOPTING pattern) |
| `tests/` | shellcheck wrapper (Docker — host stays clean) | | `tests/` | shellcheck wrapper (Docker — host stays clean) |
+62
View File
@@ -0,0 +1,62 @@
ap4ap.org
bi.knownelement.com
booking.rackrental.net
booking.reachableceo.com
booking.roguetechnologies.net
booking.startinglineproductions.com
booking.teamrental.net
booking.thepeernet.com
bookmarks.knownelement.com
broadcast.thepeernet.com
broadcast.turnsys.com
building-management.knownelement.com
chat.knownelement.com
community.turnsys.com
design.knownelement.com
erp.knownelement.com
esign.knownelement.com
ezeda.org
ezpodstack.org
finance.turnsys.com
forms.knownelement.com
git.knownelement.com
grafana.knownelement.com
helpdesk.knownelement.com
hfnfc.net
hfnoc.net
ipam.knownelement.com
jupyter.knownelement.com
knownelement.com
learn.turnsys.com
marketing.turnsys.com
matrix.turnsys.com
meetmorse.com
merchantsofhope.org
my.knownelement.com
nextcloud.knownelement.com
paperless.knownelement.com
photos.knownelement.com
phplist.knownelement.com
projects.knownelement.com
pwvault.turnsys.com
rackrental.net
reachableceo.com
readlater.knownelement.com
recordings.thepeernet.com
redwfo.com
rss.knownelement.com
rwscp.net
sidedoorgroup.org
sol-calc.com
startinglineproductions.com
staticbits.knownelement.com
status.knownelement.com
suborbital-systems.com
surveys.knownelement.com
thepeernet.com
turnsys.com
uksrs.org
url.knownelement.com
webmail.knownelement.com
webstats.knownelement.com
whiteboard.knownelement.com
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env python3
"""kuma-cloudron-sync.py — Uptime Kuma Cloudron app coverage sync [#435]
Compares the committed Cloudron app list (monitoring/cloudron-apps.txt, ex-PFVCluster) against
live Kuma http monitors, reports coverage gaps, and (with --add) creates the
missing monitors INSIDE the "Cloud Systems" group (never root — founder
ruling 2026-09-02: monitors go in groups).
App list provenance: CT-log enumeration + DNS resolve + HTTPS probe across
all 23 Kuma-monaged domains (2026-09-02, see #435 note). Re-derive with
/tmp-style probe pipeline or a read-only Cloudron API diff when a token
lands. Two apps (forms/learn) return 500 today — monitored red on purpose
(#681).
Env (from ~/.creds/uptime-kuma.env):
UPTIME_KUMA_URL, UPTIME_KUMA_TOKEN
Usage:
python3 kuma-cloudron-sync.py [--add] [--limit N] [--group-id ID]
--add create missing monitors
--limit N add at most N (canary mode)
--group-id ID parent group (default: 48 = "Cloud Systems")
"""
import argparse
import os
import sys
import time
from pathlib import Path
import socketio
HERE = Path(__file__).resolve().parent / "cloudron-apps.txt"
DEFAULT_GROUP = 277 # "Cloudron" group (founder-directed 2026-09-02)
def load_apps():
apps = []
for line in HERE.read_text().splitlines():
line = line.split("#", 1)[0].strip()
if line:
apps.append(line)
return apps
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("--add", action="store_true")
ap.add_argument("--limit", type=int, default=0)
ap.add_argument("--group-id", type=int, default=DEFAULT_GROUP)
args = ap.parse_args()
apps = load_apps()
sio, mons = kuma_connect()
try:
covered = set()
for m in mons.values():
name = (m.get("name") or "").strip().lower()
url = (m.get("url") or "").strip().lower()
for fqdn in apps:
if fqdn == name or url.rstrip("/") in (
f"https://{fqdn}".rstrip("/"),
f"https://{fqdn}/".rstrip("/"),
):
covered.add(fqdn)
missing = [a for a in apps if a not in covered]
print(f"apps in list: {len(apps)} covered: {len(covered)} missing: {len(missing)}")
for fqdn in missing:
print(f" MISSING {fqdn}")
if args.add and missing:
batch = missing if not args.limit else missing[: args.limit]
for fqdn in batch:
payload = {
"type": "http",
"name": fqdn,
"url": f"https://{fqdn}/",
"interval": 60,
"retryInterval": 60,
"resendInterval": 0,
"maxretries": 2,
"notificationIDList": {"1": True, "2": True},
"upsideDown": False,
"description": "Cloudron app HTTP 200 (#435)",
"httpBodyEncoding": "json",
"accepted_statuscodes": ["200-299"],
"conditions": [],
"active": True,
"parent": args.group_id,
"maxredirects": 10,
}
r = sio.call("add", payload, timeout=30)
ok = isinstance(r, dict) and r.get("ok")
print(f" ADD {fqdn}: {'id=' + str(r.get('monitorID')) if ok else r}")
finally:
sio.disconnect()
if __name__ == "__main__":
main()