diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix index 87b236dd5fd..5661edbee2d 100644 --- a/nixos/modules/services/databases/postgresql.nix +++ b/nixos/modules/services/databases/postgresql.nix @@ -105,6 +105,80 @@ in ''; }; + ensureDatabases = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Ensures that the specified databases exist. + This option will never delete existing databases, especially not when the value of this + option is changed. This means that databases created once through this option or + otherwise have to be removed manually. + ''; + example = [ + "gitea" + "nextcloud" + ]; + }; + + ensureUsers = mkOption { + type = types.listOf (types.submodule { + options = { + name = mkOption { + type = types.str; + description = '' + Name of the user to ensure. + ''; + }; + ensurePermissions = mkOption { + type = types.attrsOf types.str; + default = {}; + description = '' + Permissions to ensure for the user, specified as an attribute set. + The attribute names specify the database and tables to grant the permissions for. + The attribute values specify the permissions to grant. You may specify one or + multiple comma-separated SQL privileges here. + + For more information on how to specify the target + and on which privileges exist, see the + GRANT syntax. + The attributes are used as GRANT ''${attrName} ON ''${attrValue}. + ''; + example = literalExample '' + { + "DATABASE nextcloud" = "ALL PRIVILEGES"; + "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES"; + } + ''; + }; + }; + }); + default = []; + description = '' + Ensures that the specified users exist and have at least the ensured permissions. + The PostgreSQL users will be identified using peer authentication. This authenticates the Unix user with the + same name only, and that without the need for a password. + This option will never delete existing users or remove permissions, especially not when the value of this + option is changed. This means that users created and permissions assigned once through this option or + otherwise have to be removed manually. + ''; + example = literalExample '' + [ + { + name = "nextcloud"; + ensurePermissions = { + "DATABASE nextcloud" = "ALL PRIVILEGES"; + }; + } + { + name = "superuser"; + ensurePermissions = { + "ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES"; + }; + } + ] + ''; + }; + enableTCPIP = mkOption { type = types.bool; default = false; @@ -256,17 +330,30 @@ in # Wait for PostgreSQL to be ready to accept connections. postStart = '' - while ! ${pkgs.sudo}/bin/sudo -u ${cfg.superUser} psql --port=${toString cfg.port} -d postgres -c "" 2> /dev/null; do + PSQL="${pkgs.sudo}/bin/sudo -u ${cfg.superUser} psql --port=${toString cfg.port}" + + while ! $PSQL -d postgres -c "" 2> /dev/null; do if ! kill -0 "$MAINPID"; then exit 1; fi sleep 0.1 done if test -e "${cfg.dataDir}/.first_startup"; then ${optionalString (cfg.initialScript != null) '' - ${pkgs.sudo}/bin/sudo -u ${cfg.superUser} psql -f "${cfg.initialScript}" --port=${toString cfg.port} -d postgres + $PSQL -f "${cfg.initialScript}" -d postgres ''} rm -f "${cfg.dataDir}/.first_startup" fi + '' + optionalString (cfg.ensureDatabases != []) '' + ${concatMapStrings (database: '' + $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${database}'" | grep -q 1 || $PSQL -tAc "CREATE DATABASE ${database}" + '') cfg.ensureDatabases} + '' + '' + ${concatMapStrings (user: '' + $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user.name}'" | grep -q 1 || $PSQL -tAc "CREATE USER ${user.name}" + ${concatStringsSep "\n" (mapAttrsToList (database: permission: '' + $PSQL -tAc "GRANT ${permission} ON ${database} TO ${user.name}" + '') user.ensurePermissions)} + '') cfg.ensureUsers} ''; unitConfig.RequiresMountsFor = "${cfg.dataDir}"; diff --git a/nixos/tests/redmine.nix b/nixos/tests/redmine.nix index ea72a0121d1..cbdb5c8d295 100644 --- a/nixos/tests/redmine.nix +++ b/nixos/tests/redmine.nix @@ -7,7 +7,7 @@ with import ../lib/testing.nix { inherit system pkgs; }; with pkgs.lib; let - redmineTest = package: makeTest { + mysqlTest = package: makeTest { machine = { config, pkgs, ... }: { services.mysql.enable = true; @@ -21,6 +21,7 @@ let services.redmine.enable = true; services.redmine.package = package; + services.redmine.database.type = "mysql2"; services.redmine.database.socket = "/run/mysqld/mysqld.sock"; services.redmine.plugins = { redmine_env_auth = pkgs.fetchurl { @@ -38,7 +39,44 @@ let testScript = '' startAll; + $machine->waitForUnit('redmine.service'); + $machine->waitForOpenPort('3000'); + $machine->succeed("curl --fail http://localhost:3000/"); + ''; + }; + pgsqlTest = package: makeTest { + machine = + { config, pkgs, ... }: + { services.postgresql.enable = true; + services.postgresql.ensureDatabases = [ "redmine" ]; + services.postgresql.ensureUsers = [ + { name = "redmine"; + ensurePermissions = { "DATABASE redmine" = "ALL PRIVILEGES"; }; + } + ]; + + services.redmine.enable = true; + services.redmine.package = package; + services.redmine.database.type = "postgresql"; + services.redmine.database.host = ""; + services.redmine.database.port = 5432; + services.redmine.plugins = { + redmine_env_auth = pkgs.fetchurl { + url = https://github.com/Intera/redmine_env_auth/archive/0.7.zip; + sha256 = "1xb8lyarc7mpi86yflnlgyllh9hfwb9z304f19dx409gqpia99sc"; + }; + }; + services.redmine.themes = { + dkuk-redmine_alex_skin = pkgs.fetchurl { + url = https://bitbucket.org/dkuk/redmine_alex_skin/get/1842ef675ef3.zip; + sha256 = "0hrin9lzyi50k4w2bd2b30vrf1i4fi1c0gyas5801wn8i7kpm9yl"; + }; + }; + }; + + testScript = '' + startAll; $machine->waitForUnit('redmine.service'); $machine->waitForOpenPort('3000'); $machine->succeed("curl --fail http://localhost:3000/"); @@ -46,13 +84,18 @@ let }; in { - redmine_3 = redmineTest pkgs.redmine // { - name = "redmine_3"; + v3-mysql = mysqlTest pkgs.redmine // { + name = "v3-mysql"; meta.maintainers = [ maintainers.aanderse ]; }; - redmine_4 = redmineTest pkgs.redmine_4 // { - name = "redmine_4"; + v4-mysql = mysqlTest pkgs.redmine_4 // { + name = "v4-mysql"; + meta.maintainers = [ maintainers.aanderse ]; + }; + + v4-pgsql = pgsqlTest pkgs.redmine_4 // { + name = "v4-pgsql"; meta.maintainers = [ maintainers.aanderse ]; }; }