From ba86ae504d8ea9796e43c1b061aa070761cd1323 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Mon, 21 Nov 2022 18:58:56 +0100 Subject: pgbackrest --- modules/pgbackrest.nix | 192 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 modules/pgbackrest.nix (limited to 'modules') diff --git a/modules/pgbackrest.nix b/modules/pgbackrest.nix new file mode 100644 index 00000000..41a7b381 --- /dev/null +++ b/modules/pgbackrest.nix @@ -0,0 +1,192 @@ +{ config, pkgs, lib, utils, ... }: + +with utils; +with lib; + +let + cfg = config.services.pgbackrest; + settingsFormat = pkgs.formats.ini { + listsAsDuplicateKeys = true; + mkKeyValue = lib.generators.mkKeyValueDefault { + mkValueString = v: with builtins; + let err = t: v: abort + ("mkValueString: " + + "${t} not supported: ${toPretty {} v}"); + in if isInt v then toString v + # convert derivations to store paths + else if lib.isDerivation v then toString v + # we default to not quoting strings + else if isString v then v + # isString returns "1", which is not a good default + else if true == v then "y" + # here it returns to "", which is even less of a good default + else if false == v then "n" + else if null == v then "null" + # if you have lists you probably want to replace this + else if isList v then err "lists" v + # same as for lists, might want to replace + else if isAttrs v then err "attrsets" v + # functions can’t be printed of course + else if isFunction v then err "functions" v + # Floats currently can't be converted to precise strings, + # condition warning on nix version once this isn't a problem anymore + # See https://github.com/NixOS/nix/pull/3480 + else if isFloat v then libStr.floatToString v + else err "this value is" (toString v); + } "="; + }; + + loglevelType = types.enum ["off" "error" "warn" "info" "detail" "debug" "trace"]; + inherit (utils.systemdUtils.unitOptions) unitOption; +in { + options = { + services.pgbackrest = { + enable = mkEnableOption "pgBackRest"; + + package = mkPackageOption pkgs "pgbackrest" {}; + + configurePostgresql = { + enable = mkEnableOption "configuring PostgreSQL for sending WAL to pgBackRest" // { + default = config.services.postgresql.enable; + defaultText = literalExpression "config.systemd.services.postgresql.enable"; + }; + + stanza = mkOption { + type = types.str; + default = config.networking.hostName; + }; + }; + + settings = mkOption { + type = types.submodule { + freeformType = settingsFormat.type; + + options = { + global.log-level-console = mkOption { + type = loglevelType; + default = "detail"; + }; + global.log-level-file = mkOption { + type = loglevelType; + default = "off"; + }; + global.log-level-stderr = mkOption { + type = loglevelType; + default = "warn"; + }; + + global.log-subprocess = mkOption { + type = types.bool; + default = true; + }; + global.log-timestamp = mkOption { + type = types.bool; + default = false; + }; + }; + }; + default = {}; + description = '' + Configuration for pgBackRest + ''; + }; + + tlsServer = { + enable = mkEnableOption "pgBackRest TLS Server"; + + user = mkOption { + type = types.str; + default = "postgres"; + }; + group = mkOption { + type = types.str; + default = "postgres"; + }; + }; + + backups = mkOption { + type = types.attrsOf (types.submodule ({ name, ... }: { + options = { + type = mkOption { + type = types.enum ["full" "incr" "diff"]; + default = "full"; + }; + + stanza = mkOption { + type = types.str; + default = cfg.configurePostgresql.stanza; + }; + repo = mkOption { + type = types.nullOr (types.strMatching "^[0-9]+$"); + }; + + user = mkOption { + type = types.str; + default = "postgres"; + }; + group = mkOption { + type = types.str; + default = "postgres"; + }; + + timerConfig = mkOption { + type = types.attrsOf unitOption; + }; + }; + })); + default = {}; + }; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + + services.postgresql.settings = mkIf cfg.configurePostgresql.enable { + archive_command = "pgbackrest --stanza ${escapeSystemdExecArg cfg.configurePostgresql.stanza} archive-push %p"; + archive_mode = true; + max_wal_senders = mkDefault 3; + wal_level = "replica"; + }; + + systemd.services = { + postgresql.path = mkIf cfg.configurePostgresql.enable [ cfg.package ]; + pgbackrest-tls-server = mkIf cfg.tlsServer.enable { + description = "pgBackRest TLS-Server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + restartTriggers = [ config.environment.etc."pgbackrest/pgbackrest.conf".source ]; + + unitConfig = { + StartLimitIntervalSec = 0; + }; + + serviceConfig = { + Type = "simple"; + Restart = "always"; + RestartSec = 1; + User = cfg.tlsServer.user; + Group = cfg.tlsServer.group; + ExecStart = "${cfg.package}/bin/pgbackrest server"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + }; + }; + } // mapAttrs' (name: backupCfg: nameValuePair "pgbackrest-backup@${escapeSystemdPath name}" { + description = "Perform pgBackRest Backup (${name}${optionalString (!(isNull backupCfg.repo)) " repo${backupCfg.repo}"})"; + serviceConfig = { + Type = "oneshot"; + ExecStart = "${cfg.package}/bin/pgbackrest --type ${escapeSystemdExecArg backupCfg.type} --stanza ${escapeSystemdExecArg backupCfg.stanza}${optionalString (!(isNull backupCfg.repo)) " --repo ${backupCfg.repo}"} backup"; + User = backupCfg.user; + Group = backupCfg.group; + }; + }) cfg.backups; + + systemd.timers = mapAttrs' (name: backupCfg: nameValuePair "pgbackrest-backup@${escapeSystemdPath name}" { + wantedBy = [ "timers.target" ]; + inherit (backupCfg) timerConfig; + }) cfg.backups; + + environment.etc."pgbackrest/pgbackrest.conf".source = settingsFormat.generate "pgbackrest.conf" cfg.settings; + }; +} -- cgit v1.2.3