1import ./make-test.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.stdenv.lib; {
40 maintainers = with maintainers; [ dotlambda ];
41 };
42
43 nodes = {
44 client = { ... }: {
45 services.borgbackup.jobs = {
46
47 local = rec {
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 };
85 };
86
87 server = { ... }: {
88 services.openssh = {
89 enable = true;
90 passwordAuthentication = false;
91 challengeResponseAuthentication = false;
92 };
93
94 services.borgbackup.repos.repo1 = {
95 authorizedKeys = [ publicKey ];
96 path = "/data/borgbackup";
97 };
98
99 # Second repo to make sure the authorizedKeys options are merged correctly
100 services.borgbackup.repos.repo2 = {
101 authorizedKeysAppendOnly = [ publicKeyAppendOnly ];
102 path = "/data/borgbackup";
103 quota = ".5G";
104 };
105 };
106 };
107
108 testScript = ''
109 startAll;
110
111 $client->fail('test -d "${remoteRepo}"');
112
113 $client->succeed("cp ${privateKey} /root/id_ed25519");
114 $client->succeed("chmod 0600 /root/id_ed25519");
115 $client->succeed("cp ${privateKeyAppendOnly} /root/id_ed25519.appendOnly");
116 $client->succeed("chmod 0600 /root/id_ed25519.appendOnly");
117
118 $client->succeed("mkdir -p ${dataDir}");
119 $client->succeed("touch ${dataDir}/${excludeFile}");
120 $client->succeed("echo '${keepFileData}' > ${dataDir}/${keepFile}");
121
122 subtest "local", sub {
123 my $borg = "BORG_PASSPHRASE='${passphrase}' borg";
124 $client->systemctl("start --wait borgbackup-job-local");
125 $client->fail("systemctl is-failed borgbackup-job-local");
126 # Make sure exactly one archive has been created
127 $client->succeed("c=\$($borg list '${localRepo}' | wc -l) && [[ \$c == '1' ]]");
128 # Make sure excludeFile has been excluded
129 $client->fail("$borg list '${localRepo}::${archiveName}' | grep -qF '${excludeFile}'");
130 # Make sure keepFile has the correct content
131 $client->succeed("$borg extract '${localRepo}::${archiveName}'");
132 $client->succeed('c=$(cat ${dataDir}/${keepFile}) && [[ "$c" == "${keepFileData}" ]]');
133 };
134
135 subtest "remote", sub {
136 my $borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519' borg";
137 $server->waitForUnit("sshd.service");
138 $client->waitForUnit("network.target");
139 $client->systemctl("start --wait borgbackup-job-remote");
140 $client->fail("systemctl is-failed borgbackup-job-remote");
141
142 # Make sure we can't access repos other than the specified one
143 $client->fail("$borg list borg\@server:wrong");
144
145 #TODO: Make sure that data is actually deleted
146 };
147
148 subtest "remoteAppendOnly", sub {
149 my $borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly' borg";
150 $server->waitForUnit("sshd.service");
151 $client->waitForUnit("network.target");
152 $client->systemctl("start --wait borgbackup-job-remoteAppendOnly");
153 $client->fail("systemctl is-failed borgbackup-job-remoteAppendOnly");
154
155 # Make sure we can't access repos other than the specified one
156 $client->fail("$borg list borg\@server:wrong");
157
158 #TODO: Make sure that data is not actually deleted
159 };
160
161 '';
162})