diff --git a/homelab-services/postgresql-db/flake.nix b/homelab-services/postgresql-db/flake.nix new file mode 100644 index 0000000..b874141 --- /dev/null +++ b/homelab-services/postgresql-db/flake.nix @@ -0,0 +1,116 @@ +{ + description = "Declare a new table and user for postgresql"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; + }; + + outputs = { self, nixpkgs, ...}: { + nixosModules.postgresql-db = {config, lib, pkgs, ...}: + let + opts = config.services.postgresql-db; + in { + options.services.posgresql-db = { + enable = lib.mkEnableOption "Postgres make DBs"; + + dataDir = lib.mkOption { + type = lib.types.path; + default = "var/lib/postgresql"; + description = "Where to store database data"; + }; + + port = lib.mkOption { + type = lib.types.port; + default = 5432; + description = "port to host postgresql"; + }; + + credentialsFile = lib.mkOption { + type = lib.types.path; + description = "File containing postgresql user credentials"; + }; + + databases = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = []; + description = "list of databases to bootstrap"; + }; + }; + + config = lib.mkIf opts.enable { + users.groups.postgres = {}; + users.users.postgres = { + isSystemUser = true; + createHome = true; + home = opts.dataDir; + group = "postgres"; + }; + + services.postgresql = { + enable = true; + dataDir = opts.dataDir; + settings.port = opts.port; + }; + + systemd.services.bootstrap-psql = { + description = "Minio bootstrap users, policies, buckets"; + after = [ "postgresql.service" ]; + requires = [ "postgresql.service" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + Type = "oneshot"; + User = "postgres"; + Group = "postgres"; + EnvironmentFile = opts.credentialsFile; + + ExecStart = pkgs.writeShellScript "bootstrap-psql" '' + set -euo pipefail + + psql_bin=${pkgs.postgres}/bin/psql + + # Create databases and users + for db in ${lib.escapeShellArgs opts.databases}; do + # Uppercase database name + db_upper="''${db^^}" + + user_var="PSQL_''${db_upper}_USER" + pass_var="PSQL_''${db_upper}_PASSWORD" + + # Expand variable values (from EnvironmentFile) + user_val=$(eval "echo \''${$user_var:-}") + pass_val=$(eval "echo \''${$pass_var:-}") + + if [ -z "$user_val" ] || [ -z "$pass_val" ]; then + echo "Missing credentials for database '$db' ($user_var / $pass_var)" >&2 + exit 1 + fi + + echo "Bootstrapping PostgreSQL for database: $db" + + # Create user if not exists + $psql_bin --tuples-only --no-align -c \ + "SELECT 1 FROM pg_roles WHERE rolname=$user_val" | grep -q 1 \ + || $psql_bin -c "CREATE USER $user_val WITH PASSWORD '$pass_val';" + + # Create database if not exists + $psql_bin --tuples-only --no-align -c \ + "SELECT 1 FROM pg_database WHERE datname='$db'" | grep -q 1 \ + || $psql_bin -c "CREATE DATABASE $db OWNER $user_val;" + + # Ensure user owns DB + grants + $psql_bin -c "ALTER DATABASE $db OWNER TO $user_val;" + $psql_bin -d "$db" -c "GRANT ALL PRIVILEGES ON DATABASE $db TO $user_val;" + + echo "Bootstrapped DB $db (user: $user_val)" + done + ''; + }; + + + }; + + }; + }; + }; +} diff --git a/nixos/configuration.nix b/nixos/configuration.nix index c6986cc..23ff5c1 100755 --- a/nixos/configuration.nix +++ b/nixos/configuration.nix @@ -47,6 +47,12 @@ in key = "credentials"; format = "yaml"; }; + + "postgresql-credentials" = { + sopsFile = ./secrets/psql.yaml; + key = "credentials"; + format = "yaml"; + }; }; }; @@ -145,6 +151,13 @@ in # Minio for backend storage # Postgresql/postgrest for text storage + services.postgresql-db = { + enable = true; + dataDir = "var/lib/postgresql"; + port = 5432; + credentialsFile = config.sops.secrets."postgresql-credentials".path; + databases = [ "rdblog" ]; + }; # Fullstack sourcecode diff --git a/nixos/flake.nix b/nixos/flake.nix index 407e561..445d982 100755 --- a/nixos/flake.nix +++ b/nixos/flake.nix @@ -31,9 +31,10 @@ tests-service.url = "github:RoyDumblauskas/tests-service?shallow=1"; minio-service.url = "path:../homelab-services/minio-service"; mc-service.url = "path:../homelab-services/mc-service"; + postgresql-db.url = "path:../homelab-services/postgresql-db"; }; - outputs = { self, nixpkgs, nixvim, home-manager, disko, sops-nix, quasiSecrets, impermanence, firefox-addons, tests-service, minio-service, mc-service }@inputs: + outputs = { self, nixpkgs, nixvim, home-manager, disko, sops-nix, quasiSecrets, impermanence, firefox-addons, tests-service, minio-service, mc-service, postgresql-db }@inputs: let nodes = [ { @@ -64,6 +65,7 @@ tests-service.nixosModules.default minio-service.nixosModules.minio-service mc-service.nixosModules.mc-service + postgresql-db.nixosModule.postgresql-db home-manager.nixosModules.home-manager { home-manager.useGlobalPkgs = true; home-manager.useUserPackages = true; diff --git a/nixos/secrets/psql.yaml b/nixos/secrets/psql.yaml new file mode 100644 index 0000000..8318033 --- /dev/null +++ b/nixos/secrets/psql.yaml @@ -0,0 +1,25 @@ +credentials: ENC[AES256_GCM,data:S1QafjWA/2wWQ0lYCbdMAHgDEmYbtf27b8VZ8V2nB4I5GX/8Dp3rLchlYp9qAaSNg2vnK0eWfstdOP/Ot9aNAEYlgh/q/aBap1qByLNNN5Tb,iv:UI16lzygVgF4SsLvY1K7r3V5602LdNmVFdbXtzMgH4Y=,tag:uRz0vTIGYmV2B5H6m14ZDw==,type:str] +sops: + age: + - recipient: age1zad0qu648dhjav4pzjhkc6vswnqtacuv9230xcgle8wmv56r8ykqn0ud7y + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaQlJvWHhhdENTU0IrMTZu + WVFZUUxUa2ZFSms2eUlOVk84VEsxUHBiS0E4CnQrNVFqdEdpUDdseHRoZGpJUFZI + Y1dFR3d5UlF2RVpWYnRSbmFYeVFrcGcKLS0tIGlDYUZHcnk3cytSaVdKN3R6Z21Q + OEdRaUs4V1ZLZ3lkZ2U3UEQ0OEdXdEEKEQtKhg75sN76Xo+TP13Hk53bm/cfLsjG + +sLJrGoEh9JqCEuOjwq6V5pftpWU8qWKVZdTem1f4TBRV4ZzS6NdKw== + -----END AGE ENCRYPTED FILE----- + - recipient: age15w5qelqemnvytp38ywdwmflc6qmrqph3dwqmx3lrec5cm5d9nchq9pfe8s + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0WDRqaXV1akU4U0tIN0Vj + OUhDWFdINEpmTEcvcDAraUVqck9pMDFab0JJCitmS0xaemQ2T0dMd1U4TzdQNldG + cGp4QWhkTFc0eW41ZUdDL0FOYmlDNlEKLS0tIFdjK2xLaEc4N2w1ckRYSXBjYjRr + VVZnNFk0empvbkNoVm5WWkhWbnk3SUkK+1wEgD9x1bRKp2SrDIgl/UYgKhnW0270 + A90OzUDepvZNFAwKjORjNmm8n6IQ3aJz1XbKfaHQaP/MplN4vxz/5w== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2025-11-16T05:46:32Z" + mac: ENC[AES256_GCM,data:8DHljSeEmLfCZ7kxkB1jaI18+QFuQwRwo8HGooXVcyeDgTnqemBgYiSwwaq1udfIFUqwt6mHAi6baJoy2tveoZkzrNYg2wpvz2+yTrD1e5K+Bx9z9VDnQoi8NFrsbRZE2P6kYNJVzkXG6l5vZZwzuplXImdBQ2E5leejFMEzIJg=,iv:1uIDhzaOw2I+rUxyj7YB9jWrVLtyBSt2mxXtvwPwlu8=,tag:4yvrqnTBz81UDmQ6HyZaMA==,type:str] + unencrypted_suffix: _unencrypted + version: 3.10.2