diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix
index 3bedfe96a1805..0c57398d4f135 100644
--- a/nixos/modules/services/databases/postgresql.nix
+++ b/nixos/modules/services/databases/postgresql.nix
@@ -28,8 +28,86 @@ in
###### interface
- options = {
-
+ options = let
+ databaseOpts = {
+ options = {
+ name = mkOption {
+ type = types.str;
+ description = ''
+ The name of the database to ensure.
+ '';
+ example = "gitlab";
+ };
+ owner = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ description = ''
+ The name of the database owner.
+
+ The user will be created if it doesn't already
+ exist. The owner of an existing database will be
+ changed if it's changed here, and all objects in the
+ database owned by the previous owner will be
+ reassigned to the new one.
+
+ See
+ Creating a Database,
+ REASSIGN OWNED
+ and
+ ALTER DATABASE
+ for more details.
+ '';
+ };
+ extensions = mkOption {
+ type = with types; listOf str;
+ default = [];
+ description = ''
+ Extensions to add to the database.
+
+ Extensions will be added to existing databases, but
+ not removed if they're removed from the list.
+
+ See
+
+ CREATE EXTENSION for more details.
+ '';
+ example = [ "pg_trgm" "hstore" ];
+ };
+ };
+ };
+ userOpts = {
+ 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";
+ }
+ '';
+ };
+ };
+ };
+ in
+ {
services.postgresql = {
enable = mkOption {
@@ -97,13 +175,23 @@ in
};
ensureDatabases = mkOption {
- type = types.listOf types.str;
+ type = with types;
+ listOf (coercedTo str
+ (name: { inherit name; })
+ (submodule databaseOpts));
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.
+ Ensure the existence of the listed databases with their
+ respective owner and extensions.
+
+ This option will never delete existing databases; if the
+ name of a database is changed here, a new database with that
+ name will be created and the old one will remain. Databases
+ created once through this option or otherwise have to be
+ removed manually.
+
+ Extensions will be added to existing databases, but not
+ removed if they're removed from the list.
'';
example = [
"gitea"
@@ -112,37 +200,7 @@ in
};
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";
- }
- '';
- };
- };
- });
+ type = with types; listOf (submodule userOpts);
default = [];
description = ''
Ensures that the specified users exist and have at least the ensured permissions.
@@ -320,6 +378,7 @@ in
# Wait for PostgreSQL to be ready to accept connections.
postStart =
''
+ set -eu
PSQL="${pkgs.sudo}/bin/sudo -u ${cfg.superUser} psql --port=${toString cfg.port}"
while ! $PSQL -d postgres -c "" 2> /dev/null; do
@@ -333,18 +392,51 @@ in
''}
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}"
+ ''
+ + concatMapStrings (user: ''
+ $PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='${user}'" | grep -q 1 || $PSQL -tAc "CREATE USER ${user}"
+ '') (unique (concatMap (a: if a ? owner then if a.owner != null then [a.owner] else [] else [a.name]) (cfg.ensureUsers ++ cfg.ensureDatabases)))
+ + concatMapStrings (db: ''
+ database_exists=$($PSQL -tAc "SELECT 'true' FROM pg_database WHERE datname = '${db.name}'")
+ if [[ "$database_exists" != "true" ]]; then
+ $PSQL -tAc 'CREATE DATABASE "${db.name}" ${optionalString (db.owner != null) ''OWNER "${db.owner}"''}'
+ fi
+
+ echo "${db.name}" >> "${cfg.dataDir}/databases"
+
+ ${optionalString (db.owner != null) ''
+ current_owner=$($PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${db.name}'")
+ if [[ "$current_owner" != "${db.owner}" ]]; then
+ $PSQL -tAc 'ALTER DATABASE "${db.name}" OWNER TO "${db.owner}"'
+ if [[ -e "${cfg.dataDir}/.reassigning_${db.name}" ]]; then
+ echo "Reassigning ownership of ${db.name} to ${db.owner} failed on last boot. Failing..."
+ exit 1
+ fi
+ touch "${cfg.dataDir}/.reassigning_${db.name}"
+ $PSQL "${db.name}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${db.owner}\""
+ rm "${cfg.dataDir}/.reassigning_${db.name}"
+ fi
+ ''}
+ ''
+ + concatMapStrings (extension: ''
+ $PSQL '${db.name}' -tAc "CREATE EXTENSION IF NOT EXISTS ${extension}"
+ '') db.extensions
+ ) cfg.ensureDatabases
+ + ''
+ touch "${cfg.dataDir}/databases"
+ if [[ -f "${cfg.dataDir}/existing_databases" ]]; then
+ for db in $(grep -vFxf "${cfg.dataDir}/databases" "${cfg.dataDir}/existing_databases" || echo ""); do
+ echo "Dropping database $db!"
+ $PSQL -tAc "DROP DATABASE \"$db\""
+ done
+ fi
+ mv "${cfg.dataDir}/databases" "${cfg.dataDir}/existing_databases"
+ ''
+ + concatMapStrings (user: ''
${concatStringsSep "\n" (mapAttrsToList (database: permission: ''
$PSQL -tAc 'GRANT ${permission} ON ${database} TO ${user.name}'
'') user.ensurePermissions)}
- '') cfg.ensureUsers}
- '';
+ '') cfg.ensureUsers;
unitConfig.RequiresMountsFor = "${cfg.dataDir}";
};
diff --git a/nixos/modules/services/misc/gitlab.nix b/nixos/modules/services/misc/gitlab.nix
index 07ea9c4584371..5f463d8950130 100644
--- a/nixos/modules/services/misc/gitlab.nix
+++ b/nixos/modules/services/misc/gitlab.nix
@@ -602,28 +602,12 @@ in {
# We use postgres as the main data store.
services.postgresql = optionalAttrs databaseActuallyCreateLocally {
enable = true;
- ensureUsers = singleton { name = cfg.databaseUsername; };
+ ensureDatabases = singleton {
+ name = cfg.databaseName;
+ owner = cfg.databaseUsername;
+ extensions = [ "pg_trgm" ];
+ };
};
- # The postgresql module doesn't currently support concepts like
- # objects owners and extensions; for now we tack on what's needed
- # here.
- systemd.services.postgresql.postStart = mkAfter (optionalString databaseActuallyCreateLocally ''
- set -eu
-
- $PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = '${cfg.databaseName}'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "${cfg.databaseName}" OWNER "${cfg.databaseUsername}"'
- current_owner=$($PSQL -tAc "SELECT pg_catalog.pg_get_userbyid(datdba) FROM pg_catalog.pg_database WHERE datname = '${cfg.databaseName}'")
- if [[ "$current_owner" != "${cfg.databaseUsername}" ]]; then
- $PSQL -tAc 'ALTER DATABASE "${cfg.databaseName}" OWNER TO "${cfg.databaseUsername}"'
- if [[ -e "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}" ]]; then
- echo "Reassigning ownership of database ${cfg.databaseName} to user ${cfg.databaseUsername} failed on last boot. Failing..."
- exit 1
- fi
- touch "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
- $PSQL "${cfg.databaseName}" -tAc "REASSIGN OWNED BY \"$current_owner\" TO \"${cfg.databaseUsername}\""
- rm "${config.services.postgresql.dataDir}/.reassigning_${cfg.databaseName}"
- fi
- $PSQL '${cfg.databaseName}' -tAc "CREATE EXTENSION IF NOT EXISTS pg_trgm"
- '');
# Use postfix to send out mails.
services.postfix.enable = mkDefault true;