Neue zentrale Moeglichkeit, ganze Proxmox-Hosts temporaer aus allen Hausmeister-Alerts herauszunehmen, gesteuert ueber homelab.conf. - homelab.conf: ALERT_SUPPRESS_HOSTS Variable (Komma-Liste) initial gesetzt auf pve-ka-1,pve-ka-2,pve-ka-3 (Kambodscha ohne Internet seit 2026-04-04) - core/config.py: Helper get_suppressed_hosts + get_suppressed_container_names (letztere mappt Host -> Container-Namen fuer Loki/Silence-Filter) - tools/predict.py: Filter in _gather_prometheus, _gather_loki, _gather_proxmox + sichtbarer Hinweis 'Unterdrueckt: ...' im taeglichen Forecast-Report - monitor.py: Filter in check_all + format_report (Container, panic, error_rate, silence, service_restarts) Beim naechsten Mal Kambodscha-Internet-Comeback: einfach die drei Eintraege in ALERT_SUPPRESS_HOSTS entfernen und hausmeister-bot neu starten.
213 lines
6.2 KiB
Python
213 lines
6.2 KiB
Python
"""Parses homelab.conf — the single source of truth for infrastructure facts."""
|
|
|
|
import os
|
|
import re
|
|
from pathlib import Path
|
|
from dataclasses import dataclass, field
|
|
|
|
HOMELAB_CONF_PATHS = [
|
|
Path("/root/homelab-brain/homelab.conf"),
|
|
Path("/opt/homelab-brain/homelab.conf"),
|
|
]
|
|
|
|
|
|
@dataclass
|
|
class Container:
|
|
vmid: int
|
|
name: str
|
|
tailscale_ip: str
|
|
services: str
|
|
host: str # pve-hetzner, pve-ka-1, pve-mu-3, etc.
|
|
|
|
|
|
@dataclass
|
|
class Tunnel:
|
|
ct_id: int
|
|
domain: str
|
|
target: str
|
|
status: str
|
|
|
|
|
|
@dataclass
|
|
class HomelabConfig:
|
|
raw: dict = field(default_factory=dict)
|
|
domains: dict = field(default_factory=dict)
|
|
servers: dict = field(default_factory=dict)
|
|
passwords: dict = field(default_factory=dict)
|
|
containers: list = field(default_factory=list)
|
|
telegram: dict = field(default_factory=dict)
|
|
api_keys: dict = field(default_factory=dict)
|
|
tunnels: list = field(default_factory=list)
|
|
|
|
|
|
HOST_CODE_MAP = {
|
|
"HZ": "pve-hetzner",
|
|
"KA1": "pve-ka-1",
|
|
"KA2": "pve-ka-2",
|
|
"KA3": "pve-ka-3",
|
|
"MU1": "pve-mu-1",
|
|
"MU2": "pve-mu-2",
|
|
"MU3": "pve-mu-3",
|
|
"HE": "pve-he",
|
|
}
|
|
|
|
|
|
def find_config() -> Path:
|
|
for p in HOMELAB_CONF_PATHS:
|
|
if p.exists():
|
|
return p
|
|
raise FileNotFoundError(f"homelab.conf not found in {HOMELAB_CONF_PATHS}")
|
|
|
|
|
|
def parse_config(path: Path = None) -> HomelabConfig:
|
|
if path is None:
|
|
path = find_config()
|
|
|
|
raw = {}
|
|
with open(path) as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
m = re.match(r'^([A-Za-z_][A-Za-z0-9_]*)="?(.*?)"?\s*$', line)
|
|
if m:
|
|
raw[m.group(1)] = m.group(2)
|
|
|
|
cfg = HomelabConfig(raw=raw)
|
|
|
|
for k, v in raw.items():
|
|
if k.startswith("DOMAIN_"):
|
|
cfg.domains[k.replace("DOMAIN_", "").lower()] = v
|
|
elif k.startswith("SRV_"):
|
|
cfg.servers[k.replace("SRV_", "").lower()] = v
|
|
elif k.startswith("PW_"):
|
|
cfg.passwords[k.replace("PW_", "").lower()] = v
|
|
elif k.startswith("TG_"):
|
|
cfg.telegram[k.lower()] = v
|
|
elif k.startswith("FORGEJO_") or k.startswith("GITHUB_") or k.startswith("OPENROUTER_"):
|
|
cfg.api_keys[k.lower()] = v
|
|
|
|
ct_pattern = re.compile(r"^(?:CT|VM)_(\d+)_([A-Z][A-Z0-9]+)$")
|
|
|
|
for k, v in raw.items():
|
|
m = ct_pattern.match(k)
|
|
if m:
|
|
vmid = int(m.group(1))
|
|
host_code = m.group(2)
|
|
host = HOST_CODE_MAP.get(host_code, host_code.lower())
|
|
|
|
parts = v.split("|")
|
|
if len(parts) >= 3:
|
|
cfg.containers.append(Container(
|
|
vmid=vmid,
|
|
name=parts[0],
|
|
tailscale_ip=parts[1] if parts[1] != "—" else "",
|
|
services=parts[2],
|
|
host=host,
|
|
))
|
|
|
|
for k, v in raw.items():
|
|
m = re.match(r"^TUNNEL_(\d+)(?:_\w+)?$", k)
|
|
if m:
|
|
ct_id = int(m.group(1))
|
|
parts = v.split("|")
|
|
if len(parts) >= 3:
|
|
cfg.tunnels.append(Tunnel(
|
|
ct_id=ct_id,
|
|
domain=parts[0],
|
|
target=parts[1],
|
|
status=parts[2],
|
|
))
|
|
|
|
cfg.containers.sort(key=lambda c: (c.host, c.vmid))
|
|
|
|
from core import proxmox_client, loki_client
|
|
srv_to_host = {
|
|
"hetzner": "pve-hetzner",
|
|
"ka1": "pve-ka-1", "ka2": "pve-ka-2", "ka3": "pve-ka-3",
|
|
"mu1": "pve-mu-1", "mu2": "pve-mu-2", "mu3": "pve-mu-3",
|
|
"he": "pve-he",
|
|
}
|
|
for key, ip in cfg.servers.items():
|
|
host_name = srv_to_host.get(key, key)
|
|
if not key.endswith("_local") and not key.endswith("_hostname") and "pbs" not in key:
|
|
proxmox_client.PROXMOX_HOSTS[host_name] = ip
|
|
|
|
if raw.get("LOKI_URL"):
|
|
loki_client.LOKI_URL = raw["LOKI_URL"]
|
|
|
|
return cfg
|
|
|
|
|
|
def get_container(cfg: HomelabConfig, vmid: int = None, name: str = None, host: str = None) -> Container | None:
|
|
for c in cfg.containers:
|
|
if host and c.host != host:
|
|
continue
|
|
if vmid and c.vmid == vmid:
|
|
return c
|
|
if name and name.lower() in c.name.lower():
|
|
return c
|
|
return None
|
|
|
|
|
|
def get_containers_by_host(cfg: HomelabConfig, host: str) -> list[Container]:
|
|
return [c for c in cfg.containers if c.host == host]
|
|
|
|
|
|
def format_overview(cfg: HomelabConfig) -> str:
|
|
lines = ["# Homelab Infrastructure (from homelab.conf)\n"]
|
|
|
|
lines.append("## Domains")
|
|
for k, v in cfg.domains.items():
|
|
lines.append(f"- {k}: {v}")
|
|
|
|
lines.append("\n## Servers (Tailscale)")
|
|
for k, v in cfg.servers.items():
|
|
lines.append(f"- {k}: {v}")
|
|
|
|
current_host = None
|
|
for c in cfg.containers:
|
|
if c.host != current_host:
|
|
current_host = c.host
|
|
lines.append(f"\n## Containers on {current_host}")
|
|
lines.append("| CT | Name | Tailscale | Services |")
|
|
lines.append("|---|---|---|---|")
|
|
ts = c.tailscale_ip or "—"
|
|
lines.append(f"| {c.vmid} | {c.name} | {ts} | {c.services} |")
|
|
|
|
if cfg.tunnels:
|
|
lines.append("\n## Cloudflare Tunnels")
|
|
for t in cfg.tunnels:
|
|
lines.append(f"- CT {t.ct_id}: {t.domain} → {t.target} ({t.status})")
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def get_suppressed_hosts(cfg: HomelabConfig) -> set:
|
|
"""Hosts (pve-*/pbs-*) die aus Hausmeister-Alerts herausgefiltert werden sollen.
|
|
|
|
Liest ALERT_SUPPRESS_HOSTS aus homelab.conf (Komma-Liste).
|
|
Leer = keine Unterdrueckung.
|
|
"""
|
|
raw = cfg.raw.get("ALERT_SUPPRESS_HOSTS", "")
|
|
return {h.strip() for h in raw.split(",") if h.strip()}
|
|
|
|
|
|
def get_suppressed_container_names(cfg: HomelabConfig) -> set:
|
|
"""Container-Namen (lowercased) auf suppressed Hosts.
|
|
|
|
Fuer Loki/Silence-Filter, wo Loki mit Container-Labels wie
|
|
ct-101-freshrss oder webcam arbeitet statt mit Proxmox-Hostnamen.
|
|
"""
|
|
hosts = get_suppressed_hosts(cfg)
|
|
names = set()
|
|
for c in cfg.containers:
|
|
if c.host not in hosts:
|
|
continue
|
|
n = (c.name or "").strip().lower()
|
|
if n:
|
|
names.add(n)
|
|
names.add(f"ct-{c.vmid}-{n}")
|
|
names.add(f"ct{c.vmid}")
|
|
names.add(f"ct-{c.vmid}")
|
|
return names
|