"""Plesk REST API Client. Nutzt den CLI-Wrapper /api/v2/cli/{utility}/call. Vorteile: - mappt 1:1 auf `plesk bin mail` - stabil über Plesk-Versionen hinweg - Fehlertexte sind die gleichen wie auf der Shell. """ from typing import Optional import requests import urllib3 class PleskError(Exception): pass class PleskClient: def __init__(self, host: str, *, api_key: Optional[str] = None, user: Optional[str] = None, password: Optional[str] = None, port: int = 8443, verify: bool = True): if not host: raise PleskError("Plesk-Host ist leer") self.base = f"https://{host}:{port}" self.session = requests.Session() self.session.verify = verify if not verify: urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) if api_key: self.session.headers["X-API-Key"] = api_key elif user and password: self.session.auth = (user, password) else: raise PleskError( "Plesk: Weder API-Key noch User/Passwort gesetzt. " "Siehe README → Plesk-API-Key per SSH erzeugen." ) self.session.headers["Content-Type"] = "application/json" self.session.headers["Accept"] = "application/json" def close(self) -> None: try: self.session.close() except Exception: pass def _cli(self, utility: str, params: list) -> dict: url = f"{self.base}/api/v2/cli/{utility}/call" try: r = self.session.post(url, json={"params": params}, timeout=30) except requests.RequestException as e: raise PleskError(f"Plesk Verbindung fehlgeschlagen: {e}") if r.status_code == 401: raise PleskError("Plesk: Authentifizierung fehlgeschlagen (API-Key/Login prüfen)") if not r.ok: raise PleskError(f"Plesk HTTP {r.status_code}: {r.text[:300]}") try: data = r.json() except ValueError: raise PleskError(f"Plesk: ungültige Antwort: {r.text[:300]}") if data.get("code", 0) != 0: err = (data.get("stderr") or data.get("stdout") or "").strip() raise PleskError(f"Plesk CLI exit {data.get('code')}: {err}") return data def mail_exists(self, email: str) -> bool: try: self._cli("mail", ["--info", email]) return True except PleskError as e: msg = str(e).lower() if "does not exist" in msg or "not found" in msg or "unknown mailname" in msg: return False raise def create_mail(self, email: str, password: str) -> None: # plesk bin mail --create user@dom -passwd 'pw' -mailbox true self._cli("mail", [ "--create", email, "-passwd", password, "-mailbox", "true", ])