#!/usr/bin/env python3 from __future__ import annotations import os import re import sys from pathlib import Path STACK_DIR = Path(__file__).resolve().parent.parent ENV_FILE = Path(os.environ.get('ENV_FILE', STACK_DIR / '.env')) TEMPLATE = STACK_DIR / 'keepalived.conf.template' OUTPUT = STACK_DIR / 'keepalived.conf' def load_env(path: Path) -> dict[str, str]: env = dict(os.environ) if path.exists(): for raw in path.read_text(encoding='utf-8').splitlines(): line = raw.strip() if not line or line.startswith('#') or '=' not in line: continue k, v = line.split('=', 1) env.setdefault(k.strip(), v.strip().strip('"').strip("'")) return env def main() -> int: env = load_env(ENV_FILE) required = [ 'LAN_INTERFACE', 'VIP_CIDR', 'KEEPALIVED_ROUTER_ID', 'KEEPALIVED_STATE', 'KEEPALIVED_PRIORITY', 'KEEPALIVED_PASSWORD' ] missing = [k for k in required if not env.get(k)] if missing: print(f'Missing required vars in {ENV_FILE}: {", ".join(missing)}', file=sys.stderr) return 1 web_port = env.get('PIHOLE_WEB_PORT') or env.get('PD_PIHOLE_WEB_PORT') or env.get('SERENITY_PIHOLE_WEB_PORT') or env.get('RPI4_PIHOLE_WEB_PORT') if not web_port: print('Missing Pi-hole web port in .env (set PIHOLE_WEB_PORT or node-specific port variable).', file=sys.stderr) return 1 keepalived_password = env['KEEPALIVED_PASSWORD'] if len(keepalived_password) > 8: print('KEEPALIVED_PASSWORD must be 8 characters or fewer for VRRP PASS auth.', file=sys.stderr) return 1 try: web_port_hex = f"{int(web_port):04X}" except ValueError: print('Pi-hole web port must be an integer.', file=sys.stderr) return 1 router_id_name = f"pihole_{re.sub(r'[^a-z0-9]+', '_', env['KEEPALIVED_STATE'].lower())}" nopreempt_line = ' nopreempt' if env.get('KEEPALIVED_NOPREEMPT', 'true').lower() in {'1','true','yes','on'} else '' rendered = TEMPLATE.read_text(encoding='utf-8') replacements = { 'ROUTER_ID_NAME': router_id_name, 'PIHOLE_WEB_PORT_HEX': web_port_hex, 'NOPREEMPT_LINE': nopreempt_line, } replacements.update({k: env[k] for k in required}) replacements['PIHOLE_WEB_PORT'] = web_port for key, value in replacements.items(): rendered = rendered.replace('${' + key + '}', value) OUTPUT.write_text(rendered.rstrip() + '\n', encoding='utf-8') print(f'Rendered {OUTPUT}') return 0 if __name__ == '__main__': raise SystemExit(main())