Skip to content

Instantly share code, notes, and snippets.

@Mic92
Last active December 30, 2025 20:33
Show Gist options
  • Select an option

  • Save Mic92/3187fadd602ce304bbe4a1bb035ab45d to your computer and use it in GitHub Desktop.

Select an option

Save Mic92/3187fadd602ce304bbe4a1bb035ab45d to your computer and use it in GitHub Desktop.
Declarative Authelia users for NixOS - secrets resolved at runtime
# Declarative Authelia users with secret file references
# Secrets are resolved at service start, never stored in the Nix store
{
config,
lib,
pkgs,
...
}:
let
cfg = config.services.authelia.instances.main.declarativeUsers;
authelia-users-gen = pkgs.writers.writePython3 "authelia-users-gen" { } ./authelia-users-gen.py;
usersConfig = {
users = lib.mapAttrs (name: user: {
inherit (user) displayname email groups passwordHashFile;
}) cfg.users;
};
configJson = pkgs.writeText "authelia-users-config.json" (builtins.toJSON usersConfig);
in
{
options.services.authelia.instances.main.declarativeUsers = {
enable = lib.mkEnableOption "declarative users";
users = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule {
options = {
displayname = lib.mkOption { type = lib.types.str; };
email = lib.mkOption { type = lib.types.str; };
groups = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
passwordHashFile = lib.mkOption { type = lib.types.str; };
};
}
);
default = { };
};
};
config = lib.mkIf cfg.enable {
services.authelia.instances.main.settings.authentication_backend.file.path =
"/run/authelia-main/users.yaml";
systemd.services.authelia-main = {
preStart = lib.mkAfter ''
${authelia-users-gen} ${configJson} /run/authelia-main/users.yaml
'';
serviceConfig.RuntimeDirectory = lib.mkDefault "authelia-main";
};
};
}
#!/usr/bin/env python3
"""
authelia-users-gen: Generate Authelia users.yaml from config with secret file references.
Usage: authelia-users-gen <config.json> <output.yaml>
Input JSON format:
{
"users": {
"pinpox": {
"displayname": "Pablo",
"email": "pinpox@example.com",
"groups": ["admins"],
"passwordHashFile": "/run/secrets/pinpox-hash"
}
}
}
"""
import json
import os
import sys
from pathlib import Path
def main() -> None:
if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} <config.json> <output.yaml>", file=sys.stderr)
sys.exit(1)
config_path, output_path = sys.argv[1], sys.argv[2]
config = json.loads(Path(config_path).read_text())
users = {}
for username, user in config["users"].items():
hash_file = Path(user["passwordHashFile"])
if not hash_file.exists():
print(f"Error: {hash_file} not found", file=sys.stderr)
sys.exit(1)
users[username] = {
"displayname": user["displayname"],
"email": user["email"],
"groups": user.get("groups", []),
"password": hash_file.read_text().strip(),
}
# Write with 0600 permissions from the start
fd = os.open(output_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
with os.fdopen(fd, "w") as f:
json.dump({"users": users}, f, indent=2)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment