NixOS Deployment
Secret Guard’s flake provides nixosModules.secret-guard, whose
services.secret-guard.* structured options auto-generate secret-guard.toml when
configFile is not explicitly set — field validation (construction discrimination /
base_url hygiene / route dangling and cycle detection) all fails fast at eval time, so a
misconfiguration surfaces at nixos-rebuild instead of crash-looping after deployment.
Wiring the Flake
Section titled “Wiring the Flake”# flake.nix:{ inputs.secret-guard.url = "github:luochen1990/secret-guard"; # The flake's nixosModules.secret-guard ships an overlay (pkgs.secret-guard is available)}Approach 0: Structured Options + Auto-Render (Recommended)
Section titled “Approach 0: Structured Options + Auto-Render (Recommended)”The api_key_file / value_file file-style options keep the toml itself free of
secrets; secrets are decrypted to separate paths by LoadCredential or sops-nix and
injected from there (see the next section):
# Inside a NixOS module:{ # Credential injection: the *File options are pure path pass-throughs — this module # does no sops/LoadCredential derivation; pick your injection mechanism on the # deployment side (LoadCredential below is the recommended one) systemd.services.secret-guard.serviceConfig.LoadCredential = [ "zai_key:${config.sops.secrets."llm__zai_coding_plan_api_key".path}" ];
services.secret-guard = { enable = true;
providers."zai-coding-plan" = { name = "Zhipuai Coding Plan"; kind = "direct"; protocol = "openai"; baseUrl = "https://open.bigmodel.cn/api/coding/paas/v4"; apiKeyFile = "/run/credentials/secret-guard.service/zai_key"; }; # Router form (virtual endpoint, routing by model wildcard; attrsOf merges across modules) providers."default-route" = { kind = "router"; routes = [{ modelPattern = "*"; target = "zai-coding-plan"; priority = 100; }]; };
# The redact protection list (the [[secrets.entries]] section) secrets.entries = [ { id = "llm__zai_coding_plan_api_key"; valueFile = "/run/credentials/secret-guard.service/zai_key"; } ];
# Account system (optional; the [auth] section) auth = { enable = true; oidc = { issuerUrl = "https://idp.example.com/realms/main/"; clientId = "secret-guard"; clientSecretFile = "/run/credentials/secret-guard.service/oidc_secret"; redirectUrl = "https://sg.example.com/oauth2/callback"; # omitted = derived from host/port }; apiKeys = [ { label = "opencode"; keyFile = "/run/credentials/secret-guard.service/sdk_key"; } ]; }; };}The generated toml is exposed via the read-only option
services.secret-guard.resolvedConfigFile; for debugging you can simply run:
nix eval .#nixosConfigurations.<host>.config.services.secret-guard.resolvedConfigFileCredential Injection
Section titled “Credential Injection”The *File fields (api_key_file / value_file / key_file) are pure path
pass-throughs; the injection mechanism is the deployment side’s choice. The two
mainstream styles (both shown with a hand-written configFile; the same applies to the
structured options):
Approach 1: sops.secrets + systemd LoadCredential
Section titled “Approach 1: sops.secrets + systemd LoadCredential”Suits secrets shared across multiple modules (e.g. another module uses the same api_key
and has already set a different owner). LoadCredential has systemd mount the secret to
/run/credentials/<service>/<id> at service start, automatically setting mode=0400 and
the service User — no need to touch the sops.secrets owner:
systemd.services.secret-guard.serviceConfig.LoadCredential = [ "zai_key:${config.sops.secrets."llm__zai_coding_plan_api_key".path}"];
services.secret-guard.configFile = (pkgs.writeText "secret-guard.toml" '' [[providers]] id = "zai-coding-plan" kind = "direct" api_key_file = "/run/credentials/secret-guard.service/zai_key" enabled = true
[[providers.endpoints]] protocol = "openai" base_url = "https://open.bigmodel.cn/api/coding/paas/v4"'');Approach 2: sops.secrets + Direct Path
Section titled “Approach 2: sops.secrets + Direct Path”Suits secrets used only by Secret Guard. The sole difference from approach 1 is that
api_key_file points directly at the sops-decrypted path (which requires setting the
secret’s owner to the secret-guard user):
sops.secrets."zai_api_key" = { owner = "secret-guard"; };
services.secret-guard.configFile = (pkgs.writeText "secret-guard.toml" '' [[providers]] id = "zai-coding-plan" kind = "direct" api_key_file = "${config.sops.secrets."zai_api_key".path}" enabled = true
[[providers.endpoints]] protocol = "openai" base_url = "https://open.bigmodel.cn/api/coding/paas/v4"'');Batch Injection of Secret Entries
Section titled “Batch Injection of Secret Entries”Secrets also support value_file (resolved once at startup; a failed read refuses to
start), so the redact secrets list can be injected fully sanitized too, symmetrically
to the provider api_key_file. This suits batch-injecting a set of sops secrets matching
naming patterns (like *_api_key / *_api_token / *_secret) into Secret Guard for
Redact:
let # Filter the keys to redact from config.sops.secrets by pattern (SSOT: listed once). redactKeys = lib.filter (k: lib.hasSuffix "_api_key" k || lib.hasSuffix "_api_token" k || lib.hasSuffix "_secret" k ) (builtins.attrNames config.sops.secrets);in { # LoadCredential and the toml entries both derive from redactKeys — always in sync. systemd.services.secret-guard.serviceConfig.LoadCredential = map (k: "${k}:${config.sops.secrets.${k}.path}" ) redactKeys;
services.secret-guard.configFile = (pkgs.writeText "secret-guard.toml" '' ${...providers section...} ${lib.concatStrings (map (k: '' [[secrets.entries]] id = "${k}" value_file = "/run/credentials/secret-guard.service/${k}" '') redactKeys)} '');}HTTPS Reverse Proxy Note
Section titled “HTTPS Reverse Proxy Note”Secret Guard currently hardcodes its session cookie without the Secure flag (mandatory
for local HTTP development — a Secure cookie wouldn’t be returned by browsers over
HTTP). If you expose it publicly behind a TLS-terminating reverse proxy, make sure to
configure an HTTP-to-HTTPS 301 redirect at the proxy layer, so the cookie can’t be sent
in plaintext should the browser ever hit the origin over HTTP.