feat: refactored database configuration

This commit is contained in:
Leyla Becker 2025-10-27 03:55:09 -05:00
parent e57c1df6e5
commit f9c27c82b6
8 changed files with 229 additions and 204 deletions

View file

@ -102,13 +102,6 @@
directories = ["leyla_documents" "eve_documents" "users_documents" "media"]; directories = ["leyla_documents" "eve_documents" "users_documents" "media"];
}; };
}; };
postgres = {
extraUsers = {
leyla = {
isAdmin = true;
};
};
};
}; };
systemd.network = { systemd.network = {
@ -220,6 +213,12 @@
}; };
services = { services = {
# PostgreSQL database server
postgresql = {
enable = true;
adminUsers = ["leyla"];
};
# temp enable desktop environment for setup # temp enable desktop environment for setup
# Enable the X11 windowing system. # Enable the X11 windowing system.
xserver.enable = true; xserver.enable = true;
@ -327,7 +326,7 @@
enable = true; enable = true;
domain = "home.jan-leila.com"; domain = "home.jan-leila.com";
openFirewall = true; openFirewall = true;
database = "postgres"; postgres.enable = true;
extensions = { extensions = {
sonos.enable = true; sonos.enable = true;

View file

@ -2,40 +2,31 @@
lib, lib,
config, config,
... ...
}: { }: let
config = lib.mkIf config.services.forgejo.enable ( usingPostgres = config.services.forgejo.database.type == "postgres";
lib.mkMerge [ in {
config = lib.mkIf config.services.forgejo.enable {
assertions = [
{ {
host = { assertion = !usingPostgres || config.services.postgresql.enable;
postgres = { message = "PostgreSQL must be enabled when Forgejo database type is postgres";
enable = true;
};
};
assertions = [
{
assertion = config.services.forgejo.settings.database.DB_TYPE == "postgres";
message = "Forgejo database type must be postgres";
}
];
} }
(lib.mkIf config.host.postgres.enable { {
host = { assertion = !(usingPostgres && config.services.forgejo.database.createDatabase) || (builtins.any (db: db == "forgejo") config.services.postgresql.ensureDatabases);
postgres = { message = "Forgejo built-in database creation failed - expected 'forgejo' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}";
extraUsers = { }
forgejo = { {
isClient = true; assertion = !(usingPostgres && config.services.forgejo.database.createDatabase) || (builtins.any (user: user.name == "forgejo") config.services.postgresql.ensureUsers);
createUser = true; message = "Forgejo built-in user creation failed - expected user 'forgejo' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}";
}; }
}; ];
extraDatabases = {
forgejo = { services.forgejo.database.createDatabase = lib.mkDefault usingPostgres;
name = "forgejo";
}; systemd.services.forgejo = lib.mkIf usingPostgres {
}; requires = [
}; config.systemd.services.postgresql.name
}; ];
}) };
] };
);
} }

View file

@ -2,55 +2,52 @@
lib, lib,
config, config,
... ...
}: let }: {
dbUser = "hass"; options.services.home-assistant = {
in { postgres = {
config = lib.mkIf config.services.home-assistant.enable ( enable = lib.mkOption {
lib.mkMerge [ type = lib.types.bool;
default = false;
description = "Use PostgreSQL instead of SQLite";
};
user = lib.mkOption {
type = lib.types.str;
default = "hass";
description = "Database user name";
};
database = lib.mkOption {
type = lib.types.str;
default = "hass";
description = "Database name";
};
};
};
config = lib.mkIf config.services.home-assistant.enable {
assertions = [
{ {
host = { assertion = !config.services.home-assistant.postgres.enable || config.services.postgresql.enable;
postgres = { message = "PostgreSQL must be enabled when using postgres database for Home Assistant";
enable = true;
};
};
assertions = [
{
assertion = config.services.home-assistant.database == "postgres";
message = "Home Assistant database type must be postgres";
}
];
} }
(lib.mkIf config.host.postgres.enable { ];
host = {
postgres = {
extraUsers = {
${dbUser} = {
isClient = true;
createUser = true;
};
};
extraDatabases = {
${dbUser} = {
name = dbUser;
};
};
};
};
services.home-assistant = { services.postgresql.databases.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable {
extraPackages = python3Packages: enable = true;
with python3Packages; [ user = config.services.home-assistant.postgres.user;
psycopg2 database = config.services.home-assistant.postgres.database;
]; };
};
systemd.services.home-assistant = { services.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable {
requires = [ extraPackages = python3Packages:
config.systemd.services.postgresql.name with python3Packages; [
]; psycopg2
}; ];
}) };
]
); systemd.services.home-assistant = lib.mkIf config.services.home-assistant.postgres.enable {
requires = [
config.systemd.services.postgresql.name
];
};
};
} }

View file

