193 lines
5.9 KiB
Nix
193 lines
5.9 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
let
|
|
cfg = config.shb.postgresql;
|
|
contracts = pkgs.callPackage ../contracts {};
|
|
|
|
upgrade-script = old: new:
|
|
let
|
|
oldStr = builtins.toString old;
|
|
newStr = builtins.toString new;
|
|
|
|
oldPkg = pkgs.${"postgresql_${oldStr}"};
|
|
newPkg = pkgs.${"postgresql_${newStr}"};
|
|
in
|
|
pkgs.writeScriptBin "upgrade-pg-cluster-${oldStr}-${newStr}" ''
|
|
set -eux
|
|
# XXX it's perhaps advisable to stop all services that depend on postgresql
|
|
systemctl stop postgresql
|
|
|
|
export NEWDATA="/var/lib/postgresql/${newPkg.psqlSchema}"
|
|
export NEWBIN="${newPkg}/bin"
|
|
|
|
export OLDDATA="/var/lib/postgresql/${oldPkg.psqlSchema}"
|
|
export OLDBIN="${oldPkg}/bin"
|
|
|
|
install -d -m 0700 -o postgres -g postgres "$NEWDATA"
|
|
cd "$NEWDATA"
|
|
sudo -u postgres $NEWBIN/initdb -D "$NEWDATA"
|
|
|
|
sudo -u postgres $NEWBIN/pg_upgrade \
|
|
--old-datadir "$OLDDATA" --new-datadir "$NEWDATA" \
|
|
--old-bindir $OLDBIN --new-bindir $NEWBIN \
|
|
"$@"
|
|
'';
|
|
in
|
|
{
|
|
options.shb.postgresql = {
|
|
debug = lib.mkOption {
|
|
type = lib.types.bool;
|
|
description = ''
|
|
Enable debugging options.
|
|
|
|
Currently enables shared_preload_libraries = "auto_explain, pg_stat_statements"
|
|
|
|
See https://www.postgresql.org/docs/current/pgstatstatements.html'';
|
|
default = false;
|
|
};
|
|
enableTCPIP = lib.mkOption {
|
|
type = lib.types.bool;
|
|
description = "Enable TCP/IP connection on given port.";
|
|
default = false;
|
|
};
|
|
|
|
backup = lib.mkOption {
|
|
type = contracts.backup;
|
|
description = ''
|
|
Backup configuration. This is an output option.
|
|
|
|
Use it to initialize a block implementing the "backup" contract.
|
|
For example, with the restic block:
|
|
|
|
```
|
|
shb.restic.instances."postgresql" = {
|
|
enable = true;
|
|
|
|
# Options specific to Restic.
|
|
} // config.shb.nextcloud.backup;
|
|
```
|
|
'';
|
|
readOnly = true;
|
|
default = {
|
|
user = "postgresql";
|
|
sourceDirectories = [
|
|
/tmp/postgresql_backup
|
|
];
|
|
excludePatterns = [ ];
|
|
|
|
hooks.before_backup = [''
|
|
set -e -o pipefail
|
|
|
|
umask 077 # Ensure backup is only readable by postgres user
|
|
|
|
rm -rf /tmp/postgresql_backup # Clean up in case after_backup hook wasn't run.
|
|
mkdir /tmp/postgresql_backup
|
|
|
|
${pkgs.psql}/bin/pg_dumpall | ${pkgs.gzip}/bin/gzip --rsyncable > /tmp/postgresql_backup/pg_dumpall.sql.gz
|
|
''];
|
|
|
|
hooks.after_backup = [''
|
|
set -e -o pipefail
|
|
rm -rf /tmp/postgresql_backup
|
|
''];
|
|
};
|
|
};
|
|
|
|
ensures = lib.mkOption {
|
|
description = "List of username, database and/or passwords that should be created.";
|
|
type = lib.types.listOf (lib.types.submodule {
|
|
options = {
|
|
username = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Postgres user name.";
|
|
};
|
|
|
|
database = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Postgres database.";
|
|
};
|
|
|
|
passwordFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
description = "Optional password file for the postgres user. If not given, only peer auth is accepted for this user, otherwise password auth is allowed.";
|
|
default = null;
|
|
example = "/run/secrets/postgresql/password";
|
|
};
|
|
};
|
|
});
|
|
default = [];
|
|
};
|
|
};
|
|
|
|
config =
|
|
let
|
|
commonConfig = {
|
|
systemd.services.postgresql.serviceConfig.Restart = "always";
|
|
|
|
services.postgresql.settings = {
|
|
};
|
|
};
|
|
|
|
tcpConfig = {
|
|
services.postgresql.enableTCPIP = true;
|
|
services.postgresql.authentication = lib.mkOverride 10 ''
|
|
#type database DBuser origin-address auth-method
|
|
local all all peer
|
|
# ipv4
|
|
host all all 127.0.0.1/32 password
|
|
# ipv6
|
|
host all all ::1/128 password
|
|
'';
|
|
};
|
|
|
|
dbConfig = ensureCfgs: {
|
|
services.postgresql.enable = lib.mkDefault ((builtins.length ensureCfgs) > 0);
|
|
services.postgresql.ensureDatabases = map ({ database, ... }: database) ensureCfgs;
|
|
services.postgresql.ensureUsers = map ({ username, database, ... }: {
|
|
name = username;
|
|
ensureDBOwnership = true;
|
|
ensureClauses.login = true;
|
|
}) ensureCfgs;
|
|
};
|
|
|
|
pwdConfig = ensureCfgs: {
|
|
systemd.services.postgresql.postStart =
|
|
let
|
|
prefix = ''
|
|
$PSQL -tA <<'EOF'
|
|
DO $$
|
|
DECLARE password TEXT;
|
|
BEGIN
|
|
'';
|
|
suffix = ''
|
|
END $$;
|
|
EOF
|
|
'';
|
|
exec = { username, passwordFile, ... }: ''
|
|
password := trim(both from replace(pg_read_file('${passwordFile}'), E'\n', '''));
|
|
EXECUTE format('ALTER ROLE ${username} WITH PASSWORD '''%s''';', password);
|
|
'';
|
|
cfgsWithPasswords = builtins.filter (cfg: cfg.passwordFile != null) ensureCfgs;
|
|
in
|
|
if (builtins.length cfgsWithPasswords) == 0 then "" else
|
|
prefix + (lib.concatStrings (map exec cfgsWithPasswords)) + suffix;
|
|
};
|
|
|
|
debugConfig = enableDebug: lib.mkIf enableDebug {
|
|
services.postgresql.settings.shared_preload_libraries = "auto_explain, pg_stat_statements";
|
|
};
|
|
in
|
|
lib.mkMerge ([
|
|
commonConfig
|
|
(dbConfig cfg.ensures)
|
|
(pwdConfig cfg.ensures)
|
|
(lib.mkIf cfg.enableTCPIP tcpConfig)
|
|
(debugConfig cfg.debug)
|
|
{
|
|
environment.systemPackages = lib.mkIf config.services.postgresql.enable [
|
|
(upgrade-script 13 14)
|
|
(upgrade-script 14 15)
|
|
(upgrade-script 15 16)
|
|
];
|
|
}
|
|
]);
|
|
}
|