#!/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())