diff --git a/meinantrag.py b/meinantrag.py index 2a795e3..99ee7a3 100644 --- a/meinantrag.py +++ b/meinantrag.py @@ -36,6 +36,22 @@ logger = logging.getLogger(__name__) SITE_BASE_URL = os.environ.get("MEINANTRAG_BASE_URL", "http://localhost:8000") +def resolve_env_value(value): + """ + Resolve environment variable values that may use file: prefix. + If the value starts with 'file:', read the content from the specified path. + """ + if value and value.startswith("file:"): + file_path = value[5:] + try: + with open(file_path, 'r') as f: + return f.read().strip() + except (IOError, OSError) as e: + logger.warning(f"Failed to read credential file {file_path}: {e}") + return None + return value + + class BaseTemplateResource: """Base class for resources that need template rendering""" @@ -141,8 +157,9 @@ class DatenschutzResource(BaseTemplateResource): class GenerateAntragResource: def __init__(self): - # Initialize Gemini API - api_key = os.environ.get("GOOGLE_GEMINI_API_KEY") + # Initialize Gemini API with file-macro support + api_key_raw = os.environ.get("GOOGLE_GEMINI_API_KEY") + api_key = resolve_env_value(api_key_raw) if api_key: genai.configure(api_key=api_key) self.model = genai.GenerativeModel("gemini-2.5-pro") diff --git a/module.nix b/module.nix index 71f01c2..5567060 100644 --- a/module.nix +++ b/module.nix @@ -20,12 +20,27 @@ in type = lib.types.attrsOf lib.types.str; default = { }; example = { - GOOGLE_GEMINI_API_KEY = "your-api-key-here"; MEINANTRAG_BASE_URL = "https://example.com"; + GOOGLE_GEMINI_API_KEY = "file:/run/secrets/gemini_api_key"; }; description = '' Additional environment variables to pass to the MeinAntrag service. - For example, set GOOGLE_GEMINI_API_KEY for Gemini API integration. + Values starting with "file:" will be read from the specified path. + For secrets with systemd LoadCredential, use the credentials option instead. + ''; + }; + + credentials = lib.mkOption { + type = lib.types.attrsOf lib.types.str; + default = { }; + example = { + GOOGLE_GEMINI_API_KEY = "/run/secrets/gemini_api_key"; + }; + description = '' + Credentials to pass to the MeinAntrag service. + Maps environment variable names to file paths containing the secret values. + These are loaded via systemd's LoadCredential mechanism. + The Python app will automatically read the value from the file. ''; }; @@ -64,7 +79,8 @@ in "PYTHONPATH=${pkgs.meinantrag}/share/meinantrag:${pkgs.meinantrag.pythonPath}" "MEINANTRAG_TEMPLATES_DIR=${pkgs.meinantrag}/share/meinantrag/templates" "MEINANTRAG_STATIC_DIR=${pkgs.meinantrag}/share/meinantrag/assets" - ] ++ (lib.mapAttrsToList (name: value: "${name}=${value}") cfg.settings); + ] ++ (lib.mapAttrsToList (name: value: "${name}=${value}") cfg.settings) + ++ (lib.mapAttrsToList (name: _: "${name}=file:/run/credentials/uwsgi.service/${name}") cfg.credentials); settings = { "static-map" = "/static=${pkgs.meinantrag}/share/meinantrag/assets"; @@ -74,6 +90,10 @@ in }; }; + # Load credentials via systemd's LoadCredential mechanism + systemd.services.uwsgi.serviceConfig.LoadCredential = + lib.mapAttrsToList (key: value: "${key}:${value}") cfg.credentials; + # Ensure meinantrag user and group exist users.users.meinantrag = { isSystemUser = true;