#!/usr/bin/env python3 """ TP37 Punkt 2 (Dienste) — exponierte Ports als native ipam.Service-Objekte. Idempotenter Seeder (get-or-create) fuer ipam.Service: je VM-Name + Port + Protokoll ein Service-Eintrag. Vorbedingung: TP37 P1 (tp37-substrat-seed.py --commit) muss vorher gelaufen sein, da die VirtualMachine-Objekte existieren muessen. Datenmodell-Entscheid (User 2026-09-06): native Modelle, KEINE eigene App. Muster: nautobot/tp37-substrat-seed.py. Umfang (User-Default 2026-09-07): Caddy (:80/:443), Caddy-Backends (haos:8123, vaultwarden:80, gitea:3000, n8n:5678), Nautobot:8080, AdGuard:53 (bf1+bf2, tcp+udp), platform-pg:5432, mariadb:3306, influxdb2:8086, mqtt5:1883, portainer:9443, MCP-Dienste (pve-mcp:3000, shelly-mcp:3030, n8n-mcp:3001, gitea-mcp:3000). + Smart-Home (vervollstaendigt 2026-09-08, Ports per Socket-Probe verifiziert): RaspberryMatic/CCU (80/443/2001/2010), iob-master:8081, z2m25:8080. iob-exposed/iob_slave BEWUSST ohne Service (kein exponierter Port gefunden). Token-Quelle: docs/nas-vlan20/stacks/210-nautobot.yml (gitignored), Schluessel NAUTOBOT_SUPERUSER_API_TOKEN. Aufruf: python3 nautobot/tp37-dienste-seed.py # Dry-Run (default) python3 nautobot/tp37-dienste-seed.py --commit # legt an """ import json import os import re import sys import urllib.request import urllib.parse import urllib.error from pathlib import Path NB = os.environ.get("NAUTOBOT_URL", "http://10.40.20.154:8080") REPO = Path(__file__).resolve().parent.parent YML = REPO / "docs" / "nas-vlan20" / "stacks" / "210-nautobot.yml" COMMIT = "--commit" in sys.argv def load_token() -> str: for line in YML.read_text().splitlines(): m = re.search(r"NAUTOBOT_SUPERUSER_API_TOKEN[=:]\s*([^\s\"']+)", line) if m: return m.group(1) sys.exit(f"NAUTOBOT_SUPERUSER_API_TOKEN nicht in {YML} gefunden") TOKEN = load_token() HDRS = {"Authorization": f"Token {TOKEN}", "Accept": "application/json", "Content-Type": "application/json"} def req(method: str, path: str, body=None): url = f"{NB}{path}" data = json.dumps(body).encode() if body is not None else None r = urllib.request.Request(url, data=data, headers=HDRS, method=method) try: with urllib.request.urlopen(r, timeout=20) as resp: return resp.status, json.loads(resp.read().decode() or "{}") except urllib.error.HTTPError as e: return e.code, json.loads(e.read().decode() or "{}") def find(endpoint: str, params: dict, match_key: str, match_val: str): q = urllib.parse.urlencode(dict(params, depth=0)) st, d = req("GET", f"/api/{endpoint}/?{q}") for row in d.get("results", []): if row.get(match_key) == match_val: return row return None def get_or_create(endpoint: str, find_params: dict, match_key: str, match_val: str, payload: dict, label: str): existing = find(endpoint, find_params, match_key, match_val) if existing: print(f" = {label}: '{match_val}' existiert ({existing['id']})") return existing if not COMMIT: print(f" + {label}: '{match_val}' [DRY-RUN]") return {"id": f""} st, d = req("POST", f"/api/{endpoint}/", payload) if st in (200, 201): print(f" + {label}: '{match_val}' angelegt ({d.get('id')})") return d print(f" ! {label}: '{match_val}' FEHLER {st}: {json.dumps(d)[:300]}") return None # ---------------------------------------------------------------- Inventar # (vm_name, svc_name, ports, protocol, description) SERVICES = [ # Caddy Reverse-Proxy (TP34 critical) ("caddy-bf1", "caddy-http", [80], "tcp", "Caddy HTTP"), ("caddy-bf1", "caddy-https", [443], "tcp", "Caddy HTTPS Reverse Proxy"), # Caddy-Backends via bf-public Tunnel ("haos", "home-assistant", [8123], "tcp", "Home Assistant Web"), ("vaultwarden", "vaultwarden-http", [80], "tcp", "Vaultwarden (HTTP, TLS via Caddy)"), ("gitea", "gitea-http", [3000], "tcp", "Gitea Web"), ("n8n", "n8n-web", [5678], "tcp", "n8n Workflow Engine"), # Nautobot ("nautobot", "nautobot-web", [8080], "tcp", "Nautobot API/Web"), # DNS (TCP + UDP je Resolver) ("agh-bf1", "adguard-dns-tcp", [53], "tcp", "AdGuard Home DNS/TCP"), ("agh-bf1", "adguard-dns-udp", [53], "udp", "AdGuard Home DNS/UDP"), ("agh-bf2", "adguard-dns-tcp", [53], "tcp", "AdGuard Home DNS/TCP"), ("agh-bf2", "adguard-dns-udp", [53], "udp", "AdGuard Home DNS/UDP"), # Infrastruktur ("platform-pg", "postgresql", [5432], "tcp", "PostgreSQL 16 (Nautobot-DB)"), ("mariadb", "mariadb", [3306], "tcp", "MariaDB (vw/hass/gitea)"), ("influxdb2", "influxdb", [8086], "tcp", "InfluxDB 2"), ("mqtt5", "mqtt", [1883], "tcp", "MQTT Broker (Mosquitto)"), ("docker", "portainer", [9443], "tcp", "Portainer CE"), # MCP-Dienste ("pve-mcp", "pve-mcp", [3000], "tcp", "Proxmox MCP (StreamableHTTP)"), ("shelly-mcp", "shelly-mcp", [3030], "tcp", "Shelly MCP"), ("n8n-mcp", "n8n-mcp", [3001], "tcp", "n8n MCP"), ("gitea-mcp", "gitea-mcp", [3000], "tcp", "Gitea MCP"), # Smart-Home / IoT — Ports live per Socket-Probe verifiziert 2026-09-08 (NICHT raten!) ("RaspberryMatic","ccu-http", [80], "tcp", "Homematic CCU Web (HTTP)"), ("RaspberryMatic","ccu-https", [443], "tcp", "Homematic CCU Web (HTTPS)"), ("RaspberryMatic","ccu-rpc-bidcos", [2001], "tcp", "Homematic BidCos-RF XML-RPC"), ("RaspberryMatic","ccu-rpc-hmip", [2010], "tcp", "Homematic-IP XML-RPC"), ("iob-master", "iobroker-admin", [8081], "tcp", "ioBroker Admin"), ("z2m25", "zigbee2mqtt", [8080], "tcp", "Zigbee2MQTT Frontend"), # iob-exposed (.19) + iob_slave (.33): kein exponierter Port gefunden (13 Ports, alle Timeout) # -> BEWUSST kein Service, bis ein Port live belegt ist. Nicht "hilfreich" einen erfinden. ] def main(): print(f"== TP37 Dienste-Seed ({'COMMIT' if COMMIT else 'DRY-RUN'}) @ {NB} ==") # VM-IDs vorab holen (unique Namen, Batch) vm_names = sorted({vm for vm, *_ in SERVICES}) print(f"\n[0] VM-IDs holen ({len(vm_names)} unique VMs)") vm_ids = {} for name in vm_names: row = find("virtualization/virtual-machines", {"name": name}, "name", name) if row: vm_ids[name] = row["id"] print(f" = {name}: {row['id']}") else: vm_ids[name] = None print(f" ! {name}: NICHT gefunden — Service-Eintraege werden uebersprungen") print(f"\n[1] ipam.Services ({len(SERVICES)} Eintraege)") n_ok = n_skip = 0 for vm_name, svc_name, ports, protocol, desc in SERVICES: vm_id = vm_ids.get(vm_name) if not vm_id and COMMIT: print(f" ~ {vm_name}/{svc_name}: VM fehlt, uebersprungen") n_skip += 1 continue svc = get_or_create( "ipam/services", {"name": svc_name, "virtual_machine": vm_name}, "name", svc_name, {"virtual_machine": vm_id, "name": svc_name, "ports": ports, "protocol": protocol, "description": desc}, f"{vm_name}/{protocol}/{ports[0]}", ) if svc: n_ok += 1 else: n_skip += 1 print(f"\n== fertig ({'COMMIT' if COMMIT else 'DRY-RUN — nichts geschrieben'}) " f"| Services={n_ok} uebersprungen={n_skip} ==") if __name__ == "__main__": main()