Skripte nach bin/ und lib/ umstrukturiert + WSL-Log-Analyse Alle .bat-Skripte liegen jetzt in bin/, alle .py in lib/, .env(.example) in bin/. setenv.bat setzt IWZ_* (inkl. IWZ_ENV) und PYTHONPATH; die Launcher rufen setenv, aktivieren die .venv falls vorhanden und starten das zugehoerige lib/-Skript. Die Python-Skripte lesen die .env ueber IWZ_ENV (Fallback bin/.env). Neu: Nutzungsanalyse per GoAccess (analyze.py/.bat) - laedt die IONOS-Access-Logs per SFTP (paramiko) und erzeugt einen HTML-Report. GoAccess laeuft ueber WSL (GOACCESS_MODE=wsl, Pfad-Uebersetzung nach /mnt/c). install_py.bat prueft WSL+GoAccess und zeigt sonst den Installationsweg. Doku in doc/analyse.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> @
127 lines
4.0 KiB
Python
127 lines
4.0 KiB
Python
#!/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.parent # Skript liegt in lib/
|
|
PUBLIC_DIR = PROJECT_ROOT / "public"
|
|
FTP_PORT = 21
|
|
|
|
|
|
def env_file():
|
|
"""Pfad zur .env: bevorzugt IWZ_ENV (von setenv.bat), sonst bin/.env."""
|
|
from_var = os.environ.get("IWZ_ENV")
|
|
if from_var:
|
|
return Path(from_var)
|
|
return PROJECT_ROOT / "bin" / ".env"
|
|
|
|
|
|
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(env_file()), **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 bin/.env.example -> bin/.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.")
|