first
This commit is contained in:
commit
6648ab0f79
3 changed files with 826 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/result*
|
60
flake.lock
Normal file
60
flake.lock
Normal file
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1681202837,
|
||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1684215771,
|
||||
"narHash": "sha256-fsum28z+g18yreNa1Y7MPo9dtps5h1VkHfZbYQ+YPbk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "963006aab35e3e8ebbf6052b6bf4ea712fdd3c28",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-unstable",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
765
flake.nix
Normal file
765
flake.nix
Normal file
|
@ -0,0 +1,765 @@
|
|||
{
|
||||
description = "Postgresql";
|
||||
|
||||
inputs = {
|
||||
nixpkgs = {
|
||||
url = "nixpkgs/nixos-unstable";
|
||||
};
|
||||
flake-utils = {
|
||||
url = "github:numtide/flake-utils";
|
||||
};
|
||||
};
|
||||
outputs = { self, nixpkgs, flake-utils, ... }@inputs:
|
||||
(
|
||||
flake-utils.lib.eachDefaultSystem
|
||||
(system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
};
|
||||
in
|
||||
{
|
||||
packages = {
|
||||
scram-sha-256 =
|
||||
let
|
||||
pname = "scram-sha-256";
|
||||
version = "1.0.1";
|
||||
in
|
||||
pkgs.buildGoModule {
|
||||
inherit pname version;
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "supercaracal";
|
||||
repo = pname;
|
||||
rev = "v${version}";
|
||||
hash = "sha256-4/6rog+Or7YBer+Gdc38OPC2UVrXmeKUK/hsZrnYhbo=";
|
||||
};
|
||||
vendorHash = "sha256-qNJSCLMPdWgK/eFPmaYBcgH3P6jHBqQeU4gR6kE/+AE=";
|
||||
meta = with pkgs.lib; {
|
||||
homepage = "https://github.com/supercaracal/scram-sha-256";
|
||||
description = "A backup program that is fast, efficient and secure";
|
||||
platforms = platforms.all;
|
||||
license = licenses.mit;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
)
|
||||
) // {
|
||||
nixosModules = {
|
||||
postgresql = { config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.jcollie.postgresql;
|
||||
in
|
||||
{
|
||||
options = {
|
||||
jcollie.postgresql = lib.options.mkOption {
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.options.mkEnableOption "PostgreSQL";
|
||||
port = lib.options.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 5432;
|
||||
};
|
||||
superuser = lib.options.mkOption {
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
username = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "postgres";
|
||||
};
|
||||
password = lib.options.mkOption {
|
||||
type = lib.types.addCheck lib.types.str (x: lib.hasPrefix "SCRAM-SHA-256$" x);
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
replication = lib.options.mkOption {
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.options.mkEnableOption "Enable PostgreSQL replication";
|
||||
role = lib.options.mkOption {
|
||||
type = lib.types.enum [
|
||||
"primary"
|
||||
"replica"
|
||||
];
|
||||
default = "primary";
|
||||
};
|
||||
username = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "replication";
|
||||
};
|
||||
passwordFile = lib.options.mkOption {
|
||||
type = lib.types.path;
|
||||
};
|
||||
# password = lib.options.mkOption {
|
||||
# type = lib.types.str;
|
||||
# };
|
||||
primary = lib.options.mkOption {
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
hostname = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
port = lib.options.mkOption {
|
||||
type = lib.types.port;
|
||||
default = 5432;
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
backup = lib.options.mkOption {
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.options.mkEnableOption "Backup the database with PGBackRest";
|
||||
processMax = lib.options.mkOption {
|
||||
type = lib.types.ints.positive;
|
||||
default = 4;
|
||||
};
|
||||
log = lib.options.mkOption {
|
||||
type = lib.types.submodule {
|
||||
options =
|
||||
let
|
||||
logLevelType = lib.types.enum [
|
||||
"off"
|
||||
"error"
|
||||
"warn"
|
||||
"info"
|
||||
"detail"
|
||||
"debug"
|
||||
"trace"
|
||||
];
|
||||
in
|
||||
{
|
||||
console = lib.options.mkOption {
|
||||
type = logLevelType;
|
||||
default = "detail";
|
||||
};
|
||||
file = lib.options.mkOption {
|
||||
type = logLevelType;
|
||||
default = "off";
|
||||
};
|
||||
stderr = lib.options.mkOption {
|
||||
type = logLevelType;
|
||||
default = "off";
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
cipher = lib.options.mkOption {
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
password = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
type = lib.options.mkOption {
|
||||
type = lib.types.enum [
|
||||
"aes-256-cbc"
|
||||
];
|
||||
default = "aes-256-cbc";
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
storage = lib.options.mkOption {
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
type = lib.options.mkOption {
|
||||
type = lib.types.enum [
|
||||
"azure"
|
||||
"b2"
|
||||
];
|
||||
};
|
||||
b2 = lib.options.mkOption {
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
account_id = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
account_key = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
endpoint = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
region = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
bucket = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
path = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
azure = lib.options.mkOption {
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
account_name = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
account_key = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
container = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
path = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
|
||||
healthcheck = lib.options.mkOption {
|
||||
type = lib.types.submodule {
|
||||
options = {
|
||||
enable = lib.options.mkEnableOption "use healthcheck";
|
||||
api_url = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
api_key = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
timeout = lib.options.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 86400;
|
||||
};
|
||||
grace = lib.options.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 14400;
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
|
||||
users = lib.options.mkOption {
|
||||
type = lib.types.listOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
username = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
password = lib.options.mkOption {
|
||||
type = lib.types.addCheck lib.types.str (x: lib.hasPrefix "SCRAM-SHA-256$" x);
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
databases = lib.options.mkOption {
|
||||
type = lib.types.listOf (
|
||||
lib.types.submodule {
|
||||
options = {
|
||||
name = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
owner = lib.options.mkOption {
|
||||
type = lib.types.str;
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
|
||||
config =
|
||||
let
|
||||
toStr = value:
|
||||
if true == value then "yes"
|
||||
else if false == value then "no"
|
||||
else if lib.isString value then "'${lib.replaceStrings ["'"] ["''"] value}'"
|
||||
else toString value;
|
||||
|
||||
escapeShell = value: lib.replaceStrings [ "$" ] [ "\\$" ] value;
|
||||
|
||||
postgresql = pkgs.postgresql_15.withPackages (
|
||||
plugins: [
|
||||
plugins.postgis
|
||||
]
|
||||
);
|
||||
|
||||
rcloneConfig = pkgs.writeTextFile {
|
||||
name = "rclone.conf";
|
||||
text = {
|
||||
"b2" = ''
|
||||
[b2]
|
||||
type = b2
|
||||
account = ${cfg.backup.storage.b2.account_id}
|
||||
key = ${cfg.backup.storage.b2.account_key}
|
||||
'';
|
||||
"azure" = ''
|
||||
[azure]
|
||||
type = azureblob
|
||||
account = ${cfg.backup.storage.azure.account_name}
|
||||
key = ${cfg.backup.storage.azure.account_key}
|
||||
'';
|
||||
}.${cfg.backup.storage.type};
|
||||
};
|
||||
|
||||
rclone = lib.mkIf (cfg.backup.enable) (
|
||||
pkgs.writeShellScriptBin "rclone" ''
|
||||
exec ${pkgs.rclone}/bin/rclone --config ${rcloneConfig} "''$@"
|
||||
''
|
||||
);
|
||||
|
||||
dataDir = "/var/lib/postgresql/15";
|
||||
|
||||
pgbackrestEnvironment =
|
||||
{
|
||||
PGBACKREST_LOG_LEVEL_CONSOLE = cfg.backup.log.console;
|
||||
PGBACKREST_LOG_LEVEL_FILE = cfg.backup.log.file;
|
||||
PGBACKREST_LOG_LEVEL_STDERR = cfg.backup.log.stderr;
|
||||
PGBACKREST_PG1_PATH = dataDir;
|
||||
PGBACKREST_PROCESS_MAX = "${builtins.toString cfg.backup.processMax}";
|
||||
PGBACKREST_REPO1_CIPHER_PASS = cfg.backup.cipher.password;
|
||||
PGBACKREST_REPO1_CIPHER_TYPE = cfg.backup.cipher.type;
|
||||
PGBACKREST_REPO1_RETENTION_FULL = "14";
|
||||
PGBACKREST_REPO1_RETENTION_FULL_TYPE = "time";
|
||||
PGBACKREST_STANZA = "${config.networking.hostName}.${config.networking.domain}";
|
||||
} // (
|
||||
{
|
||||
"azure" = {
|
||||
PGBACKREST_REPO1_AZURE_ACCOUNT = cfg.backup.storage.azure.account_name;
|
||||
PGBACKREST_REPO1_AZURE_CONTAINER = cfg.backup.storage.azure.container;
|
||||
PGBACKREST_REPO1_AZURE_KEY = cfg.backup.storage.azure.account_key;
|
||||
PGBACKREST_REPO1_PATH = cfg.backup.storage.azure.path;
|
||||
PGBACKREST_REPO1_TYPE = "azure";
|
||||
};
|
||||
"b2" = {
|
||||
PGBACKREST_REPO1_PATH = cfg.backup.storage.b2.path;
|
||||
PGBACKREST_REPO1_S3_BUCKET = cfg.backup.storage.b2.bucket;
|
||||
PGBACKREST_REPO1_S3_ENDPOINT = cfg.backup.storage.b2.endpoint;
|
||||
PGBACKREST_REPO1_S3_KEY = cfg.backup.storage.b2.account_id;
|
||||
PGBACKREST_REPO1_S3_KEY_SECRET = cfg.backup.storage.b2.account_key;
|
||||
PGBACKREST_REPO1_S3_REGION = cfg.backup.storage.b2.region;
|
||||
PGBACKREST_REPO1_TYPE = "s3";
|
||||
};
|
||||
}.${cfg.backup.storage.type}
|
||||
);
|
||||
pgbackrest = lib.mkIf (cfg.backup.enable) (
|
||||
let
|
||||
environment = lib.concatStringsSep "\n" (
|
||||
lib.mapAttrsToList
|
||||
(
|
||||
n: v:
|
||||
''export ${n}="${builtins.toString v}"''
|
||||
)
|
||||
pgbackrestEnvironment
|
||||
);
|
||||
in
|
||||
pkgs.writeShellScriptBin "pgbackrest" ''
|
||||
${environment}
|
||||
exec ${pkgs.pgbackrest}/bin/pgbackrest "''$@"
|
||||
''
|
||||
);
|
||||
in
|
||||
lib.mkIf cfg.enable {
|
||||
environment.systemPackages = [
|
||||
postgresql
|
||||
pgbackrest
|
||||
rclone
|
||||
self.packages.${pkgs.system}.scram-sha-256
|
||||
];
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ cfg.port ];
|
||||
|
||||
security.acme.certs."${config.networking.hostName}.${config.networking.domain}" = {
|
||||
reloadServices = [
|
||||
"postgresql"
|
||||
];
|
||||
};
|
||||
|
||||
users.groups.postgres.gid = config.ids.gids.postgres;
|
||||
users.users.postgres = {
|
||||
name = "postgres";
|
||||
uid = config.ids.uids.postgres;
|
||||
group = "postgres";
|
||||
description = "PostgreSQL Server";
|
||||
home = "/var/lib/postgresql/15";
|
||||
useDefaultShell = true;
|
||||
};
|
||||
|
||||
systemd.services.postgresql =
|
||||
let
|
||||
hbaFile = pkgs.writeTextDir "pg_hba.conf" ''
|
||||
local all all ident map=default
|
||||
hostnossl all all all reject
|
||||
hostssl all all all scram-sha-256
|
||||
local replication all ident map=default
|
||||
hostnossl replication all all reject
|
||||
hostssl replication all all scram-sha-256
|
||||
'';
|
||||
identFile = pkgs.writeTextDir "pg_ident.conf" ''
|
||||
default root postgres
|
||||
default postgres postgres
|
||||
'';
|
||||
archiveCommand = "${pkgs.pgbackrest}/bin/pgbackrest archive-push %p";
|
||||
settings = {
|
||||
bgwriter_flush_after = "512kB";
|
||||
checkpoint_flush_after = "256kB";
|
||||
data_directory = dataDir;
|
||||
datestyle = "iso, mdy";
|
||||
default_text_search_config = "pg_catalog.english";
|
||||
dynamic_shared_memory_type = "posix";
|
||||
hba_file = "${dataDir}/pg_hba.conf";
|
||||
hot_standby = "on";
|
||||
ident_file = "${dataDir}/pg_ident.conf";
|
||||
lc_messages = "en_US.utf8";
|
||||
lc_monetary = "en_US.utf8";
|
||||
lc_numeric = "en_US.utf8";
|
||||
lc_time = "en_US.utf8";
|
||||
listen_addresses = "*";
|
||||
log_destination = "stderr";
|
||||
log_connections = "on";
|
||||
log_line_prefix = "%m [%l][%p] {%h} :%d:%u:%c: ";
|
||||
# log_statement = "all";
|
||||
log_timezone = "America/Chicago";
|
||||
logging_collector = "off";
|
||||
max_connections = 100;
|
||||
max_wal_senders = "3";
|
||||
max_wal_size = "1GB";
|
||||
min_wal_size = "80MB";
|
||||
password_encryption = "scram-sha-256";
|
||||
port = cfg.port;
|
||||
shared_buffers = "128MB";
|
||||
ssl = "on";
|
||||
ssl_cert_file = "/run/credentials/postgresql.service/fullchain.pem";
|
||||
ssl_key_file = "/run/credentials/postgresql.service/key.pem";
|
||||
timezone = "America/Chicago";
|
||||
unix_socket_directories = "/run/postgresql";
|
||||
wal_level = "replica";
|
||||
wal_log_hints = "on";
|
||||
} // (
|
||||
if (cfg.backup.enable)
|
||||
then {
|
||||
archive_command = archiveCommand;
|
||||
archive_mode = "on";
|
||||
}
|
||||
else
|
||||
{ }
|
||||
);
|
||||
configFile = pkgs.writeTextDir "postgresql.conf" (
|
||||
lib.concatStrings (
|
||||
lib.mapAttrsToList
|
||||
(
|
||||
n: v:
|
||||
"${n} = ${toStr v}\n"
|
||||
)
|
||||
settings
|
||||
)
|
||||
);
|
||||
backupSetup =
|
||||
if (cfg.backup.enable)
|
||||
then
|
||||
''
|
||||
init=''$(${pkgs.pgbackrest}/bin/pgbackrest info --output=json | ${pkgs.jq}/bin/jq '.[0].status.code == 0')
|
||||
if [ "$init" != "true" ]
|
||||
then
|
||||
${postgresql}/bin/pg_ctl -o "-c ssl=off -c listen_addresses=''' -p 55432" -w start
|
||||
${pkgs.pgbackrest}/bin/pgbackrest --pg1-port=55432 stanza-create
|
||||
${postgresql}/bin/pg_ctl -m fast -w stop
|
||||
fi
|
||||
''
|
||||
else
|
||||
"";
|
||||
in
|
||||
{
|
||||
description = "PostgreSQL Server";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
environment = {
|
||||
PGDATA = dataDir;
|
||||
} // (
|
||||
if (cfg.backup.enable && (!cfg.replication.enable || cfg.replication.role == "primary"))
|
||||
then
|
||||
pgbackrestEnvironment
|
||||
else { }
|
||||
) // (
|
||||
if (cfg.replication.enable && cfg.replication.role == "replica")
|
||||
then {
|
||||
PGPASSFILE = "${dataDir}/.pgpass";
|
||||
}
|
||||
else { }
|
||||
);
|
||||
path = [
|
||||
postgresql
|
||||
pgbackrest
|
||||
];
|
||||
|
||||
preStart =
|
||||
if (!cfg.replication.enable || cfg.replication.role == "primary")
|
||||
then
|
||||
''
|
||||
if [ ! -s "${dataDir}/PG_VERSION" ]
|
||||
then
|
||||
${postgresql}/bin/initdb \
|
||||
--username=${cfg.superuser.username} \
|
||||
--pwfile=<(echo -n "${escapeShell cfg.superuser.password}") \
|
||||
--data-checksums \
|
||||
--locale=en_US.utf8 \
|
||||
--auth-host=scram-sha-256 \
|
||||
--auth-local=trust
|
||||
fi
|
||||
|
||||
ln -sfn "${configFile}/postgresql.conf" "${dataDir}/postgresql.conf"
|
||||
ln -sfn "${hbaFile}/pg_hba.conf" "${dataDir}/pg_hba.conf"
|
||||
ln -sfn "${identFile}/pg_ident.conf" "${dataDir}/pg_ident.conf"
|
||||
|
||||
${backupSetup}
|
||||
''
|
||||
else
|
||||
''
|
||||
read -r password < ${cfg.replication.passwordFile}
|
||||
(umask 077; echo "${cfg.replication.primary.hostname}:${builtins.toString cfg.replication.primary.port}:replication:${cfg.replication.username}:''${password}" > ${dataDir}/.pgpass)
|
||||
chmod 0600 ${dataDir}/.pgpass
|
||||
|
||||
if [ ! -s "${dataDir}/PG_VERSION" ]
|
||||
then
|
||||
${postgresql}/bin/pg_basebackup \
|
||||
--verbose \
|
||||
--progress \
|
||||
--pgdata=${dataDir} \
|
||||
--write-recovery-conf \
|
||||
--slot=${config.networking.hostName} \
|
||||
--create-slot \
|
||||
--wal-method=stream \
|
||||
--host=${cfg.replication.primary.hostname} \
|
||||
--port=${builtins.toString cfg.replication.primary.port} \
|
||||
--username=${cfg.replication.username} \
|
||||
--no-password
|
||||
fi
|
||||
|
||||
ln -sfn "${configFile}/postgresql.conf" "${dataDir}/postgresql.conf"
|
||||
ln -sfn "${hbaFile}/pg_hba.conf" "${dataDir}/pg_hba.conf"
|
||||
ln -sfn "${identFile}/pg_ident.conf" "${dataDir}/pg_ident.conf"
|
||||
'';
|
||||
|
||||
unitConfig = {
|
||||
RequiresMountsFor = "${dataDir}";
|
||||
};
|
||||
serviceConfig = {
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
ExecStart = "${postgresql}/bin/postgres";
|
||||
Group = "postgres";
|
||||
KillMode = "mixed";
|
||||
KillSignal = "SIGINT";
|
||||
RuntimeDirectory = "postgresql";
|
||||
StateDirectory = "postgresql";
|
||||
StateDirectoryMode = "0750";
|
||||
TimeoutSec = 120;
|
||||
Type = "notify";
|
||||
User = "postgres";
|
||||
LoadCredential = [
|
||||
"fullchain.pem:${config.security.acme.certs."${config.networking.hostName}.${config.networking.domain}".directory}/fullchain.pem"
|
||||
"key.pem:${config.security.acme.certs."${config.networking.hostName}.${config.networking.domain}".directory}/key.pem"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.postgresql-setup = lib.mkIf (!cfg.replication.enable || cfg.replication.role == "primary") (
|
||||
let
|
||||
replicationSetup =
|
||||
if (cfg.replication.enable && cfg.replication.role == "primary") then
|
||||
''
|
||||
if $PSQL --command "SELECT 1 FROM pg_roles WHERE rolname='${cfg.replication.username}';" | grep -q 1
|
||||
then
|
||||
echo "alter replication user ${cfg.replication.username}"
|
||||
$PSQL \
|
||||
--variable=v1="''$(<''${CREDENTIALS_DIRECTORY}/postgresql-replication-password)" \
|
||||
--command "ALTER ROLE ${cfg.replication.username} WITH REPLICATION LOGIN PASSWORD :'v1';"
|
||||
else
|
||||
echo "create replication user ${cfg.replication.username}"
|
||||
$PSQL \
|
||||
--variable=v1="''$(<''${CREDENTIALS_DIRECTORY}/postgresql-replication-password)" \
|
||||
--command "CREATE ROLE ${cfg.replication.username} WITH REPLICATION LOGIN PASSWORD :'v1';"
|
||||
fi
|
||||
''
|
||||
else "";
|
||||
userSetup = lib.strings.concatStringsSep "\n"
|
||||
(
|
||||
map
|
||||
(
|
||||
user:
|
||||
''
|
||||
if $PSQL --command "SELECT 1 FROM pg_roles WHERE rolname='${user.username}';" | grep -q 1
|
||||
then
|
||||
echo "alter user ${user.username}"
|
||||
$PSQL --command "ALTER ROLE ${user.username} WITH LOGIN PASSWORD '${escapeShell user.password}';"
|
||||
else
|
||||
echo "create user ${user.username}"
|
||||
$PSQL --command "CREATE ROLE ${user.username} WITH LOGIN PASSWORD '${escapeShell user.password}';"
|
||||
fi
|
||||
''
|
||||
)
|
||||
cfg.users
|
||||
);
|
||||
databaseSetup = lib.strings.concatStringsSep "\n"
|
||||
(
|
||||
map
|
||||
(
|
||||
database:
|
||||
''
|
||||
if ! ( $PSQL --command "SELECT 1 FROM pg_database WHERE datname='${database.name}';" | grep -q 1 )
|
||||
then
|
||||
echo "create database ${database.name}"
|
||||
$PSQL --command "CREATE DATABASE ${database.name} WITH OWNER ${database.owner};"
|
||||
fi
|
||||
|
||||
echo "grant public schema priviliges to user ${database.owner}"
|
||||
$PSQL --dbname ${database.name} --command "GRANT ALL PRIVILEGES ON SCHEMA public to ${database.owner};"
|
||||
echo "grant priviliges on database ${database.name} to user ${database.owner}"
|
||||
$PSQL --dbname ${database.name} --command "GRANT ALL PRIVILEGES ON DATABASE ${database.name} to ${database.owner};"
|
||||
''
|
||||
)
|
||||
cfg.databases
|
||||
);
|
||||
in
|
||||
{
|
||||
description = "PostgreSQL User/Database Setup";
|
||||
requiredBy = [ "postgresql.service" ];
|
||||
bindsTo = [ "postgresql.service" ];
|
||||
script = ''
|
||||
while ! ${postgresql}/bin/psql -d postgres -c "" 2> /dev/null
|
||||
do
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
PSQL="${postgresql}/bin/psql --tuples-only --no-align"
|
||||
|
||||
$PSQL --command "ALTER ROLE ${cfg.superuser.username} WITH SUPERUSER LOGIN PASSWORD '${escapeShell cfg.superuser.password}';"
|
||||
|
||||
${replicationSetup}
|
||||
${userSetup}
|
||||
${databaseSetup}
|
||||
'';
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "postgres";
|
||||
Group = "postgres";
|
||||
RemainAfterExit = true;
|
||||
TimeoutSec = 120;
|
||||
LoadCredential = [
|
||||
"postgresql-replication-password:${cfg.replication.passwordFile}"
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
systemd.services.postgresql-backup-full = lib.mkIf (cfg.backup.enable && (!cfg.replication.enable || cfg.replication.role == "primary")) {
|
||||
description = "PostgreSQL Full Backup";
|
||||
requires = [ "postgresql.service" ];
|
||||
script = ''
|
||||
while ! ${postgresql}/bin/psql -d postgres -c "" 2> /dev/null
|
||||
do
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
${pkgs.pgbackrest}/bin/pgbackrest --type=full --start-fast --stop-auto --delta backup
|
||||
'';
|
||||
environment = pgbackrestEnvironment;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "postgres";
|
||||
Group = "postgres";
|
||||
TimeoutSec = 3600;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.postgresql-backup-full = lib.mkIf (cfg.backup.enable && (!cfg.replication.enable || cfg.replication.role == "primary")) {
|
||||
description = "PostgreSQL Full Backup";
|
||||
timerConfig = {
|
||||
OnCalendar = "Sun *-*-* 02:00:00";
|
||||
RandomizedDelaySec = "5m";
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
systemd.services.postgresql-backup-diff = lib.mkIf (cfg.backup.enable && (!cfg.replication.enable || cfg.replication.role == "primary")) {
|
||||
description = "PostgreSQL Differential Backup";
|
||||
requires = [ "postgresql.service" ];
|
||||
script = ''
|
||||
while ! ${postgresql}/bin/psql -d postgres -c "" 2> /dev/null
|
||||
do
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
${pkgs.pgbackrest}/bin/pgbackrest --type=diff --start-fast --stop-auto --delta backup
|
||||
'';
|
||||
environment = pgbackrestEnvironment;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "postgres";
|
||||
Group = "postgres";
|
||||
TimeoutSec = 3600;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.postgresql-backup-diff = lib.mkIf (cfg.backup.enable && (!cfg.replication.enable || cfg.replication.role == "primary")) {
|
||||
description = "PostgreSQL Differential Backup";
|
||||
timerConfig = {
|
||||
OnCalendar = "Mon,Tue,Wed,Thu,Fri,Sat *-*-* 02:00:00";
|
||||
RandomizedDelaySec = "5m";
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
systemd.services.postgresql-backup-incr = lib.mkIf (cfg.backup.enable && (!cfg.replication.enable || cfg.replication.role == "primary")) {
|
||||
description = "PostgreSQL Incremental Backup";
|
||||
requires = [ "postgresql.service" ];
|
||||
script = ''
|
||||
while ! ${postgresql}/bin/psql -d postgres -c "" 2> /dev/null
|
||||
do
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
${pkgs.pgbackrest}/bin/pgbackrest --type=incr --start-fast --stop-auto --delta backup
|
||||
'';
|
||||
environment = pgbackrestEnvironment;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
User = "postgres";
|
||||
Group = "postgres";
|
||||
TimeoutSec = 3600;
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.postgresql-backup-incr = lib.mkIf (cfg.backup.enable && (!cfg.replication.enable || cfg.replication.role == "primary")) {
|
||||
description = "PostgreSQL Incremental Backup";
|
||||
timerConfig = {
|
||||
OnCalendar = "*-*-* 06,10,14,18,22:00:00";
|
||||
RandomizedDelaySec = "5m";
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue