1{ lib, pkgs, ... }:
2let
3 inherit (import ../ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
4 backupPath = "/var/lib/pgbackrest";
5in
6{
7 name = "pgbackrest-posix";
8
9 meta = {
10 maintainers = with lib.maintainers; [ wolfgangwalther ];
11 };
12
13 nodes.primary =
14 {
15 pkgs,
16 ...
17 }:
18 {
19 services.openssh.enable = true;
20 users.users.postgres.openssh.authorizedKeys.keys = [
21 snakeOilPublicKey
22 ];
23
24 services.postgresql = {
25 enable = true;
26 initialScript = pkgs.writeText "init.sql" ''
27 CREATE TABLE t(c text);
28 INSERT INTO t VALUES ('hello world');
29 '';
30 };
31
32 services.pgbackrest = {
33 enable = true;
34 repos.backup = {
35 type = "posix";
36 path = backupPath;
37 host-user = "pgbackrest";
38 };
39 };
40 };
41
42 nodes.backup =
43 {
44 nodes,
45 ...
46 }:
47 {
48 services.openssh.enable = true;
49 users.users.pgbackrest.openssh.authorizedKeys.keys = [
50 snakeOilPublicKey
51 ];
52
53 services.pgbackrest = {
54 enable = true;
55 repos.localhost.path = backupPath;
56
57 stanzas.default = {
58 jobs.future = {
59 schedule = "3000-01-01";
60 type = "full";
61 };
62 instances.primary = {
63 path = nodes.primary.services.postgresql.dataDir;
64 user = "postgres";
65 };
66 };
67
68 # Examples from https://pgbackrest.org/configuration.html#introduction
69 # Not used for the test, except for dumping the config.
70 stanzas.config-format.settings = {
71 start-fast = true;
72 compress-level = 3;
73 buffer-size = "2MiB";
74 db-timeout = 600;
75 db-exclude = [
76 "db1"
77 "db2"
78 "db5"
79 ];
80 tablespace-map = {
81 ts_01 = "/db/ts_01";
82 ts_02 = "/db/ts_02";
83 };
84 };
85 };
86 };
87
88 testScript =
89 { nodes, ... }:
90 ''
91 start_all()
92
93 primary.wait_for_unit("multi-user.target")
94 backup.wait_for_unit("multi-user.target")
95
96 with subtest("config file is written correctly"):
97 from textwrap import dedent
98 have = backup.succeed("cat /etc/pgbackrest/pgbackrest.conf")
99 want = dedent("""\
100 [config-format]
101 buffer-size=2MiB
102 compress-level=3
103 db-exclude=db1
104 db-exclude=db2
105 db-exclude=db5
106 db-timeout=600
107 start-fast=y
108 tablespace-map=ts_01=/db/ts_01
109 tablespace-map=ts_02=/db/ts_02
110 """)
111 assert want in have, repr((want, have))
112
113 primary.log(primary.succeed("""
114 HOME="${nodes.primary.services.postgresql.dataDir}"
115 mkdir -m 700 -p ~/.ssh
116 cat ${snakeOilPrivateKey} > ~/.ssh/id_ecdsa
117 chmod 400 ~/.ssh/id_ecdsa
118 ssh-keyscan backup >> ~/.ssh/known_hosts
119 chown -R postgres:postgres ~/.ssh
120 """))
121
122 backup.log(backup.succeed("""
123 HOME="${backupPath}"
124 mkdir -m 700 -p ~/.ssh
125 cat ${snakeOilPrivateKey} > ~/.ssh/id_ecdsa
126 chmod 400 ~/.ssh/id_ecdsa
127 ssh-keyscan primary >> ~/.ssh/known_hosts
128 chown -R pgbackrest:pgbackrest ~
129 """))
130
131 with subtest("backup/restore works with remote instance/local repo (SSH)"):
132 backup.succeed("sudo -u pgbackrest pgbackrest --stanza=default stanza-create")
133 backup.succeed("sudo -u pgbackrest pgbackrest --stanza=default check")
134
135 backup.systemctl("start pgbackrest-default-future")
136
137 # corrupt cluster
138 primary.systemctl("stop postgresql")
139 primary.execute("rm ${nodes.primary.services.postgresql.dataDir}/global/pg_control")
140
141 primary.succeed("sudo -u postgres pgbackrest --stanza=default restore --delta")
142
143 primary.systemctl("start postgresql")
144 primary.wait_for_unit("postgresql.service")
145 assert "hello world" in primary.succeed("sudo -u postgres psql -c 'TABLE t;'")
146 '';
147}