@ -3,24 +3,28 @@
config, config,
... ...
}: { }: {
config = lib.mkIf config.services.immich.enable (lib.mkMerge [ config = lib.mkIf config.services.immich.enable {
{ assertions = [
host = { {
postgres = { assertion = !config.services.immich.database.enable || config.services.postgresql.enable;
enable = true; message = "PostgreSQL must be enabled when using postgres database for Immich";
}; }
}; {
} assertion = !(config.services.immich.database.enable && config.services.immich.database.createDB) || (builtins.any (db: db == "immich") config.services.postgresql.ensureDatabases);
(lib.mkIf config.host.postgres.enable { message = "Immich built-in database creation failed - expected 'immich' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}";
host = { }
postgres = { {
extraUsers = { assertion = !(config.services.immich.database.enable && config.services.immich.database.createDB) || (builtins.any (user: user.name == "immich") config.services.postgresql.ensureUsers);
${config.services.immich.database.user} = { message = "Immich built-in user creation failed - expected user 'immich' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}";
isClient = true; }
}; ];
};
}; # Note: Immich has built-in database creation via services.immich.database.createDB we only add the systemd dependency
};
}) systemd.services.immich-server = lib.mkIf config.services.immich.database.enable {
]); requires = [
config.systemd.services.postgresql.name
];
};
};
} }

View file

@ -3,32 +3,46 @@
config, config,
... ...
}: { }: {
config = lib.mkIf config.services.panoramax.enable (lib.mkMerge [ options.services.panoramax = {
{ database = {
host = { postgres = {
postgres = { enable = lib.mkOption {
enable = true; type = lib.types.bool;
default = false;
description = "Use PostgreSQL instead of SQLite";
};
user = lib.mkOption {
type = lib.types.str;
default = "panoramax";
description = "Database user name";
};
database = lib.mkOption {
type = lib.types.str;
default = "panoramax";
description = "Database name";
}; };
}; };
} };
( };
lib.mkIf config.host.postgres.enable {
host = { config = lib.mkIf config.services.panoramax.enable {
postgres = { assertions = [
extraUsers = { {
${config.services.panoramax.database.user} = { assertion = !config.services.panoramax.database.postgres.enable || config.services.postgresql.enable;
isClient = true; message = "PostgreSQL must be enabled when using postgres database for Panoramax";
createUser = true;
};
};
extraDatabases = {
${config.services.panoramax.database.name} = {
name = config.services.panoramax.database.user;
};
};
};
};
} }
) ];
]);
services.postgresql.databases.panoramax = lib.mkIf config.services.panoramax.database.postgres.enable {
enable = true;
user = config.services.panoramax.database.postgres.user;
database = config.services.panoramax.database.postgres.database;
};
systemd.services.panoramax = lib.mkIf config.services.panoramax.database.postgres.enable {
requires = [
config.systemd.services.postgresql.name
];
};
};
} }

View file

@ -3,32 +3,28 @@
lib, lib,
... ...
}: { }: {
config = lib.mkIf config.services.paperless.enable (lib.mkMerge [ config = lib.mkIf config.services.paperless.enable {
{ assertions = [
host = { {
postgres = { assertion = !config.services.paperless.database.createLocally || config.services.postgresql.enable;
enable = true; message = "PostgreSQL must be enabled when using local postgres database for Paperless";
};
};
}
(
lib.mkIf config.host.postgres.enable {
host = {
postgres = {
extraUsers = {
${config.services.paperless.database.user} = {
isClient = true;
createUser = true;
};
};
extraDatabases = {
${config.services.paperless.database.user} = {
name = config.services.paperless.database.user;
};
};
};
};
} }
) {
]); assertion = !config.services.paperless.database.createLocally || (builtins.any (db: db == "paperless") config.services.postgresql.ensureDatabases);
message = "Paperless built-in database creation failed - expected 'paperless' in ensureDatabases but got: ${builtins.toString config.services.postgresql.ensureDatabases}";
}
{
assertion = !config.services.paperless.database.createLocally || (builtins.any (user: user.name == "paperless") config.services.postgresql.ensureUsers);
message = "Paperless built-in user creation failed - expected user 'paperless' in ensureUsers but got: ${builtins.toString (builtins.map (u: u.name) config.services.postgresql.ensureUsers)}";
}
];
services.paperless.database.createLocally = lib.mkDefault true;
systemd.services.paperless-scheduler = lib.mkIf config.services.paperless.database.createLocally {
requires = [
config.systemd.services.postgresql.name
];
};
};
} }

View file

