Skip to content

Commit 88d181d

Browse files
committed
feat: add azure postgres backups
still need to change the placeholder secrets
1 parent dc9a52c commit 88d181d

8 files changed

Lines changed: 238 additions & 5 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
config,
3+
lib,
4+
...
5+
}:
6+
let
7+
cfg = config.services.tik-backup;
8+
in
9+
{
10+
imports = [
11+
./postgresql.nix
12+
];
13+
14+
options.services.tik-backup = {
15+
azure.enable = (lib.mkEnableOption "backing up of Azure data") // {
16+
default = cfg.enable;
17+
defaultText = lib.literalExpression ''
18+
config.services.tik-backup.enable
19+
'';
20+
};
21+
};
22+
23+
config.assertions = [
24+
{
25+
assertion = cfg.azure.enable -> cfg.enable;
26+
message = ''
27+
`services.tik-backup.azure.enable` cannot be enabled without `services.tik-backup.enable`
28+
'';
29+
}
30+
];
31+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
{
2+
config,
3+
pkgs,
4+
lib,
5+
...
6+
}:
7+
let
8+
cfg = config.services.tik-backup;
9+
subdir = "azure-psql";
10+
user = "azure-psql";
11+
12+
stagingScript = pkgs.writeShellApplication {
13+
name = "stage-azure-psql-backup";
14+
runtimeInputs = with pkgs; [
15+
postgresql
16+
];
17+
text = builtins.readFile ./stage-postgresql.sh;
18+
};
19+
in
20+
{
21+
options.services.tik-backup.azure = {
22+
postgresql = {
23+
enable = (lib.mkEnableOption "backing up postgresql databases") // {
24+
default = cfg.azure.enable;
25+
defaultText = lib.literalExpression ''
26+
config.services.tik-backup.azure.enable
27+
'';
28+
};
29+
};
30+
};
31+
32+
config = lib.mkIf cfg.azure.postgresql.enable {
33+
assertions = [
34+
{
35+
assertion = cfg.azure.postgresql.enable -> cfg.azure.enable;
36+
message = ''
37+
`services.tik-backup.azure.postgresql.enable` cannot be enabled without `services.tik-backup.azure.enable`
38+
'';
39+
}
40+
];
41+
42+
sops = {
43+
secrets =
44+
lib.genAttrs
45+
[
46+
"azure/pguser"
47+
"azure/pghost"
48+
"azure/pgpass"
49+
]
50+
(name: {
51+
sopsFile = ../../secrets/backup.yaml;
52+
});
53+
templates.azure-psql-envfile = {
54+
owner = user;
55+
content = ''
56+
PGUSER=${config.sops.placeholder."azure/pguser"}
57+
PGHOST=${config.sops.placeholder."azure/pghost"}
58+
PGPASSWORD=${config.sops.placeholder."azure/pgpass"}
59+
'';
60+
};
61+
};
62+
63+
services.tik-backup = {
64+
stagingServices = [ "stage-azure-psql.service" ];
65+
stagingSubdirs = [
66+
{
67+
inherit subdir user;
68+
}
69+
];
70+
};
71+
72+
systemd.services.stage-azure-psql = {
73+
description = "Backup Azure postgresql databases";
74+
wants = [ "network-online.target" ];
75+
after = [ "network-online.target" ];
76+
restartIfChanged = false;
77+
serviceConfig = {
78+
Type = "oneshot";
79+
EnvironmentFile = config.sops.templates.azure-psql-envfile.path;
80+
ExecStart = ''${lib.getExe stagingScript} "${cfg.stagingDir}/${subdir}/"'';
81+
User = user;
82+
Group = user;
83+
};
84+
};
85+
86+
users = {
87+
users.${user} = {
88+
isSystemUser = true;
89+
group = user;
90+
};
91+
groups.${user} = { };
92+
};
93+
};
94+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# $1 = target directory to stage to
2+
set -euo pipefail
3+
4+
die() {
5+
echo "${1:?}" >&2
6+
exit "${2:-1}"
7+
}
8+
9+
targetdir="$1"
10+
if [[ -z "$targetdir" || ! -d "$targetdir" || ! -w "$targetdir" ]]; then
11+
die "fatal: target directory should be passed as the first argument"
12+
fi
13+
14+
15+
: "${PGHOST:?}"
16+
: "${PGUSER:?}"
17+
: "${PGPASSWORD:?}"
18+
19+
db_list=$(
20+
psql -d postgres --no-align --tuples-only -c \
21+
"SELECT datname FROM pg_database
22+
WHERE datistemplate = false
23+
AND datname NOT IN ('postgres', 'azure_maintenance', 'azure_sys');"
24+
) || {
25+
die "fatal: failed to list databases with psql"
26+
}
27+
28+
if [[ -z "$db_list" ]]; then
29+
die "fatal: no databases found"
30+
fi
31+
32+
# read newline separated string into array indices
33+
mapfile -t databases_arr <<< "$db_list"
34+
35+
echo "Found postgresql databases:"
36+
printf "%s\n" "${databases_arr[@]}"
37+
38+
all_succeeded=true
39+
[[ -d "$targetdir" ]] || die "fatal: target directory '$targetdir' does not exist"
40+
41+
for db in "${databases_arr[@]}"; do
42+
[[ -n "$db" ]] || continue
43+
44+
outTarget="$targetdir/$db.dump"
45+
echo "Staging $db to $outTarget"
46+
if ! pg_dump -d "$db" --format=directory --compress=none -f "$outTarget"; then
47+
echo "ERROR: failed to stage $db" >&2
48+
all_succeeded=false
49+
else
50+
echo "Staged $db"
51+
fi
52+
done
53+
54+
# Allow backup group to read and delete all
55+
chmod -R g+rwX "$targetdir"/*
56+
57+
if [[ "$all_succeeded" != true ]]; then
58+
die "Not all databases were staged, failing..."
59+
fi
60+
61+
echo "Done"

tikpannu-nixos-config/modules/backup/configuration.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
services.tik-backup = {
33
enable = true;
4+
azure.enable = true;
45
storageboxServer = "u563055.your-storagebox.de";
56
storageboxMountPath = "/mnt/backup";
67
stagingDir = "/var/lib/backup";

tikpannu-nixos-config/modules/backup/default.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ let
44
in
55
{
66
imports = [
7+
./azure
78
./configuration.nix
89
./storagebox.nix
910
./secrets.nix

tikpannu-nixos-config/modules/secrets/backup.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
storagebox-credentials: ENC[AES256_GCM,data:1O1uDtmpty4Dc/rf65y8RalGLeBjZTK3JDG6bwSiyBSeJXuNH9Cf0ABF343A9j1/djNk1bS1eWWXrA8=,iv:9R4KChsmqPdzE4eEwbLlsmp+N91ez/2thv5qQUWQjdU=,tag:G903ZbTtY4fzPXP8SbC+eg==,type:str]
22
restic-password: ENC[AES256_GCM,data:lEP6UAPyESSrt5sXsxMomj2BKbmGoFMYBBKzaCImFvgZT5YKdWjEYFFoqZt+X8Aldd1JCCA5fNy3Jt+1LjNzxw==,iv:EjlNSLSH4wAs0Pxan2EywjIS9Ef+/JJ1T16mQvIPbHQ=,tag:VEwmngYfgRB9qaBUA6OB7g==,type:str]
33
gatus-token: ENC[AES256_GCM,data:j+ZoY6ekV2R/WfdqEklsuK48ieXhCTWTnDsKBolxSpI=,iv:X1TQuPOpezUurS0XoaY4XXbIhzv2dRcFGvxCUS4I/fg=,tag:m1x98Z6JFkFCQny8K3Sugg==,type:str]
4+
azure:
5+
pguser: ENC[AES256_GCM,data:LwgJWacmgaNlv78=,iv:+iBqlUAONePoTtXwGKogN9Q1mgT6Mg5PFeGOxMCcJ64=,tag:yGBYh/weKtdHp/emDB4E9w==,type:str]
6+
pghost: ENC[AES256_GCM,data:2+s5U+mv3MJj4Rw=,iv:sT0ivRVGY7DtNSAHcpHT7BQd9n6fCefA04n71biNWmM=,tag:ex2bn6xInOa0GaJKQK4p4A==,type:str]
7+
pgpass: ENC[AES256_GCM,data:L+Uuu5eGaxn3jN0=,iv:d2Zm7jFcd/GZvUfcHFGmmZuz9QjAniFCDogIzilm5P4=,tag:VQaySczOt1XiiWy7Y050xw==,type:str]
48
sops:
59
age:
610
- recipient: age1lp4pv3x95n2z4dh024f0ev05gnwrwf7n67z0c75cxa2vtus704msxgv700
@@ -31,7 +35,7 @@ sops:
3135
ErFcJu5H3iDzyafp5QPEwiT1WU070LvVofxkcmIbyDI5ARAv7jiut7O9B1LjZ2Un
3236
dJU=
3337
-----END AGE ENCRYPTED FILE-----
34-
lastmodified: "2026-03-26T13:17:52Z"
35-
mac: ENC[AES256_GCM,data:2clPaNZBmLpEXUx0sJz9OAstZndGYuXNN2h3hd9adsjAthj6LeHjrBMNS2Syx4NQAH7dX1kdplY8ZJNJALCasf6HKEkD9HcUGNawZwNE+fK4vMNv5MW47B7vXEVcDYnRSPI9BjgUzY15//9tMit7nw89FAuuxmgOE6H6b1DdaYU=,iv:Xkbz8gEtC9ivJABtwMG4+rfPJ2tR5dDoStrMo+6PpRg=,tag:CJ7MaVfEDZ9Hlx0667e9mQ==,type:str]
38+
lastmodified: "2026-03-31T17:00:20Z"
39+
mac: ENC[AES256_GCM,data:mRovsL4LM6dBVncpEi86/2qjtzQOQGEy/VLi1fFL/ZvsgzIoIFxmwfBMgUuIEFbqEfl7UWOwaj9HgBob/njBujdqclBpOo4gekY/7qwNDWS4yLRQzqj9j0MBW/P6WOXTlpl1PpzVqBMEq1t0yLC/YPlX9L+IFjRWVlIXoMEpFC0=,iv:eKmTaU6RljaCP5Wgrfd7zP5ub1AoWSezAxhGhGovay8=,tag:Hpg1xPp7OjX/wThWhyv9lA==,type:str]
3640
unencrypted_suffix: _unencrypted
3741
version: 3.11.0

tikpannu-nixos-config/tests/backups.nix

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,41 @@
1111
./base-pannu-config.nix
1212
];
1313

14-
services.tik-backup.enable = lib.mkForce true;
14+
services.tik-backup = {
15+
enable = lib.mkForce true;
16+
azure.enable = lib.mkForce true;
17+
azure.postgresql.enable = lib.mkForce true;
18+
};
1519

20+
# Enable a local PostgreSQL server and create mock DBs for the test VM.
21+
services.postgresql = {
22+
enable = lib.mkForce true;
23+
ensureDatabases = [
24+
"mock1"
25+
"mock2"
26+
"mock3"
27+
];
28+
ensureUsers = [
29+
{
30+
name = "azure-psql-backup";
31+
ensureClauses = {
32+
password = "verygoodpass";
33+
};
34+
}
35+
];
36+
};
37+
38+
systemd.services.stage-azure-psql = {
39+
requires = [ "postgresql.target" ];
40+
after = [ "postgresql.target" ];
41+
serviceConfig.EnvironmentFile = lib.mkForce (
42+
pkgs.writeText "envfile" ''
43+
PGHOST=127.0.0.1
44+
PGUSER=azure-psql-backup
45+
PGPASSWORD=verygoodpass
46+
''
47+
);
48+
};
1649
# Enable discourse for the backup logic, not to run it
1750
services.discourse.enable = lib.mkForce true;
1851
systemd.services.discourse.wantedBy = lib.mkForce [ ];
@@ -51,14 +84,19 @@
5184
5285
pannu.succeed("systemctl start restic-backups-tik-backup.service")
5386
unit_succeeded("discourse-stage-backup.service")
87+
unit_succeeded("stage-azure-psql.service")
88+
89+
90+
# Backup user must be able to clean up this dir
91+
pannu.succeed("sudo -u backup sh -c 'rm -rf /var/lib/backup/*'")
5492
5593
_, stdout = pannu.execute("restic-tik-backup ls latest")
5694
print("Backed up files:")
5795
print(stdout)
5896
5997
# At least min_files files must exist in the backup. This does not include
6098
# directories.
61-
min_files = 1
99+
min_files = 4
62100
stdout = pannu.succeed(f''''
63101
restic-tik-backup ls --json latest \\
64102
| jq --exit-status --slurp \\

tikpannu-nixos-config/tests/base-pannu-config.nix

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ in
4848
};
4949
};
5050

51-
services.tik-backup.enable = mkWeakForce false;
51+
services.tik-backup = {
52+
enable = mkWeakForce false;
53+
azure.enable = mkWeakForce false;
54+
};
5255
services.restic.backups.tik-backup = lib.mkIf config.services.tik-backup.enable {
5356
passwordFile = mkWeakForce resticPassFile.outPath;
5457
};

0 commit comments

Comments
 (0)