diff --git a/example_secrets.yaml b/example_secrets.yaml index 3160664..47b6aa7 100644 --- a/example_secrets.yaml +++ b/example_secrets.yaml @@ -1,7 +1,7 @@ traefik: - acmeEmail: email@example.com - CLOUDFLARE_EMAIL: email@example.com - CLOUDFLARE_DNS_API_TOKEN: AVeryLongAPIKeyHere + acmeEmail: email@example.com + CLOUDFLARE_EMAIL: email@example.com + CLOUDFLARE_DNS_API_TOKEN: AVeryLongAPIKeyHere authentik: db_pass: AVeryLongSecurePassword secret_key: AVeryLongSecretKey @@ -11,3 +11,6 @@ authentik: email_from: mail@example.com email_username: mail@example.com email_password: ADifferentVeryLongSecurePassword +postgres: + # Every database used should have an entry with the password here + authentik: AVeryLongSecurePassword diff --git a/flake.nix b/flake.nix index 3139cfa..5883ff5 100644 --- a/flake.nix +++ b/flake.nix @@ -50,6 +50,7 @@ hosts ; }; + nodeSpecialArgs = builtins.mapAttrs (_: cfg: { host = cfg; }) hosts; }; } // (nixpkgs.lib.mapAttrs ( diff --git a/hosts/nix-test.nix b/hosts/nix-test.nix index 8c760b3..77b6c3e 100644 --- a/hosts/nix-test.nix +++ b/hosts/nix-test.nix @@ -8,9 +8,11 @@ ip = "192.168.10.99"; roles = with roles; [ + postgres + podman + traefik sonarr - podman authentik ]; config = { diff --git a/roles/authentik.nix b/roles/authentik.nix index 88de91a..888b78e 100644 --- a/roles/authentik.nix +++ b/roles/authentik.nix @@ -26,6 +26,7 @@ lib, config, pkgs, + host, ... }: let @@ -55,10 +56,6 @@ # TODO: Persist some/all of this into ceph cluster environment.persistence."/persistent" = { directories = [ - { - directory = "/appdata/authentik/postgres"; - mode = "0700"; - } { directory = "/appdata/authentik/redis"; mode = "0700"; @@ -101,7 +98,6 @@ owner = "authentik"; content = '' AUTHENTIK_POSTGRESQL__PASSWORD=${config.sops.placeholder."authentik/db_pass"} - POSTGRES_PASSWORD=${config.sops.placeholder."authentik/db_pass"} AUTHENTIK_SECRET_KEY="${config.sops.placeholder."authentik/secret_key"}" AUTHENTIK_EMAIL__HOST="${config.sops.placeholder."authentik/email_host"}" AUTHENTIK_EMAIL__PORT="${config.sops.placeholder."authentik/email_port"}" @@ -111,23 +107,11 @@ ''; }; + # Create the database + postgres.databases = [ "authentik" ]; + podman.containers = { - # TODO: Use system postgres here instead of a separate container - "authentik-postgres" = { - image = "docker.io/library/postgres:16-alpine"; - autoStart = true; - volumes = [ - "/appdata/authentik/postgres:/var/lib/postgresql/data" - ]; - environment = { - POSTGRES_USER = "authentik"; - POSTGRES_DB = "authentik"; - }; - environmentFiles = [ - config.sops.templates."authentik-secret.env".path - publicEnv - ]; - }; + # TODO: Does using system redis make sense here? "authentik-redis" = { image = "docker.io/library/redis:7.4.2-alpine"; autoStart = true; @@ -137,12 +121,11 @@ }; "authentik-server" = { image = "ghcr.io/goauthentik/server:${AUTHENTIK_VERSION}"; - user = "authentik"; autoStart = true; cmd = [ "server" ]; environment = { AUTHENTIK_REDIS__HOST = "authentik-redis"; - AUTHENTIK_POSTGRESQL__HOST = "authentik-postgres"; + AUTHENTIK_POSTGRESQL__HOST = "host.containers.internal"; AUTHENTIK_POSTGRESQL__USER = "authentik"; AUTHENTIK_POSTGRESQL__NAME = "authentik"; }; @@ -159,12 +142,12 @@ }; "authentik-worker" = { image = "ghcr.io/goauthentik/server:${AUTHENTIK_VERSION}"; - user = "authentik"; + user = "root"; autoStart = true; cmd = [ "worker" ]; environment = { AUTHENTIK_REDIS__HOST = "authentik-redis"; - AUTHENTIK_POSTGRESQL__HOST = "authentik-postgres"; + AUTHENTIK_POSTGRESQL__HOST = "host.containers.internal"; AUTHENTIK_POSTGRESQL__USER = "authentik"; AUTHENTIK_POSTGRESQL__NAME = "authentik"; }; diff --git a/roles/default.nix b/roles/default.nix index f72f3e0..7511ca9 100644 --- a/roles/default.nix +++ b/roles/default.nix @@ -3,8 +3,12 @@ ... }: { + # Utility + postgres = utils.mkRole (import ./postgres.nix); + podman = utils.mkRole (import ./podman.nix); + + # Services sonarr = utils.mkRole (import ./sonarr.nix); traefik = utils.mkRole (import ./traefik.nix); - podman = utils.mkRole (import ./podman.nix); authentik = utils.mkRole (import ./authentik.nix); } diff --git a/roles/podman.nix b/roles/podman.nix index 39bcd2e..d099e7a 100644 --- a/roles/podman.nix +++ b/roles/podman.nix @@ -23,6 +23,8 @@ virtualisation.podman.defaultNetwork.settings.dns_enabled = true; virtualisation.oci-containers.backend = "podman"; + # TODO: Maybe we want to pre-fetch the images during build? + # This would ensure the config always reproduces the exact same system virtualisation.oci-containers.containers = cfg.containers; }; }; diff --git a/roles/postgres.nix b/roles/postgres.nix new file mode 100644 index 0000000..4c75ed3 --- /dev/null +++ b/roles/postgres.nix @@ -0,0 +1,107 @@ +{ + name = "PostgreSQL"; + description = '' + Runs a PostgreSQL database server on this host. + Other roles can use this role to create the required databases on this host through the + `postgres` attribute. + ''; + + nixosModule = + { + lib, + pkgs, + config, + ... + }: + with lib; + { + options.postgres = { + databases = mkOption { + type = types.listOf types.str; + default = [ ]; + }; + }; + config = + let + cfg = config.postgres; + in + { + # Create the postgresql service + services.postgresql = { + enable = true; + enableTCPIP = true; + ensureDatabases = map (db: db) cfg.databases; + ensureUsers = map (db: { + name = db; + ensureDBOwnership = true; + ensureClauses.login = true; + }) cfg.databases; + identMap = '' + # ArbitraryMapName systemUser DBUser + superuser_map root postgres + superuser_map postgres postgres + ''; + + authentication = pkgs.lib.mkOverride 10 '' + # Allow local users to log into the database user with the same name + local sameuser postgres peer map=superuser_map + # Allow "md5" (password) authentication + local all all md5 + host all all 0.0.0.0/0 md5 + host all all 127.0.0.1/32 md5 + host all all ::1/128 md5 + ''; + + # FIXME: For debug + settings = { + log_connections = true; + }; + }; + + networking.firewall.allowedTCPPorts = [ + 5432 + ]; + + # Persist the database contents across reboots + # TODO: This should be automatically backed up daily into the CEPH + # cluster storage. It can't be on the cluster all the time, since + # CEPH doesn't perform great with databases (according to people + # online). + environment.persistence."/persistent" = { + directories = [ + { + directory = "/var/lib/postgresql"; + user = "postgres"; + mode = "0700"; + } + ]; + }; + + # Create the required secret files + sops.secrets = listToAttrs ( + map (db: { + name = "postgres/${db}"; + value = { + owner = "postgres"; + }; + }) cfg.databases + ); + + # Use the secret files from sops to set database user passwords + systemd.services.postgresql.postStart = concatMapStrings (db: '' + $PSQL -tA <<'EOF' + DO $$ + DECLARE username TEXT; + DECLARE password TEXT; + BEGIN + username := trim(both from replace('${db}', E'\n', ''')); + password := trim(both from replace(pg_read_file('${ + (attrsets.getAttrFromPath [ "postgres/${db}" ] config.sops.secrets).path + }'), E'\n', ''')); + EXECUTE format('ALTER ROLE %s WITH PASSWORD '''%s''';', username, password); + END $$; + EOF + '') cfg.databases; + }; + }; +} diff --git a/secrets/nix-test.yaml b/secrets/nix-test.yaml index dec3445..17f609e 100644 --- a/secrets/nix-test.yaml +++ b/secrets/nix-test.yaml @@ -10,6 +10,8 @@ authentik: email_from: ENC[AES256_GCM,data:X6NP2i3uAZQFK7JdeviIMFhNPw==,iv:dwZFyzzzzFNTVfe1nhWebXrTolCa991p+vJUAOxFJf8=,tag:gClo9mZfaVFP35yZath0Nw==,type:str] email_username: ENC[AES256_GCM,data:c1lu5Tw6N6w96uUujSj1wHh7fQ==,iv:XX2iYXOzz8EhcZ75NlmLsasnZnCrihE9K17qS2nhAyI=,tag:qfhh3bB530IIsJwmjG20Lw==,type:str] email_password: ENC[AES256_GCM,data:2f/LN5q/5RRIzAc8ol9RByf+RrQ=,iv:gy/UvcKzpvC0r4nQFbTYta8alzTjPWhFWCjGIw/PnuU=,tag:LLOk7NMuQ3VZ2zA779A5dw==,type:str] +postgres: + authentik: ENC[AES256_GCM,data:45DJfPHXeGyT8KDty5Po68whOVSTbT+iAfBpJ/6dKy0EeaKLKq/w1A==,iv:CtmwN+9tKmsCcU46OvBME/urkAvjEtVBqfqgs8dkkCU=,tag:j+yZfVv62IhkgF7HRT6zLQ==,type:str] sops: kms: [] gcp_kms: [] @@ -43,8 +45,8 @@ sops: OHkvUTViMVZSUGFSeDN1ZDcxN3NtNzQK48qiEMcKbsrh8ZhnMD7lkhsy0JRMYiOU EtXwHxEzIXukStQ9kXazfHJJouuqv7mhx12tgv+QKvrfWxCJ5WvE2A== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-02-10T22:59:35Z" - mac: ENC[AES256_GCM,data:uTzFEW5na0YCBLGb9k/yICjNGKWCefGgiSH8M0QZgrvRf3ioXz1W/rGUJqTmIh3QdQ45evHoj6cKSIys2gU28dR98qGX2sCBmnWYMB2oT57YLXeACsaqXKtXFsOx/YnOy8baQpckUYOwHJpM+dhKf/s348X1jfx1k8TOoNE3aj0=,iv:Jr1HvXKsxKfMBUu2r6ezodySMWduVdlPw+EIckpi5i0=,tag:fDjBfC7nKwkO3mm91iB/HQ==,type:str] + lastmodified: "2025-02-11T16:20:59Z" + mac: ENC[AES256_GCM,data:e1uqTmgVc60EHKEwsrcdh+qA2pA+Acy89DHEMCTN2eTR9hb9ya2FkEa6+X2ckgMQsngWGg/N+wAxR+wOyqLNuX1Rcb0ee3YxzaHxQZamTp09XL5IPooTNqfQdEiexPHBoJr+OtftkvsvhxmBUvh9+/VZpvnEVHwBcJAPF9KRrCI=,iv:66Uq37oID3XrRY+xcza2VNZCqhSKkAz6SJeJ3scfTmE=,tag:r6hua8+RfF99DtJsTALRpQ==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.9.2