154 lines
5.9 KiB
Python
154 lines
5.9 KiB
Python
"""Network / Headscale / AdGuard routes."""
|
|
|
|
from fastapi import APIRouter
|
|
import subprocess
|
|
import json
|
|
import httpx
|
|
|
|
router = APIRouter(tags=["network"])
|
|
|
|
CLOUDFLARE_TOKEN = "REDACTED_TOKEN" # pragma: allowlist secret
|
|
CLOUDFLARE_ZONE_ID = "4dbd15d096d71101b7c0c6362b307a66"
|
|
AUTHENTIK_URL = "https://sso.vish.gg"
|
|
AUTHENTIK_TOKEN = "REDACTED_TOKEN" # pragma: allowlist secret
|
|
GITEA_URL = "https://git.vish.gg"
|
|
GITEA_TOKEN = "REDACTED_TOKEN" # pragma: allowlist secret
|
|
|
|
ADGUARD_URL = "http://192.168.0.250:9080"
|
|
ADGUARD_USER = "vish"
|
|
ADGUARD_PASS = "REDACTED_PASSWORD"
|
|
|
|
|
|
def _adguard_get(path):
|
|
with httpx.Client(timeout=10) as client:
|
|
client.post(f"{ADGUARD_URL}/control/login", json={"name": ADGUARD_USER, "password": ADGUARD_PASS})
|
|
r = client.get(f"{ADGUARD_URL}/control{path}")
|
|
r.raise_for_status()
|
|
return r.json() if r.content else {}
|
|
|
|
|
|
@router.get("/network/headscale")
|
|
def headscale_nodes():
|
|
"""List Headscale nodes."""
|
|
result = subprocess.run(
|
|
["ssh", "-o", "ConnectTimeout=3", "calypso",
|
|
"docker exec headscale headscale nodes list -o json"],
|
|
capture_output=True, text=True, timeout=15,
|
|
)
|
|
if result.returncode != 0:
|
|
return {"nodes": [], "error": result.stderr.strip()}
|
|
nodes = json.loads(result.stdout)
|
|
return {"nodes": [
|
|
{"name": n.get("givenName") or n.get("name", "?"),
|
|
"ip": (n.get("ipAddresses") or ["?"])[0],
|
|
"online": n.get("online", False),
|
|
"last_seen": n.get("lastSeen", "")}
|
|
for n in nodes
|
|
]}
|
|
|
|
|
|
@router.get("/network/adguard")
|
|
def adguard_stats():
|
|
"""Get AdGuard DNS stats."""
|
|
try:
|
|
stats = _adguard_get("/stats")
|
|
return {
|
|
"total_queries": stats.get("num_dns_queries", 0),
|
|
"blocked": stats.get("num_blocked_filtering", 0),
|
|
"avg_time": stats.get("avg_processing_time", 0),
|
|
}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
@router.get("/network/adguard/rewrites")
|
|
def adguard_rewrites():
|
|
"""List AdGuard DNS rewrites."""
|
|
try:
|
|
data = _adguard_get("/rewrite/list")
|
|
return [{"domain": r.get("domain", ""), "answer": r.get("answer", "")} for r in (data or [])]
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
@router.get("/network/cloudflare")
|
|
def cloudflare_stats():
|
|
"""Cloudflare DNS record summary."""
|
|
try:
|
|
with httpx.Client(timeout=10) as client:
|
|
r = client.get(f"https://api.cloudflare.com/client/v4/zones/{CLOUDFLARE_ZONE_ID}/dns_records",
|
|
headers={"Authorization": f"Bearer {CLOUDFLARE_TOKEN}"},
|
|
params={"per_page": 100})
|
|
r.raise_for_status()
|
|
records = r.json().get("result", [])
|
|
proxied = sum(1 for rec in records if rec.get("proxied"))
|
|
types = {}
|
|
for rec in records:
|
|
t = rec.get("type", "?")
|
|
types[t] = types.get(t, 0) + 1
|
|
return {"total": len(records), "proxied": proxied, "dns_only": len(records) - proxied, "types": types}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
@router.get("/network/authentik")
|
|
def authentik_sessions():
|
|
"""Authentik active sessions and recent events."""
|
|
try:
|
|
with httpx.Client(timeout=10, verify=False) as client:
|
|
# Sessions
|
|
sr = client.get(f"{AUTHENTIK_URL}/api/v3/core/authenticated_sessions/",
|
|
headers={"Authorization": f"Bearer {AUTHENTIK_TOKEN}"})
|
|
sessions = sr.json().get("results", []) if sr.status_code == 200 else []
|
|
# Recent events
|
|
er = client.get(f"{AUTHENTIK_URL}/api/v3/events/events/",
|
|
headers={"Authorization": f"Bearer {AUTHENTIK_TOKEN}"},
|
|
params={"page_size": 10, "ordering": "-created"})
|
|
events = er.json().get("results", []) if er.status_code == 200 else []
|
|
return {
|
|
"active_sessions": len(sessions),
|
|
"sessions": [{"user": s.get("user", {}).get("username", "?"),
|
|
"last_used": s.get("last_used_at", "?")} for s in sessions[:10]],
|
|
"recent_events": [{"action": e.get("action", "?"), "user": e.get("user", {}).get("username", "?"),
|
|
"created": e.get("created", "?")[:19]} for e in events[:5]],
|
|
}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|
|
|
|
|
|
@router.get("/network/gitea")
|
|
def gitea_activity():
|
|
"""Recent Gitea commits and open PRs."""
|
|
try:
|
|
with httpx.Client(timeout=10) as client:
|
|
# Recent commits
|
|
cr = client.get(f"{GITEA_URL}/api/v1/repos/vish/homelab/commits",
|
|
headers={"Authorization": f"token {GITEA_TOKEN}"},
|
|
params={"limit": 5, "sha": "main"})
|
|
commits = []
|
|
if cr.status_code == 200:
|
|
for c in cr.json()[:5]:
|
|
commits.append({
|
|
"sha": c.get("sha", "?")[:7],
|
|
"message": c.get("commit", {}).get("message", "?").split("\n")[0][:80],
|
|
"date": c.get("commit", {}).get("committer", {}).get("date", "?")[:10],
|
|
"author": c.get("commit", {}).get("author", {}).get("name", "?"),
|
|
})
|
|
|
|
# Open PRs
|
|
pr = client.get(f"{GITEA_URL}/api/v1/repos/vish/homelab/pulls",
|
|
headers={"Authorization": f"token {GITEA_TOKEN}"},
|
|
params={"state": "open", "limit": 5})
|
|
prs = []
|
|
if pr.status_code == 200:
|
|
for p in pr.json():
|
|
prs.append({
|
|
"number": p.get("number"),
|
|
"title": p.get("title", "?"),
|
|
"user": p.get("user", {}).get("login", "?"),
|
|
})
|
|
|
|
return {"commits": commits, "open_prs": prs}
|
|
except Exception as e:
|
|
return {"error": str(e)}
|