#!/usr/bin/env python3 """Render ${VAR} placeholders in the live SearXNG settings.yml from the host-side env file. Why this exists: SearXNG does NOT expand environment variables inside settings.yml. The repo-tracked shared/searxng/settings.yml deliberately keeps secrets as "${KAGI_API_KEY}"-style placeholders so no key ever enters git; the deployer copies that file to /opt/searxng/config/settings.yml and runs this script to bake in the real values. Without it the app runs with the literal placeholder strings (observed 2026-09-26: secret_key literally "${SEARXNG_SECRET}" and the Kagi engine failing setup() on an empty key). Only the allowlisted names are substituted. A placeholder that has no value in the env file is a deploy-blocking error: serving a paid-API engine with a literal "${KAGI_API_KEY}" bearer would waste requests and hide the misconfig. Usage: searxng-render-settings.py ENV_FILE SETTINGS_FILE Both paths are taken as arguments; secrets never appear in any argv. Exit codes: 0 rendered (idempotent on an already-rendered file), 1 unresolved placeholder, 2 usage error. """ import sys ALLOWED = ("KAGI_API_KEY", "SEARXNG_SECRET", "SEARXNG_BASE_URL") def load_env(path: str) -> dict[str, str]: values: dict[str, str] = {} with open(path, encoding="utf-8") as fh: for line in fh: line = line.strip() if not line or line.startswith("#") or "=" not in line: continue key, _, value = line.partition("=") if key in ALLOWED: values[key] = value return values def main(argv: list[str]) -> int: args = [a for a in argv[1:]] if len(args) != 2: print( "usage: searxng-render-settings.py ENV_FILE SETTINGS_FILE", file=sys.stderr, ) return 2 env_file, settings_file = args values = load_env(env_file) with open(settings_file, encoding="utf-8") as fh: text = fh.read() for name in ALLOWED: if name in values: text = text.replace("${%s}" % name, values[name]) unresolved = [n for n in ALLOWED if "${%s}" % n in text] if unresolved: print( "unresolved placeholders in %s: %s (missing from %s)" % (settings_file, ", ".join(unresolved), env_file), file=sys.stderr, ) return 1 with open(settings_file, "w", encoding="utf-8") as fh: fh.write(text) return 0 if __name__ == "__main__": sys.exit(main(sys.argv))