From 98320fc8275fd9b2aaf57c25d7d49621346c53cc Mon Sep 17 00:00:00 2001 From: Stanislav Hubacek Date: Sat, 4 Apr 2026 23:44:32 +0200 Subject: [PATCH] First real commit --- scripts/deploy_caddy.sh | 17 +++++++ scripts/generate_caddy.py | 96 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) mode change 100644 => 100755 scripts/deploy_caddy.sh mode change 100644 => 100755 scripts/generate_caddy.py diff --git a/scripts/deploy_caddy.sh b/scripts/deploy_caddy.sh old mode 100644 new mode 100755 index e69de29..85e65ce --- a/scripts/deploy_caddy.sh +++ b/scripts/deploy_caddy.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +set -euo pipefail + +BASE="/opt/homelab" +GENERATOR="$BASE/generate_caddy.py" +CADDYFILE="/etc/caddy/Caddyfile" + +echo "[1/4] Generating Caddy fragments..." +python3 "$GENERATOR" + +echo "[2/4] Validating Caddy config..." +caddy validate --config "$CADDYFILE" + +echo "[3/4] Reloading Caddy..." +systemctl reload caddy + +echo "[4/4] Done." \ No newline at end of file diff --git a/scripts/generate_caddy.py b/scripts/generate_caddy.py old mode 100644 new mode 100755 index e69de29..483b40a --- a/scripts/generate_caddy.py +++ b/scripts/generate_caddy.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 + +from pathlib import Path +import sys +import yaml +from jinja2 import Environment, FileSystemLoader, StrictUndefined + +BASE = Path("/opt/homelab") +SERVICES_DIR = BASE / "services" +TEMPLATES_DIR = BASE / "templates" +OUTPUT_DIR = BASE / "generated" / "caddy" + + +def load_yaml(path: Path) -> dict: + with path.open("r", encoding="utf-8") as f: + data = yaml.safe_load(f) or {} + return data + + +def validate_service(data: dict, source: Path) -> None: + required_common = ["name", "type", "domain"] + for key in required_common: + if key not in data: + raise ValueError(f"{source.name}: missing required key '{key}'") + + svc_type = data["type"] + if svc_type not in {"static", "proxy"}: + raise ValueError(f"{source.name}: unsupported type '{svc_type}'") + + if svc_type == "static" and "root" not in data: + raise ValueError(f"{source.name}: static service requires 'root'") + + if svc_type == "proxy" and "backend" not in data: + raise ValueError(f"{source.name}: proxy service requires 'backend'") + + +def render_service(env: Environment, data: dict) -> str: + svc_type = data["type"] + + defaults = { + "headers": False, + "auth": False, + "real_ip": False, + "health_uri": None, + "health_interval": None, + } + + merged = {**defaults, **data} + + if svc_type == "static": + template = env.get_template("static.caddy.j2") + return template.render(**merged).strip() + "\n" + + reverse_proxy_block = any( + [ + merged.get("real_ip"), + merged.get("health_uri"), + merged.get("health_interval"), + ] + ) + merged["reverse_proxy_block"] = reverse_proxy_block + + template = env.get_template("proxy.caddy.j2") + return template.render(**merged).strip() + "\n" + + +def main() -> int: + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + + env = Environment( + loader=FileSystemLoader(str(TEMPLATES_DIR)), + undefined=StrictUndefined, + trim_blocks=True, + lstrip_blocks=True, + ) + + rendered_files = [] + + for svc_file in sorted(SERVICES_DIR.glob("*.yml")): + data = load_yaml(svc_file) + validate_service(data, svc_file) + + rendered = render_service(env, data) + output_file = OUTPUT_DIR / f"{data['name']}.caddy" + output_file.write_text(rendered, encoding="utf-8") + rendered_files.append(output_file.name) + + print("Generated:") + for name in rendered_files: + print(f" - {name}") + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file