mkdocs-auto/script_creator.py

136 lines
4.8 KiB
Python

from flask import Flask, request, jsonify
import os
import subprocess
import redis
from threading import Lock
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")
NEXTCLOUD_PATH = os.getenv("NEXTCLOUD_PATH")
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 obscure_password(password):
result = subprocess.run(
["rclone", "obscure", password],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
def write_rclone_config():
config_dir = "/tmp/rclone"
os.makedirs(config_dir, exist_ok=True)
config_path = os.path.join(config_dir, "rclone.conf")
obscured_password = obscure_password(NEXTCLOUD_PASSWORD)
with open(config_path, "w") as f:
f.write(f"""[nextcloud]
type = webdav
url = {NEXTCLOUD_URL_DAV}
vendor = nextcloud
user = {NEXTCLOUD_USER}
pass = {obscured_password}
encoding = Slash,BackSlash,Colon,Dot
""")
return config_path
def mount_nextcloud(website, mount_path="/mnt"):
config_file = write_rclone_config()
remote_path = f"nextcloud:{NEXTCLOUD_PATH}/@{website}"
log_debug(f"Mounting {remote_path} to {mount_path}")
os.makedirs(mount_path, exist_ok=True)
# Try to unmount first in case of stale mount
subprocess.run(f"fusermount -u {mount_path}", shell=True, stderr=subprocess.DEVNULL)
cmd = f"rclone --config {config_file} mount {remote_path} {mount_path} --vfs-cache-mode full --daemon"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"Failed to mount: {result.stderr}")
def stream_command(cmd):
log_debug(f"Executing: {cmd}")
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in process.stdout:
print(line.rstrip(), flush=True)
process.wait()
return process.returncode
@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
mount_path = "/mnt"
compile_path = f"/tmp/{website}#compile"
final_path = f"/srv/{website}"
src = os.path.join(mount_path, "mkdocs.yml")
try:
mount_nextcloud(website, mount_path)
log_debug(f"Checking if mkdocs.yml exists at {src}")
if not os.path.exists(src):
log_debug(f"{src} not found after mount")
return jsonify({"error": f"{src} not found after mount"}), 404
log_debug(f"Running MkDocs build: {src} -> {compile_path}")
mkdocs_cmd = f"mkdocs build --no-strict --config-file {src} --site-dir {compile_path}"
if stream_command(mkdocs_cmd) != 0:
return jsonify({"status": "error", "message": "MkDocs build failed"}), 500
log_debug(f"Performing differential copy from {compile_path} to {final_path}")
rsync_cmd = f"rsync -a --delete {compile_path}/ {final_path}/"
if stream_command(rsync_cmd) != 0:
return jsonify({"status": "error", "message": "Rsync failed"}), 500
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)
subprocess.run(f"fusermount -u {mount_path}", shell=True, stderr=subprocess.DEVNULL)
log_debug(f"Redis lock released and mount cleaned 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)