Session-Caching, Domain-/Gruppen-Skripte; KeePass-DB aus Repo entfernen

- inwx_common.py: Login mit Session-Cache, KP_PW-Option
- inwx_list/add: Domain als Argument, gemeinsamer Helfer
- inwx_domains(_owner).py: Domains alphabetisch, NS, Gruppen via citeq-TXT (DNS)
- hosting.kdbx aus Tracking genommen und in .gitignore

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 15:57:10 +02:00
parent d8e051d4b3
commit e31bdeb59d
8 changed files with 556 additions and 114 deletions
+156
View File
@@ -0,0 +1,156 @@
#!/usr/bin/env python3
"""Listet alle für den Benutzer sichtbaren Domains alphabetisch sortiert auf."""
import os
import subprocess
import sys
from inwx_common import get_client, call_api
PAGE_LIMIT = 100 # Domains pro API-Seite
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
OWNER_FILE = os.path.join(BASE_DIR, "domain_owner")
TXT_PREFIX = "citeq:" # Präfix der Gruppen-TXT-Einträge
def fetch_all_domains(api):
"""Holt alle Domains über alle Seiten hinweg (domain.list ist paginiert)."""
domains = []
page = 1
while True:
res = call_api(api, 'domain.list', {'page': page, 'pageLimit': PAGE_LIMIT})
if res['code'] != 1000:
print(f"❌ API Fehler: {res['msg']} (Code: {res['code']})")
sys.exit(1)
data = res['resData']
domains.extend(data.get('domain', []))
# Solange wir noch nicht alle laut 'count' geladen haben, weiterblättern.
if len(domains) >= data.get('count', 0) or not data.get('domain'):
break
page += 1
return domains
def get_nameservers(api, domain):
"""Ermittelt die zuständigen Nameserver (Registry-Delegation) einer Domain.
Nutzt domain.info -> Feld 'ns', das die delegierten Nameserver enthält und
auch dann funktioniert, wenn die DNS-Zone nicht bei INWX gehostet wird.
Liefert eine sortierte Liste der NS-Hostnamen oder None bei Fehler.
"""
res = call_api(api, 'domain.info', {'domain': domain})
if res['code'] != 1000:
return None
ns = res['resData'].get('ns') or []
return sorted(n.rstrip('.') for n in ns)
def load_owners():
"""Liest die Zuordnung Ziffer -> Klarname aus der Datei domain_owner.
Format pro Zeile: <ziffer><Whitespace><klarname>, wobei als Trenner
beliebiger Whitespace dient (ein oder mehrere Tabs/Leerzeichen). Der
Klarname darf selbst Leerzeichen enthalten.
Leere Zeilen und Kommentarzeilen (#) werden ignoriert.
"""
owners = {}
if not os.path.exists(OWNER_FILE):
print(f"⚠️ Datei {OWNER_FILE} nicht gefunden Gruppen werden ohne Klarnamen angezeigt.")
return owners
try:
with open(OWNER_FILE, "r") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
parts = line.split(None, 1) # an erstem Whitespace (Tab/Space) trennen
if len(parts) == 2:
ziffer, klarname = parts
owners[ziffer.strip()] = klarname.strip()
except Exception as e:
print(f"⚠️ Konnte {OWNER_FILE} nicht lesen: {e}")
return owners
def get_group(domain):
"""Ermittelt die Gruppen-Ziffer aus dem citeq:-TXT-Eintrag einer Domain.
Nutzt eine echte DNS-Abfrage (dig +short TXT) und funktioniert daher
unabhängig davon, wo die DNS-Zone gehostet wird. Liefert die Ziffer als
String oder None, wenn kein passender TXT-Eintrag gefunden wird.
"""
try:
out = subprocess.run(
["dig", "+short", "TXT", domain],
capture_output=True, text=True, timeout=10,
)
except FileNotFoundError:
print("⚠️ 'dig' nicht gefunden Gruppen können nicht ermittelt werden.")
return None
except subprocess.TimeoutExpired:
return None
if out.returncode != 0:
return None
for line in out.stdout.splitlines():
# dig liefert TXT-Werte in Anführungszeichen, lange Werte ggf. in
# mehreren Teilen ("teil1" "teil2") -> zusammenfügen und entkleiden.
content = line.replace('" "', '').strip().strip('"')
if content.startswith(TXT_PREFIX):
return content[len(TXT_PREFIX):].strip()
return None
def main():
api = get_client()
print("Rufe Domain-Liste ab...")
domains = fetch_all_domains(api)
if not domains:
print("️ Keine Domains gefunden.")
return
# Zuordnung Ziffer -> Klarname laden
owners = load_owners()
# Alphabetisch sortieren (Groß-/Kleinschreibung ignorieren)
domains.sort(key=lambda d: d['domain'].lower())
print(f"\n{'DOMAIN':<35} {'STATUS':<8} {'GRUPPE':<18} {'NAMESERVER'}")
print("-" * 100)
for d in domains:
name = d.get('domain', '')
status = d.get('status', '')
# Gruppen-Ziffer aus citeq:-TXT (per DNS) ermitteln und Klarnamen zuordnen
ziffer = get_group(name)
if ziffer is None:
group = "-"
elif ziffer in owners:
group = f"{ziffer} ({owners[ziffer]})"
else:
group = f"{ziffer} (?)"
# Zuständige Nameserver pro Domain ermitteln
ns_list = get_nameservers(api, name)
if ns_list is None:
ns = "(nicht abrufbar)"
elif not ns_list:
ns = "(keine NS-Einträge gefunden)"
else:
ns = ", ".join(ns_list)
print(f"{name:<35} {status:<8} {group:<18} {ns}")
print(f"\nGesamt: {len(domains)} Domain(s)")
# Kein logout() -> die Session bleibt gültig und kann wiederverwendet werden.
if __name__ == "__main__":
main()