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