hydra: 0-unstable-2024-11-25 -> 0-unstable-2024-12-05 (#363987)
[NixPkgs.git] / nixos / tests / borgbackup.nix
blobaf7c12009c3630b9c50f76e91645d26bfda44159
1 import ./make-test-python.nix ({ pkgs, ... }:
3 let
4   passphrase = "supersecret";
5   dataDir = "/ran:dom/data";
6   excludeFile = "not_this_file";
7   keepFile = "important_file";
8   keepFileData = "important_data";
9   localRepo = "/root/back:up";
10   # a repository on a file system which is not mounted automatically
11   localRepoMount = "/noAutoMount";
12   archiveName = "my_archive";
13   remoteRepo = "borg@server:."; # No need to specify path
14   privateKey = pkgs.writeText "id_ed25519" ''
15     -----BEGIN OPENSSH PRIVATE KEY-----
16     b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
17     QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
18     RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
19     AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
20     9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
21     -----END OPENSSH PRIVATE KEY-----
22   '';
23   publicKey = ''
24     ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv root@client
25   '';
26   privateKeyAppendOnly = pkgs.writeText "id_ed25519" ''
27     -----BEGIN OPENSSH PRIVATE KEY-----
28     b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
29     QyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLwAAAJC9YTxxvWE8
30     cQAAAAtzc2gtZWQyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLw
31     AAAEAAhV7wTl5dL/lz+PF/d4PnZXuG1Id6L/mFEiGT1tZsuFpxm7PUQsZB2Ejs8Xp0YVp8
32     IOW+HylIRzhweORbRCMvAAAADXJzY2h1ZXR6QGt1cnQ=
33     -----END OPENSSH PRIVATE KEY-----
34   '';
35   publicKeyAppendOnly = ''
36     ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFpxm7PUQsZB2Ejs8Xp0YVp8IOW+HylIRzhweORbRCMv root@client
37   '';
39 in {
40   name = "borgbackup";
41   meta = with pkgs.lib; {
42     maintainers = with maintainers; [ dotlambda ];
43   };
45   nodes = {
46     client = { ... }: {
47       virtualisation.fileSystems.${localRepoMount} = {
48         device = "tmpfs";
49         fsType = "tmpfs";
50         options = [ "noauto" ];
51       };
53       services.borgbackup.jobs = {
55         local = {
56           paths = dataDir;
57           repo = localRepo;
58           preHook = ''
59             # Don't append a timestamp
60             archiveName="${archiveName}"
61           '';
62           encryption = {
63             mode = "repokey";
64             inherit passphrase;
65           };
66           compression = "auto,zlib,9";
67           prune.keep = {
68             within = "1y";
69             yearly = 5;
70           };
71           exclude = [ "*/${excludeFile}" ];
72           postHook = "echo post";
73           startAt = [ ]; # Do not run automatically
74         };
76         localMount = {
77           paths = dataDir;
78           repo = localRepoMount;
79           encryption.mode = "none";
80           startAt = [ ];
81         };
83         remote = {
84           paths = dataDir;
85           repo = remoteRepo;
86           encryption.mode = "none";
87           startAt = [ ];
88           environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
89         };
91         remoteAppendOnly = {
92           paths = dataDir;
93           repo = remoteRepo;
94           encryption.mode = "none";
95           startAt = [ ];
96           environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly";
97         };
99         commandSuccess = {
100           dumpCommand = pkgs.writeScript "commandSuccess" ''
101             echo -n test
102           '';
103           repo = remoteRepo;
104           encryption.mode = "none";
105           startAt = [ ];
106           environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
107         };
109         commandFail = {
110           dumpCommand = "${pkgs.coreutils}/bin/false";
111           repo = remoteRepo;
112           encryption.mode = "none";
113           startAt = [ ];
114           environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
115         };
117         sleepInhibited = {
118           inhibitsSleep = true;
119           # Blocks indefinitely while "backing up" so that we can try to suspend the local system while it's hung
120           dumpCommand = pkgs.writeScript "sleepInhibited" ''
121             cat /dev/zero
122           '';
123           repo = remoteRepo;
124           encryption.mode = "none";
125           startAt = [ ];
126           environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
127         };
129       };
130     };
132     server = { ... }: {
133       services.openssh = {
134         enable = true;
135         settings = {
136           PasswordAuthentication = false;
137           KbdInteractiveAuthentication = false;
138         };
139       };
141       services.borgbackup.repos.repo1 = {
142         authorizedKeys = [ publicKey ];
143         path = "/data/borgbackup";
144       };
146       # Second repo to make sure the authorizedKeys options are merged correctly
147       services.borgbackup.repos.repo2 = {
148         authorizedKeysAppendOnly = [ publicKeyAppendOnly ];
149         path = "/data/borgbackup";
150         quota = ".5G";
151       };
152     };
153   };
155   testScript = ''
156     start_all()
158     client.fail('test -d "${remoteRepo}"')
160     client.succeed(
161         "cp ${privateKey} /root/id_ed25519"
162     )
163     client.succeed("chmod 0600 /root/id_ed25519")
164     client.succeed(
165         "cp ${privateKeyAppendOnly} /root/id_ed25519.appendOnly"
166     )
167     client.succeed("chmod 0600 /root/id_ed25519.appendOnly")
169     client.succeed("mkdir -p ${dataDir}")
170     client.succeed("touch ${dataDir}/${excludeFile}")
171     client.succeed("echo '${keepFileData}' > ${dataDir}/${keepFile}")
173     with subtest("local"):
174         borg = "BORG_PASSPHRASE='${passphrase}' borg"
175         client.systemctl("start --wait borgbackup-job-local")
176         client.fail("systemctl is-failed borgbackup-job-local")
177         # Make sure exactly one archive has been created
178         assert int(client.succeed("{} list '${localRepo}' | wc -l".format(borg))) > 0
179         # Make sure excludeFile has been excluded
180         client.fail(
181             "{} list '${localRepo}::${archiveName}' | grep -qF '${excludeFile}'".format(borg)
182         )
183         # Make sure keepFile has the correct content
184         client.succeed("{} extract '${localRepo}::${archiveName}'".format(borg))
185         assert "${keepFileData}" in client.succeed("cat ${dataDir}/${keepFile}")
186         # Make sure the same is true when using `borg mount`
187         client.succeed(
188             "mkdir -p /mnt/borg && {} mount '${localRepo}::${archiveName}' /mnt/borg".format(
189                 borg
190             )
191         )
192         assert "${keepFileData}" in client.succeed(
193             "cat /mnt/borg/${dataDir}/${keepFile}"
194         )
196     with subtest("localMount"):
197         # the file system for the repo should not be already mounted
198         client.fail("mount | grep ${localRepoMount}")
199         # ensure trying to write to the mountpoint before the fs is mounted fails
200         client.succeed("chattr +i ${localRepoMount}")
201         borg = "borg"
202         client.systemctl("start --wait borgbackup-job-localMount")
203         client.fail("systemctl is-failed borgbackup-job-localMount")
204         # Make sure exactly one archive has been created
205         assert int(client.succeed("{} list '${localRepoMount}' | wc -l".format(borg))) > 0
207     with subtest("remote"):
208         borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519' borg"
209         server.wait_for_unit("sshd.service")
210         client.wait_for_unit("network.target")
211         client.systemctl("start --wait borgbackup-job-remote")
212         client.fail("systemctl is-failed borgbackup-job-remote")
214         # Make sure we can't access repos other than the specified one
215         client.fail("{} list borg\@server:wrong".format(borg))
217         # TODO: Make sure that data is actually deleted
219     with subtest("remoteAppendOnly"):
220         borg = (
221             "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly' borg"
222         )
223         server.wait_for_unit("sshd.service")
224         client.wait_for_unit("network.target")
225         client.systemctl("start --wait borgbackup-job-remoteAppendOnly")
226         client.fail("systemctl is-failed borgbackup-job-remoteAppendOnly")
228         # Make sure we can't access repos other than the specified one
229         client.fail("{} list borg\@server:wrong".format(borg))
231         # TODO: Make sure that data is not actually deleted
233     with subtest("commandSuccess"):
234         server.wait_for_unit("sshd.service")
235         client.wait_for_unit("network.target")
236         client.systemctl("start --wait borgbackup-job-commandSuccess")
237         client.fail("systemctl is-failed borgbackup-job-commandSuccess")
238         id = client.succeed("borg-job-commandSuccess list | tail -n1 | cut -d' ' -f1").strip()
239         client.succeed(f"borg-job-commandSuccess extract ::{id} stdin")
240         assert "test" == client.succeed("cat stdin")
242     with subtest("commandFail"):
243         server.wait_for_unit("sshd.service")
244         client.wait_for_unit("network.target")
245         client.systemctl("start --wait borgbackup-job-commandFail")
246         client.succeed("systemctl is-failed borgbackup-job-commandFail")
248     with subtest("sleepInhibited"):
249         server.wait_for_unit("sshd.service")
250         client.wait_for_unit("network.target")
251         client.fail("systemd-inhibit --list | grep -q borgbackup")
252         client.systemctl("start borgbackup-job-sleepInhibited")
253         client.wait_until_succeeds("systemd-inhibit --list | grep -q borgbackup")
254         client.systemctl("stop borgbackup-job-sleepInhibited")
255   '';