@ -5,7 +5,7 @@
}: let }: let
dataDir = "/var/lib/postgresql/16"; dataDir = "/var/lib/postgresql/16";
in { in {
config = lib.mkIf (config.host.postgres.enable && config.host.impermanence.enable) { config = lib.mkIf (config.services.postgresql.enable && config.host.impermanence.enable) {
assertions = [ assertions = [
{ {
assertion = config.services.postgresql.dataDir == dataDir; assertion = config.services.postgresql.dataDir == dataDir;

View file

@ -4,70 +4,94 @@
pkgs, pkgs,
... ...
}: let }: let
adminUsers = lib.lists.filter (user: user.isAdmin) (lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraUsers); enabledDatabases = lib.filterAttrs (_: db: db.enable) config.services.postgresql.databases;
clientUsers = lib.lists.filter (user: user.isClient) (lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraUsers); extraDatabasesList = config.services.postgresql.extraDatabases;
createUsers = lib.lists.filter (user: user.createUser) (lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraUsers);
createDatabases = lib.attrsets.mapAttrsToList (_: user: user) config.host.postgres.extraDatabases; serviceDatabaseUsers = lib.mapAttrsToList (_: db: {
name = db.user;
ensureDBOwnership = true;
}) (lib.filterAttrs (_: db: db.ensureUser) enabledDatabases);
extraDatabaseUsers =
builtins.map (dbName: {
name = dbName;
ensureDBOwnership = true;
})
extraDatabasesList;
serviceDatabases = lib.mapAttrsToList (_: db: db.database) enabledDatabases;
extraDatabaseNames = extraDatabasesList;
serviceUserMappings = lib.mapAttrsToList (_: db: "user_map ${db.user} ${db.user}") enabledDatabases;
extraUserMappings = builtins.map (dbName: "user_map ${dbName} ${dbName}") extraDatabasesList;
builtinServiceMappings = let
forgejoMapping = lib.optional (config.services.forgejo.enable && config.services.forgejo.database.type == "postgres") "user_map forgejo forgejo";
immichMapping = lib.optional (config.services.immich.enable && config.services.immich.database.enable) "user_map immich immich";
paperlessMapping = lib.optional (config.services.paperless.enable && config.services.paperless.database.createLocally) "user_map paperless paperless";
in
forgejoMapping ++ immichMapping ++ paperlessMapping;
in { in {
options = { options = {
host.postgres = { services.postgresql = {
enable = lib.mkEnableOption "enable postgres"; databases = lib.mkOption {
extraUsers = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { type = lib.types.attrsOf (lib.types.submodule ({name, ...}: {
options = { options = {
name = lib.mkOption { enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = "Whether to create this database and user";
};
user = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = name; default = name;
description = "Database user name";
}; };
isAdmin = lib.mkOption { database = lib.mkOption {
type = lib.types.bool; type = lib.types.str;
default = false; default = name;
description = "Database name";
}; };
isClient = lib.mkOption { ensureUser = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = false; default = true;
}; description = "Whether to ensure the user exists";
createUser = lib.mkOption {
type = lib.types.bool;
default = false;
}; };
}; };
})); }));
default = {}; default = {};
description = "Databases to create for services";
}; };
extraDatabases = lib.mkOption { extraDatabases = lib.mkOption {
type = lib.types.attrsOf (lib.types.submodule ({name, ...}: { type = lib.types.listOf lib.types.str;
options = { default = [];
name = lib.mkOption { description = "Additional databases to create (user name will match database name)";
type = lib.types.str; example = ["custom_db" "test_db"];
default = name; };
};
}; adminUsers = lib.mkOption {
})); type = lib.types.listOf lib.types.str;
default = {}; default = [];
description = "System users who should have PostgreSQL superuser access";
example = ["leyla" "admin"];
}; };
}; };
}; };
config = lib.mkIf config.host.postgres.enable { config = lib.mkIf config.services.postgresql.enable {
services = { services = {
postgresql = { postgresql = {
enable = true;
package = pkgs.postgresql_16; package = pkgs.postgresql_16;
ensureUsers = ensureUsers =
[ [
{ {name = "postgres";}
name = "postgres";
}
] ]
++ ( ++ serviceDatabaseUsers ++ extraDatabaseUsers;
builtins.map (user: {
name = user.name; ensureDatabases = serviceDatabases ++ extraDatabaseNames;
ensureDBOwnership = true;
})
createUsers
);
ensureDatabases = builtins.map (database: database.name) createDatabases;
identMap = identMap =
'' ''
# ArbitraryMapName systemUser DBUser # ArbitraryMapName systemUser DBUser
@ -77,16 +101,16 @@ in {
superuser_map postgres postgres superuser_map postgres postgres
'' ''
+ ( + (
lib.strings.concatLines (builtins.map (user: "superuser_map ${user.name} postgres") adminUsers) lib.strings.concatLines (builtins.map (user: "superuser_map ${user} postgres") config.services.postgresql.adminUsers)
) )
+ '' + ''
# Client Users # Client Users
'' ''
+ ( + (
lib.strings.concatLines (builtins.map (user: "user_map ${user.name} ${user.name}") clientUsers) lib.strings.concatLines (serviceUserMappings ++ extraUserMappings ++ builtinServiceMappings)
); );
# configuration here lets users access the db that matches their name and lets user postgres access everything
authentication = pkgs.lib.mkOverride 10 '' authentication = pkgs.lib.mkOverride 10 ''
# type database DBuser origin-address auth-method optional_ident_map # type database DBuser origin-address auth-method optional_ident_map
local all postgres peer map=superuser_map local all postgres peer map=superuser_map