summary refs log tree commit diff
path: root/nixos/tests/borgbackup.nix
blob: d3cd6c66bfebedd0e494fa1b6b3ddd9c3c88243f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
import ./make-test-python.nix ({ pkgs, ... }:

let
  passphrase = "supersecret";
  dataDir = "/ran:dom/data";
  excludeFile = "not_this_file";
  keepFile = "important_file";
  keepFileData = "important_data";
  localRepo = "/root/back:up";
  archiveName = "my_archive";
  remoteRepo = "borg@server:."; # No need to specify path
  privateKey = pkgs.writeText "id_ed25519" ''
    -----BEGIN OPENSSH PRIVATE KEY-----
    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
    QyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrwAAAJB+cF5HfnBe
    RwAAAAtzc2gtZWQyNTUxOQAAACBx8UB04Q6Q/fwDFjakHq904PYFzG9pU2TJ9KXpaPMcrw
    AAAEBN75NsJZSpt63faCuaD75Unko0JjlSDxMhYHAPJk2/xXHxQHThDpD9/AMWNqQer3Tg
    9gXMb2lTZMn0pelo8xyvAAAADXJzY2h1ZXR6QGt1cnQ=
    -----END OPENSSH PRIVATE KEY-----
  '';
  publicKey = ''
    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHHxQHThDpD9/AMWNqQer3Tg9gXMb2lTZMn0pelo8xyv root@client
  '';
  privateKeyAppendOnly = pkgs.writeText "id_ed25519" ''
    -----BEGIN OPENSSH PRIVATE KEY-----
    b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
    QyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLwAAAJC9YTxxvWE8
    cQAAAAtzc2gtZWQyNTUxOQAAACBacZuz1ELGQdhI7PF6dGFafCDlvh8pSEc4cHjkW0QjLw
    AAAEAAhV7wTl5dL/lz+PF/d4PnZXuG1Id6L/mFEiGT1tZsuFpxm7PUQsZB2Ejs8Xp0YVp8
    IOW+HylIRzhweORbRCMvAAAADXJzY2h1ZXR6QGt1cnQ=
    -----END OPENSSH PRIVATE KEY-----
  '';
  publicKeyAppendOnly = ''
    ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFpxm7PUQsZB2Ejs8Xp0YVp8IOW+HylIRzhweORbRCMv root@client
  '';

in {
  name = "borgbackup";
  meta = with pkgs.lib; {
    maintainers = with maintainers; [ dotlambda ];
  };

  nodes = {
    client = { ... }: {
      services.borgbackup.jobs = {

        local = {
          paths = dataDir;
          repo = localRepo;
          preHook = ''
            # Don't append a timestamp
            archiveName="${archiveName}"
          '';
          encryption = {
            mode = "repokey";
            inherit passphrase;
          };
          compression = "auto,zlib,9";
          prune.keep = {
            within = "1y";
            yearly = 5;
          };
          exclude = [ "*/${excludeFile}" ];
          postHook = "echo post";
          startAt = [ ]; # Do not run automatically
        };

        remote = {
          paths = dataDir;
          repo = remoteRepo;
          encryption.mode = "none";
          startAt = [ ];
          environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
        };

        remoteAppendOnly = {
          paths = dataDir;
          repo = remoteRepo;
          encryption.mode = "none";
          startAt = [ ];
          environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly";
        };

        commandSuccess = {
          dumpCommand = pkgs.writeScript "commandSuccess" ''
            echo -n test
          '';
          repo = remoteRepo;
          encryption.mode = "none";
          startAt = [ ];
          environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
        };

        commandFail = {
          dumpCommand = "${pkgs.coreutils}/bin/false";
          repo = remoteRepo;
          encryption.mode = "none";
          startAt = [ ];
          environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519";
        };

      };
    };

    server = { ... }: {
      services.openssh = {
        enable = true;
        passwordAuthentication = false;
        kbdInteractiveAuthentication = false;
      };

      services.borgbackup.repos.repo1 = {
        authorizedKeys = [ publicKey ];
        path = "/data/borgbackup";
      };

      # Second repo to make sure the authorizedKeys options are merged correctly
      services.borgbackup.repos.repo2 = {
        authorizedKeysAppendOnly = [ publicKeyAppendOnly ];
        path = "/data/borgbackup";
        quota = ".5G";
      };
    };
  };

  testScript = ''
    start_all()

    client.fail('test -d "${remoteRepo}"')

    client.succeed(
        "cp ${privateKey} /root/id_ed25519"
    )
    client.succeed("chmod 0600 /root/id_ed25519")
    client.succeed(
        "cp ${privateKeyAppendOnly} /root/id_ed25519.appendOnly"
    )
    client.succeed("chmod 0600 /root/id_ed25519.appendOnly")

    client.succeed("mkdir -p ${dataDir}")
    client.succeed("touch ${dataDir}/${excludeFile}")
    client.succeed("echo '${keepFileData}' > ${dataDir}/${keepFile}")

    with subtest("local"):
        borg = "BORG_PASSPHRASE='${passphrase}' borg"
        client.systemctl("start --wait borgbackup-job-local")
        client.fail("systemctl is-failed borgbackup-job-local")
        # Make sure exactly one archive has been created
        assert int(client.succeed("{} list '${localRepo}' | wc -l".format(borg))) > 0
        # Make sure excludeFile has been excluded
        client.fail(
            "{} list '${localRepo}::${archiveName}' | grep -qF '${excludeFile}'".format(borg)
        )
        # Make sure keepFile has the correct content
        client.succeed("{} extract '${localRepo}::${archiveName}'".format(borg))
        assert "${keepFileData}" in client.succeed("cat ${dataDir}/${keepFile}")
        # Make sure the same is true when using `borg mount`
        client.succeed(
            "mkdir -p /mnt/borg && {} mount '${localRepo}::${archiveName}' /mnt/borg".format(
                borg
            )
        )
        assert "${keepFileData}" in client.succeed(
            "cat /mnt/borg/${dataDir}/${keepFile}"
        )

    with subtest("remote"):
        borg = "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519' borg"
        server.wait_for_unit("sshd.service")
        client.wait_for_unit("network.target")
        client.systemctl("start --wait borgbackup-job-remote")
        client.fail("systemctl is-failed borgbackup-job-remote")

        # Make sure we can't access repos other than the specified one
        client.fail("{} list borg\@server:wrong".format(borg))

        # TODO: Make sure that data is actually deleted

    with subtest("remoteAppendOnly"):
        borg = (
            "BORG_RSH='ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly' borg"
        )
        server.wait_for_unit("sshd.service")
        client.wait_for_unit("network.target")
        client.systemctl("start --wait borgbackup-job-remoteAppendOnly")
        client.fail("systemctl is-failed borgbackup-job-remoteAppendOnly")

        # Make sure we can't access repos other than the specified one
        client.fail("{} list borg\@server:wrong".format(borg))

        # TODO: Make sure that data is not actually deleted

    with subtest("commandSuccess"):
        server.wait_for_unit("sshd.service")
        client.wait_for_unit("network.target")
        client.systemctl("start --wait borgbackup-job-commandSuccess")
        client.fail("systemctl is-failed borgbackup-job-commandSuccess")
        id = client.succeed("borg-job-commandSuccess list | tail -n1 | cut -d' ' -f1").strip()
        client.succeed(f"borg-job-commandSuccess extract ::{id} stdin")
        assert "test" == client.succeed("cat stdin")

    with subtest("commandFail"):
        server.wait_for_unit("sshd.service")
        client.wait_for_unit("network.target")
        client.systemctl("start --wait borgbackup-job-commandFail")
        client.succeed("systemctl is-failed borgbackup-job-commandFail")
  '';
})