From 82e519947fe2df5123093884f794e5608133b8cd Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Mon, 5 May 2025 16:27:39 +0200 Subject: [PATCH] nixos/pgbackrest: add SFTP support --- nixos/modules/services/backup/pgbackrest.nix | 21 +++++ nixos/tests/pgbackrest/default.nix | 1 + nixos/tests/pgbackrest/sftp.nix | 95 ++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 nixos/tests/pgbackrest/sftp.nix diff --git a/nixos/modules/services/backup/pgbackrest.nix b/nixos/modules/services/backup/pgbackrest.nix index da3c719c217a..f361b68cf71a 100644 --- a/nixos/modules/services/backup/pgbackrest.nix +++ b/nixos/modules/services/backup/pgbackrest.nix @@ -100,6 +100,7 @@ in if name == "localhost" then null # "posix" is the default repo type, which uses the -host option. + # Other types use prefixed options, for example -sftp-host. else if config.type or "posix" != type then null else @@ -114,6 +115,26 @@ in defaultText = lib.literalExpression "name"; description = "Repository host when operating remotely"; }; + + options.sftp-host = lib.mkOption { + type = nullOr str; + default = setHostForType "sftp"; + defaultText = lib.literalExpression "name"; + description = "SFTP repository host"; + }; + + options.sftp-private-key-file = lib.mkOption { + type = nullOr (pathWith { + inStore = false; + absolute = true; + }); + default = null; + description = '' + SFTP private key file. + + The file must be accessible by both the pgbackrest and the postgres users. + ''; + }; } ) ); diff --git a/nixos/tests/pgbackrest/default.nix b/nixos/tests/pgbackrest/default.nix index 72cfd6eb4b51..5f837e5c351c 100644 --- a/nixos/tests/pgbackrest/default.nix +++ b/nixos/tests/pgbackrest/default.nix @@ -1,4 +1,5 @@ { runTest }: { posix = runTest ./posix.nix; + sftp = runTest ./sftp.nix; } diff --git a/nixos/tests/pgbackrest/sftp.nix b/nixos/tests/pgbackrest/sftp.nix new file mode 100644 index 000000000000..8e97fb679980 --- /dev/null +++ b/nixos/tests/pgbackrest/sftp.nix @@ -0,0 +1,95 @@ +{ lib, pkgs, ... }: +let + inherit (import ../ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey; + backupPath = "/home/backup"; +in +{ + name = "pgbackrest-sftp"; + + meta = { + maintainers = with lib.maintainers; [ wolfgangwalther ]; + }; + + nodes.primary = + { + pkgs, + ... + }: + { + services.postgresql = { + enable = true; + initialScript = pkgs.writeText "init.sql" '' + CREATE TABLE t(c text); + INSERT INTO t VALUES ('hello world'); + ''; + }; + + services.pgbackrest = { + enable = true; + repos.backup = { + type = "sftp"; + path = "/home/backup"; + sftp-host-key-check-type = "none"; + sftp-host-key-hash-type = "sha256"; + sftp-host-user = "backup"; + sftp-private-key-file = "/var/lib/pgbackrest/sftp_key"; + }; + + stanzas.default.jobs.future = { + schedule = "3000-01-01"; + type = "diff"; + }; + }; + }; + + nodes.backup = + { + nodes, + ... + }: + { + services.openssh.enable = true; + users.users.backup = { + name = "backup"; + group = "backup"; + isNormalUser = true; + createHome = true; + openssh.authorizedKeys.keys = [ + snakeOilPublicKey + ]; + }; + users.groups.backup = { }; + }; + + testScript = + { nodes, ... }: + '' + start_all() + + primary.wait_for_unit("multi-user.target") + backup.wait_for_unit("multi-user.target") + + primary.log(primary.succeed(""" + HOME="/var/lib/pgbackrest" + cat ${snakeOilPrivateKey} > ~/sftp_key + chown -R pgbackrest:pgbackrest ~/sftp_key + chmod 770 ~ + """)) + + with subtest("backup/restore works with local instance/remote repo (SFTP)"): + primary.succeed("sudo -u pgbackrest pgbackrest --stanza=default stanza-create", timeout=10) + primary.succeed("sudo -u pgbackrest pgbackrest --stanza=default check") + + primary.systemctl("start pgbackrest-default-future") + + # corrupt cluster + primary.systemctl("stop postgresql") + primary.execute("rm ${nodes.primary.services.postgresql.dataDir}/global/pg_control") + + primary.succeed("sudo -u postgres pgbackrest --stanza=default restore --delta") + + primary.systemctl("start postgresql") + primary.wait_for_unit("postgresql.service") + assert "hello world" in primary.succeed("sudo -u postgres psql -c 'TABLE t;'") + ''; +}