1 import ./make-test-python.nix ({ pkgs, ... }:
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-----
24 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv root@client
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-----
35 publicKeyAppendOnly = ''
36 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFpxm7PUQsZB2Ejs8Xp0YVp8IOW+HylIRzhweORbRCMv root@client
41 meta = with pkgs.lib; {
42 maintainers = with maintainers; [ dotlambda ];
47 virtualisation.fileSystems.${localRepoMount} = {
50 options = [ "noauto" ];
53 services.borgbackup.jobs = {
59 # Don't append a timestamp
60 archiveName="${archiveName}"
66 compression = "auto,zlib,9";
71 exclude = [ "*/${excludeFile}" ];
72 postHook = "echo post";
73 startAt = [ ]; # Do not run automatically
78 repo = localRepoMount;
79 encryption.mode = "none";
86 encryption.mode = "none";
88 environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
94 encryption.mode = "none";
96 environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly";
100 dumpCommand = pkgs.writeScript "commandSuccess" ''
104 encryption.mode = "none";
106 environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
110 dumpCommand = "${pkgs.coreutils}/bin/false";
112 encryption.mode = "none";
114 environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
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" ''
124 encryption.mode = "none";
126 environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
136 PasswordAuthentication = false;
137 KbdInteractiveAuthentication = false;
141 services.borgbackup.repos.repo1 = {
142 authorizedKeys = [ publicKey ];
143 path = "/data/borgbackup";
146 # Second repo to make sure the authorizedKeys options are merged correctly
147 services.borgbackup.repos.repo2 = {
148 authorizedKeysAppendOnly = [ publicKeyAppendOnly ];
149 path = "/data/borgbackup";
158 client.fail('test -d "${remoteRepo}"')
161 "cp ${privateKey} /root/id_ed25519"
163 client.succeed("chmod 0600 /root/id_ed25519")
165 "cp ${privateKeyAppendOnly} /root/id_ed25519.appendOnly"
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
181 "{} list '${localRepo}::${archiveName}' | grep -qF '${excludeFile}'".format(borg)
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`
188 "mkdir -p /mnt/borg && {} mount '${localRepo}::${archiveName}' /mnt/borg".format(
192 assert "${keepFileData}" in client.succeed(
193 "cat /mnt/borg/${dataDir}/${keepFile}"
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}")
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"):
221 "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly' borg"
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")