#!/usr/bin/env python3 """ IWZ Ferienwohnungen - Deploy Script Baut die Hugo-Seite und deployt sie per FTP zur Bunny.net Storage Zone. """ import ftplib import os import subprocess import sys import urllib.error import urllib.parse import urllib.request from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parent PUBLIC_DIR = PROJECT_ROOT / "public" FTP_PORT = 21 def load_env(path): env = {} if not path.exists(): return env for line in path.read_text(encoding="utf-8").splitlines(): line = line.strip() if not line or line.startswith("#") or "=" not in line: continue key, value = line.split("=", 1) env[key.strip()] = value.strip() return env def hugo_build(): print("\n-> Hugo Build...") result = subprocess.run("hugo --gc --minify", shell=True) if result.returncode != 0: print("\nFEHLER: Hugo Build fehlgeschlagen!") sys.exit(1) def ftp_upload_dir(ftp, local_dir, remote_dir=""): for entry in sorted(local_dir.iterdir()): remote_path = f"{remote_dir}/{entry.name}" if remote_dir else entry.name if entry.is_dir(): try: ftp.mkd(remote_path) except ftplib.error_perm: pass # existiert bereits ftp_upload_dir(ftp, entry, remote_path) else: with open(entry, "rb") as f: ftp.storbinary(f"STOR {remote_path}", f) print(f" {remote_path}") def deploy_to_bunny(host, zone, password): print(f"\n-> Deploy zu Bunny.net Storage Zone '{zone}'...") with ftplib.FTP() as ftp: ftp.connect(host, FTP_PORT) ftp.login(zone, password) ftp.set_pasv(True) ftp_upload_dir(ftp, PUBLIC_DIR) def purge_cdn_cache(pull_zone_url, api_key): """Leert den Bunny CDN-Cache der Pull Zone per Wildcard-URL-Purge. Ohne Purge liefert das CDN unveraenderte Dateinamen (z.B. /css/style.css) weiter aus dem Cache -> online sieht man die alte Version. """ if not pull_zone_url or not api_key: print("\n-> CDN-Purge uebersprungen " "(BUNNY_PULL_ZONE_URL / BUNNY_API_KEY nicht gesetzt).") print(" Cache ggf. manuell im Bunny-Dashboard leeren.") return wildcard = pull_zone_url.rstrip("/") + "/*" query = urllib.parse.urlencode({"url": wildcard, "async": "false"}) url = f"https://api.bunny.net/purge?{query}" print(f"\n-> CDN-Cache leeren: {wildcard}") req = urllib.request.Request(url, method="POST") req.add_header("AccessKey", api_key) req.add_header("Accept", "application/json") try: with urllib.request.urlopen(req, timeout=60) as resp: if resp.status == 200: print(" Cache geleert.") else: print(f" WARNUNG: unerwarteter Status {resp.status}") except urllib.error.HTTPError as e: print(f" WARNUNG: Purge fehlgeschlagen (HTTP {e.code}). " "Ist BUNNY_API_KEY der Account-API-Key?") except urllib.error.URLError as e: print(f" WARNUNG: Purge fehlgeschlagen ({e.reason}).") if __name__ == "__main__": print("=== IWZ Deploy ===") env = {**load_env(PROJECT_ROOT / ".env"), **os.environ} zone = env.get("BUNNY_STORAGE_ZONE") host = env.get("BUNNY_STORAGE_HOST", "storage.bunnycdn.com") password = env.get("BUNNY_STORAGE_PASSWORD") pull_zone_url = env.get("BUNNY_PULL_ZONE_URL") api_key = env.get("BUNNY_API_KEY") if not zone or not password: print("\nFEHLER: BUNNY_STORAGE_ZONE / BUNNY_STORAGE_PASSWORD fehlen.") print("Siehe .env.example -> .env kopieren und befuellen.") sys.exit(1) hugo_build() deploy_to_bunny(host, zone, password) purge_cdn_cache(pull_zone_url, api_key) print("\nFertig! Seite ist live.")