diff --git a/scripts/homepage-status.py b/scripts/homepage-status.py new file mode 100755 index 0000000..3e49f62 --- /dev/null +++ b/scripts/homepage-status.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +"""homepage-status.py — the homepage status checker. + +Reads a service config (a JSON array of service entries), probes each backend, +and writes a status.json atomically. A wip service is not probed. + +Usage: homepage-status.py +""" +import json +import os +import socket +import sys +import tempfile +from datetime import datetime, timezone + +TIMEOUT = 5 # seconds, per probe (Q11 / Q40) + + +def probe(host, port, method="tcp"): + """Return 'up' if the backend answers, else 'down'.""" + try: + with socket.create_connection((host, port), timeout=TIMEOUT): + return "up" + except Exception: + return "down" + + +def main(argv): + if len(argv) != 3: + sys.stderr.write("usage: homepage-status.py \n") + return 2 + services_path, out_path = argv[1], argv[2] + with open(services_path) as f: + services = json.load(f) + + results = [] + for svc in services: + if svc.get("wip"): + state = "wip" + else: + host, _, port = svc["probe"].rpartition(":") + state = probe(host, int(port), svc.get("method", "tcp")) + results.append({ + "name": svc.get("name"), + "category": svc.get("category", "user"), + "state": state, + }) + + doc = { + "generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "services": results, + } + + # Atomic write: a temp file in the same directory, then rename over the target. + out_dir = os.path.dirname(os.path.abspath(out_path)) + fd, tmp = tempfile.mkstemp(dir=out_dir, prefix=".status.", suffix=".tmp") + try: + with os.fdopen(fd, "w") as f: + json.dump(doc, f) + os.replace(tmp, out_path) + except Exception: + if os.path.exists(tmp): + os.unlink(tmp) + raise + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/tests/homepage_checker.bats b/tests/homepage_checker.bats new file mode 100644 index 0000000..edf5090 --- /dev/null +++ b/tests/homepage_checker.bats @@ -0,0 +1,45 @@ +#!/usr/bin/env bats +# Seam: scripts/homepage-status.py +# Given a service config, the checker writes a valid status.json with the +# correct state per service: an open port is up, a closed port is down, +# a wip service is wip (not probed). The output carries generated_at. + +load helpers + +setup() { + CHECKER="$REPO_ROOT/scripts/homepage-status.py" + TMP="$(mktemp -d)" + UP_PORT=18899 + DOWN_PORT=18900 + # Hold a TCP listener on UP_PORT so a probe of it succeeds. + python3 -c 'import socket,time; s=socket.socket(); s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1); s.bind(("127.0.0.1",'"$UP_PORT"')); s.listen(5); time.sleep(300)' & + LISTENER_PID=$! + sleep 0.5 +} + +teardown() { + [ -n "${LISTENER_PID:-}" ] && kill "$LISTENER_PID" 2>/dev/null + rm -rf "$TMP" +} + +@test "checker: open port is up, closed port is down, wip is wip" { + local svc="$TMP/services.json" out="$TMP/status.json" + cat > "$svc" <