93 lines
3.5 KiB
Python
93 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|||
|
|
"""Live health check for the attack-defense platform after the PixiJS work.
|
||
|
|
|
||
|
|
Uses the panel's own API with credentials read from .env, so no shell quoting
|
||
|
|
hazard and no password on a command line.
|
||
|
|
"""
|
||
|
|
import json
|
||
|
|
import subprocess
|
||
|
|
import urllib.request
|
||
|
|
import http.cookiejar
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
BASE = "http://127.0.0.1:18081"
|
||
|
|
ENV = Path("/opt/gemastik18-final/panel/.env")
|
||
|
|
|
||
|
|
|
||
|
|
def load_env():
|
||
|
|
cfg = {}
|
||
|
|
for line in ENV.read_text().splitlines():
|
||
|
|
line = line.strip()
|
||
|
|
if not line or line.startswith("#") or "=" not in line:
|
||
|
|
continue
|
||
|
|
k, _, v = line.partition("=")
|
||
|
|
cfg[k.strip()] = v.strip().strip('"').strip("'")
|
||
|
|
return cfg
|
||
|
|
|
||
|
|
|
||
|
|
def get(url, cookie=None):
|
||
|
|
req = urllib.request.Request(url)
|
||
|
|
if cookie:
|
||
|
|
req.add_header("Cookie", "; ".join(f"{c.name}={c.value}" for c in cookie))
|
||
|
|
with urllib.request.urlopen(req, timeout=30) as r:
|
||
|
|
return r.read().decode()
|
||
|
|
|
||
|
|
|
||
|
|
def post_json(url, payload, cookie=None):
|
||
|
|
data = json.dumps(payload).encode()
|
||
|
|
req = urllib.request.Request(url, data=data, method="POST",
|
||
|
|
headers={"Content-Type": "application/json"})
|
||
|
|
if cookie:
|
||
|
|
req.add_header("Cookie", "; ".join(f"{c.name}={c.value}" for c in cookie))
|
||
|
|
with urllib.request.urlopen(req, timeout=30) as r:
|
||
|
|
return r.read().decode()
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
cfg = load_env()
|
||
|
|
jar = http.cookiejar.CookieJar()
|
||
|
|
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(jar))
|
||
|
|
|
||
|
|
# main.py's /api/login expects {"user","pass"} — not username/password.
|
||
|
|
body = json.dumps({"user": cfg["PANEL_ADMIN_USER"],
|
||
|
|
"pass": cfg["PANEL_ADMIN_PASS"]}).encode()
|
||
|
|
req = urllib.request.Request(BASE + "/api/login", data=body, method="POST",
|
||
|
|
headers={"Content-Type": "application/json"})
|
||
|
|
with opener.open(req, timeout=30) as r:
|
||
|
|
login = json.loads(r.read().decode())
|
||
|
|
print("login:", login)
|
||
|
|
|
||
|
|
cookie = jar
|
||
|
|
teams_raw = json.loads(get(BASE + "/api/teams", cookie))
|
||
|
|
teams = teams_raw.get("teams", teams_raw) if isinstance(teams_raw, dict) else teams_raw
|
||
|
|
print("\nteams:")
|
||
|
|
for t in teams:
|
||
|
|
print(f" #{t.get('index')} {t.get('label')} domain={t.get('domain')} "
|
||
|
|
f"receivers={t.get('receiver_port')} challenges={len(t.get('challenges') or [])}")
|
||
|
|
|
||
|
|
topo = json.loads(get(BASE + "/api/topology", cookie))
|
||
|
|
print("\ntopology API:",
|
||
|
|
{k: (len(v) if isinstance(v, list) else v) for k, v in topo.items()})
|
||
|
|
|
||
|
|
containers = subprocess.run(
|
||
|
|
["docker", "ps", "--format", "{{.Names}}"],
|
||
|
|
capture_output=True, text=True, timeout=60).stdout.split()
|
||
|
|
team_ct = [c for c in containers if c.endswith(tuple(f"_team{i}" for i in range(1, 10)))]
|
||
|
|
orphans = [c for c in containers
|
||
|
|
if "_container" in c and not any(c.endswith(f"_team{i}") for i in range(1, 10))]
|
||
|
|
print(f"\ncontainers: {len(team_ct)} team-scoped, orphans={orphans}")
|
||
|
|
|
||
|
|
services = subprocess.run(
|
||
|
|
["systemctl", "is-active",
|
||
|
|
"gemastik-panel", "gemastik-receiver-service",
|
||
|
|
"gemastik-receiver-team1", "gemastik-receiver-team2"],
|
||
|
|
capture_output=True, text=True, timeout=60).stdout.strip()
|
||
|
|
print("services:\n ", services.replace("\n", "\n "))
|
||
|
|
|
||
|
|
load = subprocess.run(["uptime"], capture_output=True, text=True).stdout.strip()
|
||
|
|
print("load:", load)
|
||
|
|
print("expected team containers:", len(teams) * 16, "| actual:", len(team_ct))
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|