diff --git a/modules/podman-secrets.nix b/modules/podman-secrets.nix index fb99285..83073b3 100644 --- a/modules/podman-secrets.nix +++ b/modules/podman-secrets.nix @@ -77,7 +77,7 @@ let (builtins.readFile ./../bin/secret-translator.jl); # Submodule type for each service's secrets configuration - serviceSecretsType = types.submodule ({ config, name, ... }: { + serviceSecretsType = types.submodule { options = { user = mkOption { type = types.str; @@ -97,28 +97,18 @@ let ''; default = [ ]; }; - - ref = mkOption { - type = types.str; - description = - "Reference to the systemd service name for declaring dependencies"; - readOnly = true; - default = "podman-secrets-${name}.service"; - example = "podman-secrets-caddy.service"; - }; }; - }); + }; - # Generate a systemd user service for each configured service + # Generate a systemd service for each configured service mkSecretsService = name: serviceCfg: nameValuePair "podman-secrets-${name}" { description = "Podman secrets converter service for ${name}"; - wantedBy = [ "default.target" ]; - - unitConfig.ConditionUser = "${serviceCfg.user}"; + wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "oneshot"; + User = serviceCfg.user; ProtectProc = "invisible"; ExecStart = "${secret-translator}/bin/secret-translator ${ lib.concatStringsSep " " serviceCfg.secrets-files @@ -138,8 +128,8 @@ in { type = types.attrsOf serviceSecretsType; description = '' Per-service Podman secrets configuration. - Each attribute creates a separate systemd user service that translates TOML secrets - files into Podman secrets. + Each attribute creates a separate systemd service that translates TOML secrets + files into Podman secrets for the specified user. ''; example = literalExpression '' { @@ -162,6 +152,6 @@ in { }; config = mkIf (enabledServices != { }) { - systemd.user.services = lib.mapAttrs' mkSecretsService enabledServices; + systemd.services = lib.mapAttrs' mkSecretsService enabledServices; }; } diff --git a/services/authentik.nix b/services/authentik.nix index ee57695..ce924b6 100644 --- a/services/authentik.nix +++ b/services/authentik.nix @@ -5,8 +5,8 @@ let port = "9000"; in { - # Secrets are translated in the system NixOS scope, but performed by the user, - # via a user systemd unit, so are available in the user's Podman secrets + # Secrets are translated in the system scope, but performed by the user, + # so are available in the user's Podman secrets age.secrets = { "authentik.toml" = { file = ./../secrets/authentik.toml.age; @@ -19,15 +19,7 @@ in { secrets-files = [ config.age.secrets."authentik.toml".path ]; }; - # Podman, unlike Docker apparently, does not automatically create mount points - # within folders, so every mounted folder needs to be specified here. - systemd.tmpfiles.rules = [ - "d ${state-directory} 1775 ${user} ${user} -" - "d ${state-directory}/database 1775 ${user} ${user} -" - "d ${state-directory}/media 1775 ${user} ${user} -" - "d ${state-directory}/certs 1775 ${user} ${user} -" - "d ${state-directory}/custom-templates 1775 ${user} ${user} -" - ]; + systemd.tmpfiles.rules = [ "d ${state-directory} 1775 ${user} ${user} -" ]; services.caddy.virtualHosts."auth.millironx.com".extraConfig = '' reverse_proxy http://127.0.0.1:${port} @@ -35,153 +27,104 @@ in { # Create a dedicated user for this service users.users."${user}" = { - # Group is technically not mandatory, but NixOS complains that an unset - # group is "unsafe." The group has to be declared below group = "${user}"; - - # System users don't have a shell. For security purposes, that *would* be - # superior, but that means that, e.g. borgmatic can't `sudo` into the - # account to access Podman commands - isNormalUser = true; - - # A home directory is mandatory in order to allow systemd user units to be - # created and run + isSystemUser = true; home = "${state-directory}"; createHome = true; - - # Settings for running containers while a login shell is not active linger = true; autoSubUidGidRange = true; }; users.groups."${user}" = { }; - home-manager.users."${user}" = { config, osConfig, ... }: { + home-manager.users."${user}" = { config, ... }: { imports = [ home-manager-quadlet-nix ]; home.stateVersion = "25.05"; - virtualisation.quadlet = { - containers = { - authentik-db = { - autoStart = true; - containerConfig = { - image = "docker.io/library/postgres:16"; - environments = { - POSTGRES_DB = "${user}"; - POSTGRES_USER = "${user}"; - }; - secrets = [ - # POSTGRES_PASSWORD is used by the container to setup the - # database, PGPASSWORD is used by pg_dump to authenticate for - # backup purposes - "AUTHENTIK_POSTGRESQL__PASSWORD,type=env,target=POSTGRES_PASSWORD" - "AUTHENTIK_POSTGRESQL__PASSWORD,type=env,target=PGPASSWORD" - ]; - - # Double dollarsigns are required for use of *container* environment - # variables, Nix escaping creates the weird $\${} syntax - healthCmd = "pg_isready -d $\${POSTGRES_DB} -U $\${POSTGRES_USER}"; - healthInterval = "30s"; - healthRetries = 5; - healthStartPeriod = "20s"; - - # Volumes have to be bound with the :U label to allow for username - # remapping in rootless containers. :Z/:z is not needed b/c NixOS - # does not support SELinux - volumes = - [ "${state-directory}/database:/var/lib/postgresql/data:U" ]; - - # A network is actually required for these containers to talk to one - # another. I suppose the more idiomatic way would be for these - # related containers to be in a "pod," but I'm still struggling - # learning the idiosyncrasies of quadlet/rootless/podman, so we'll - # stick with this for now - networks = - [ config.virtualisation.quadlet.networks.authentik-net.ref ]; - + virtualisation.quadlet.containers = { + authentik-db = { + autoStart = true; + containerConfig = { + image = "docker.io/library/postgres:16"; + environments = { + POSTGRES_DB = "${user}"; + POSTGRES_USER = "${user}"; }; - - # Allowing this container to start before the secrets are translated - # will lead to errors. Use osConfig b/c secrets are declared in the - # system NixOS scope, even though it is a user process. - unitConfig.Requires = - [ osConfig.millironx.podman-secrets.authentik.ref ]; - unitConfig.After = - [ osConfig.millironx.podman-secrets.authentik.ref ]; - }; - - authentik-worker = { - autoStart = true; - containerConfig = { - image = "ghcr.io/goauthentik/server:2025.10.2"; - environments = { - AUTHENTIK_POSTGRESQL__HOST = "authentik-db"; - AUTHENTIK_POSTGRESQL__NAME = "${user}"; - AUTHENTIK_POSTGRESQL__USER = "${user}"; - }; - exec = "worker"; - secrets = [ - "AUTHENTIK_POSTGRESQL__PASSWORD,type=env" - "AUTHENTIK_SECRET_KEY,type=env" - ]; - volumes = [ - "${state-directory}/media:/media:U" - "${state-directory}/custom-templates:/templates:U" - "${state-directory}/certs:/certs:U" - ]; - networks = - [ config.virtualisation.quadlet.networks.authentik-net.ref ]; - }; - unitConfig.Requires = - [ config.virtualisation.quadlet.containers.authentik-db.ref ]; - unitConfig.After = - [ config.virtualisation.quadlet.containers.authentik-db.ref ]; - }; - - authentik = { - autoStart = true; - containerConfig = { - image = "ghcr.io/goauthentik/server:2025.10.2"; - environments = { - AUTHENTIK_POSTGRESQL__HOST = "authentik-db"; - AUTHENTIK_POSTGRESQL__NAME = "${user}"; - AUTHENTIK_POSTGRESQL__USER = "${user}"; - }; - exec = "server"; - secrets = [ - "AUTHENTIK_POSTGRESQL__PASSWORD,type=env" - "AUTHENTIK_SECRET_KEY,type=env" - "AUTHENTIK_EMAIL__HOST,type=env" - "AUTHENTIK_EMAIL__PORT,type=env" - "AUTHENTIK_EMAIL__USERNAME,type=env" - "AUTHENTIK_EMAIL__PASSWORD,type=env" - "AUTHENTIK_EMAIL__USE_SSL,type=env" - "AUTHENTIK_EMAIL__FROM,type=env" - ]; - - # Change from Traefik: publish ports to localhost only via 127.0.0.1 - # and then reverse proxy to that port. Authentik does not appear to - # have a way to configure the port, so we will use the default of - # 9000 for non-secured traffic. - publishPorts = [ "127.0.0.1:${port}:${port}" ]; - volumes = [ - "${state-directory}/media:/media:U" - "${state-directory}/custom-templates:/templates:U" - ]; - networks = - [ config.virtualisation.quadlet.networks.authentik-net.ref ]; - }; - unitConfig.Requires = - [ config.virtualisation.quadlet.containers.authentik-db.ref ]; - unitConfig.After = - [ config.virtualisation.quadlet.containers.authentik-db.ref ]; + secrets = [ + "AUTHENTIK_POSTGRESQL__PASSWORD,type=env,target=POSTGRES_PASSWORD" + ]; + healthCmd = "pg_isready -d \${POSTGRES_DB} -U \${POSTGRES_USER}"; + healthInterval = "30s"; + healthRetries = 5; + healthStartPeriod = "20s"; + volumes = [ "${state-directory}/database:/var/lib/postgresql/data" ]; }; }; - networks.authentik-net = { }; + authentik-worker = { + autoStart = true; + containerConfig = { + image = "ghcr.io/goauthentik/server:2025.10.2"; + environments = { + AUTHENTIK_POSTGRESQL__HOST = "authentik-db"; + AUTHENTIK_POSTGRESQL__NAME = "${user}"; + AUTHENTIK_POSTGRESQL__USER = "${user}"; + }; + exec = "worker"; + secrets = [ + "AUTHENTIK_POSTGRESQL__PASSWORD,type=env" + "AUTHENTIK_SECRET_KEY,type=env" + ]; + volumes = [ + "${state-directory}/media:/media" + "${state-directory}/custom-templates:/templates" + "${state-directory}/certs:/certs" + ]; + }; + unitConfig.Requires = [ + config.virtualisation.quadlet.containers.authentik-db.ref + "network-online.target" + ]; + unitConfig.After = [ + config.virtualisation.quadlet.containers.authentik-db.ref + "network-online.target" + ]; + }; - # One of the main advantages of using Quadlet - autoUpdate.enable = true; + authentik = { + autoStart = true; + containerConfig = { + image = "ghcr.io/goauthentik/server:2025.10.2"; + environments = { + AUTHENTIK_POSTGRESQL__HOST = "authentik-db"; + AUTHENTIK_POSTGRESQL__NAME = "${user}"; + AUTHENTIK_POSTGRESQL__USER = "${user}"; + }; + secrets = [ + "AUTHENTIK_POSTGRESQL__PASSWORD,type=env" + "AUTHENTIK_SECRET_KEY,type=env" + "AUTHENTIK_EMAIL__HOST,type=env" + "AUTHENTIK_EMAIL__PORT,type=env" + "AUTHENTIK_EMAIL__USERNAME,type=env" + "AUTHENTIK_EMAIL__PASSWORD,type=env" + "AUTHENTIK_EMAIL__USE_SSL,type=env" + "AUTHENTIK_EMAIL__FROM,type=env" + ]; + publishPorts = [ "127.0.0.1:${port}:${port}" ]; + volumes = [ + "${state-directory}/media:/media" + "${state-directory}/custom-templates:/templates" + ]; + }; + unitConfig.Requires = [ + config.virtualisation.quadlet.containers.authentik-db.ref + "network-online.target" + ]; + unitConfig.After = [ + config.virtualisation.quadlet.containers.authentik-db.ref + "network-online.target" + ]; + }; }; }; } diff --git a/systems/linux/mcentire.nix b/systems/linux/mcentire.nix index 9171e5a..4a1b09a 100644 --- a/systems/linux/mcentire.nix +++ b/systems/linux/mcentire.nix @@ -44,7 +44,7 @@ millironx = { isNormalUser = true; description = "Thomas A. Christensen II"; - extraGroups = [ "adm" "wheel" ]; + extraGroups = [ "wheel" ]; }; };