from flask import Flask, request, jsonify import os import subprocess import base64 import requests import redis import shutil from threading import Lock from webdav3.client import Client app = Flask(__name__) # Environment variables NEXTCLOUD_URL_DAV = os.getenv("NEXTCLOUD_URL_DAV") NEXTCLOUD_USER = os.getenv("NEXTCLOUD_USER") NEXTCLOUD_PASSWORD = os.getenv("NEXTCLOUD_PASSWORD") N8N_WEBHOOK_URL = os.getenv("N8N_WEBHOOK_URL", "https://n8n.n8n.svc.kube.ia86.cc/webhook/7950310f-e526-475a-82d1-63818da79339") DEBUG = bool(os.getenv("DEBUG", True)) redis_host = os.getenv("REDIS_HOST", "redis.redis.svc.cluster.local") redis_port = int(os.getenv("REDIS_PORT", 6379)) redis_client = redis.Redis(host=redis_host, port=redis_port, decode_responses=True) lock_prefix = "lock:mkdocs:" local_lock = Lock() def log_debug(msg): if DEBUG: print(f"🔍 DEBUG: {msg}", flush=True) def sync_from_nextcloud(website, tmp_path): remote_path = f"/sites/@{website}/" local_path = tmp_path if os.path.exists(local_path): shutil.rmtree(local_path) os.makedirs(local_path, exist_ok=True) options = { 'webdav_hostname': NEXTCLOUD_URL_DAV, 'webdav_login': NEXTCLOUD_USER, 'webdav_password': NEXTCLOUD_PASSWORD } client = Client(options) log_debug(f"Starting WebDAV sync from '{remote_path}' to '{local_path}'") client.download_sync(remote_path=remote_path, local_path=local_path) log_debug("WebDAV sync completed successfully.") @app.route("/build", methods=["POST"]) def build_mkdocs(): log_debug("POST request received at /build") data = request.json website = data.get("WEBSITE") error_callback = data.get("ERROR_CALLBACK", N8N_WEBHOOK_URL) if not website: log_debug("Error: Missing 'WEBSITE' parameter in request.") return jsonify({"error": "WEBSITE parameter missing"}), 400 lock_key = f"{lock_prefix}{website}" log_debug(f"Attempting to acquire Redis lock: {lock_key}") try: lock_acquired = redis_client.set(lock_key, "locked", nx=True, ex=60) except redis.exceptions.ConnectionError as e: log_debug(f"Redis connection failed: {e}") return jsonify({"error": "Redis connection error"}), 500 if not lock_acquired: log_debug(f"Build already active for website: {website}") return jsonify({"status": "busy", "message": f"Build already active: {website}"}), 429 tmp_path = f"/tmp/{website}" compile_path = f"{tmp_path}#compile" final_path = f"/srv/{website}" try: sync_from_nextcloud(website, tmp_path) src = os.path.join(tmp_path, "mkdocs.yml") log_debug(f"Checking if mkdocs.yml exists at {src}") if not os.path.exists(src): log_debug(f"{src} not found after sync") return jsonify({"error": f"{src} not found after sync"}), 404 log_debug(f"Running MkDocs build: {src} -> {compile_path}") cmd = f"mkdocs build --quiet --no-strict --config-file {src} --site-dir {compile_path}" result = subprocess.run(cmd, shell=True, capture_output=True, text=True) if result.returncode != 0: build_error = base64.b64encode(result.stderr.encode()).decode() json_payload = {"site": website, "error": build_error} log_debug(f"MkDocs build failed: {result.stderr}") requests.post(error_callback, json=json_payload, headers={"Content-Type": "application/json"}) return jsonify({"status": "error", "message": "Build failed", "error": result.stderr}), 500 log_debug(f"Performing differential copy from {compile_path} to {final_path}") subprocess.run(f"rsync -a --delete {compile_path}/ {final_path}/", shell=True, check=True) log_debug(f"MkDocs build and sync successful for website: {website}") return jsonify({"status": "success", "message": "Build successful"}), 200 finally: redis_client.delete(lock_key) log_debug(f"Redis lock released for website: {website}") if __name__ == "__main__": log_debug("Starting Flask server on 0.0.0.0:80") app.run(host="0.0.0.0", port=80)