Merge pull request 'better backups' (#343) from better-backups into main

Reviewed-on: #343
Reviewed-by: teutat3s <teutat3s@noreply.git.pub.solar>
This commit is contained in:
teutat3s 2025-04-17 21:21:38 +00:00
commit efe18beefb
Signed by: pub.solar gitea
GPG key ID: F0332B04B7054873
5 changed files with 179 additions and 86 deletions

View file

@ -71,6 +71,35 @@ in
};
};
resources = mkOption {
description = "resources required to exist before starting restic backup archive process";
default = { };
type = types.attrsOf (
types.submodule (
{ ... }:
{
options = {
resourceCreateCommand = mkOption {
type = with types; nullOr str;
default = null;
description = ''
A script that must run successfully to create the resource. Optional.
'';
};
resourceDestroyCommand = mkOption {
type = with types; nullOr str;
default = null;
description = ''
A script that runs when the resource is destroyed. Optional.
'';
};
};
}
)
);
};
restic = mkOption {
description = ''
Periodic backups to create with Restic.
@ -80,6 +109,11 @@ in
{ name, ... }:
{
options = {
resources = mkOption {
type = types.listOf types.str;
default = [ ];
};
paths = mkOption {
# This is nullable for legacy reasons only. We should consider making it a pure listOf
# after some time has passed since this comment was added.
@ -262,31 +296,88 @@ in
};
};
config = {
services.restic.backups =
let
repos = config.pub-solar-os.backups.repos;
restic = config.pub-solar-os.backups.restic;
config =
let
repos = config.pub-solar-os.backups.repos;
restic = config.pub-solar-os.backups.restic;
resources = config.pub-solar-os.backups.resources;
repoNames = builtins.attrNames repos;
backupNames = builtins.attrNames restic;
repoNames = builtins.attrNames repos;
resourceNames = builtins.attrNames resources;
backupNames = builtins.attrNames restic;
createBackups =
backupName:
map (repoName: {
name = "${backupName}-${repoName}";
value = repos."${repoName}" // restic."${backupName}";
}) repoNames;
createResourceService = resourceName: {
name = "restic-backups-resource-${resourceName}";
value = {
serviceConfig =
let
createResourceApp = pkgs.writeShellApplication {
name = "create-resource-${resourceName}";
text = resources."${resourceName}".resourceCreateCommand;
};
destroyResourceApp = pkgs.writeShellApplication {
name = "destroy-resource-${resourceName}";
text = resources."${resourceName}".resourceDestroyCommand;
};
in
builtins.listToAttrs (lib.lists.flatten (map createBackups backupNames));
in
{
Type = "oneshot";
ExecStart = lib.mkIf (
resources."${resourceName}".resourceCreateCommand != null
) "${createResourceApp}/bin/create-resource-${resourceName}";
ExecStop = lib.mkIf (
resources."${resourceName}".resourceDestroyCommand != null
) "${destroyResourceApp}/bin/destroy-resource-${resourceName}";
RemainAfterExit = true;
};
unitConfig.StopWhenUnneeded = true;
};
};
# Used for pub-solar-os.backups.repos.storagebox
programs.ssh.knownHosts = {
"u377325.your-storagebox.de".publicKey =
"ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA5EB5p/5Hp3hGW1oHok+PIOH9Pbn7cnUiGmUEBrCVjnAw+HrKyN8bYVV0dIGllswYXwkG/+bgiBlE6IVIBAq+JwVWu1Sss3KarHY3OvFJUXZoZyRRg/Gc/+LRCE7lyKpwWQ70dbelGRyyJFH36eNv6ySXoUYtGkwlU5IVaHPApOxe4LHPZa/qhSRbPo2hwoh0orCtgejRebNtW5nlx00DNFgsvn8Svz2cIYLxsPVzKgUxs8Zxsxgn+Q/UvR7uq4AbAhyBMLxv7DjJ1pc7PJocuTno2Rw9uMZi1gkjbnmiOh6TTXIEWbnroyIhwc8555uto9melEUmWNQ+C+PwAK+MPw==";
"[u377325.your-storagebox.de]:23".publicKey =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIICf9svRenC/PLKIL9nk6K/pxQgoiFC41wTNvoIncOxs";
createResourceDependency = resourceName: backupName: repoName: {
name = "restic-backups-${backupName}-${repoName}";
value = {
after = [ "restic-backups-resource-${resourceName}.service" ];
requires = [ "restic-backups-resource-${resourceName}.service" ];
serviceConfig.PrivateTmp = lib.mkForce false;
unitConfig = {
JoinsNamespaceOf = [ "restic-backups-resource-${resourceName}.service" ];
};
};
};
createResourceDependencies =
backupName:
map (
repoName:
map (resourceName: createResourceDependency resourceName backupName repoName)
restic."${backupName}".resources
) repoNames;
createBackups =
backupName:
map (repoName: {
name = "${backupName}-${repoName}";
value = lib.attrsets.filterAttrs (key: val: (key != "resources")) (
repos."${repoName}" // restic."${backupName}"
);
}) repoNames;
in
{
systemd.services =
(builtins.listToAttrs (map createResourceService resourceNames))
// (builtins.listToAttrs (lib.lists.flatten (map createResourceDependencies backupNames)));
services.restic.backups = builtins.listToAttrs (lib.lists.flatten (map createBackups backupNames));
# Used for pub-solar-os.backups.repos.storagebox
programs.ssh.knownHosts = {
"u377325.your-storagebox.de".publicKey =
"ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA5EB5p/5Hp3hGW1oHok+PIOH9Pbn7cnUiGmUEBrCVjnAw+HrKyN8bYVV0dIGllswYXwkG/+bgiBlE6IVIBAq+JwVWu1Sss3KarHY3OvFJUXZoZyRRg/Gc/+LRCE7lyKpwWQ70dbelGRyyJFH36eNv6ySXoUYtGkwlU5IVaHPApOxe4LHPZa/qhSRbPo2hwoh0orCtgejRebNtW5nlx00DNFgsvn8Svz2cIYLxsPVzKgUxs8Zxsxgn+Q/UvR7uq4AbAhyBMLxv7DjJ1pc7PJocuTno2Rw9uMZi1gkjbnmiOh6TTXIEWbnroyIhwc8555uto9melEUmWNQ+C+PwAK+MPw==";
"[u377325.your-storagebox.de]:23".publicKey =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIICf9svRenC/PLKIL9nk6K/pxQgoiFC41wTNvoIncOxs";
};
};
};
}

View file

@ -59,18 +59,18 @@
};
};
pub-solar-os.backups.restic.keycloak = {
paths = [ "/tmp/keycloak-backup.sql" ];
timerConfig = {
OnCalendar = "*-*-* 03:00:00 Etc/UTC";
pub-solar-os.backups = {
resources.keycloak-db.resourceCreateCommand = ''
${pkgs.sudo}/bin/sudo -u postgres ${pkgs.postgresql}/bin/pg_dump -d keycloak -f /tmp/keycloak-backup.sql
'';
restic.keycloak = {
resources = [ "keycloak-db" ];
paths = [ "/tmp/keycloak-backup.sql" ];
timerConfig = {
OnCalendar = "*-*-* 03:00:00 Etc/UTC";
};
initialize = true;
};
initialize = true;
backupPrepareCommand = ''
${pkgs.sudo}/bin/sudo -u postgres ${pkgs.postgresql}/bin/pg_dump -d keycloak > /tmp/keycloak-backup.sql
'';
backupCleanupCommand = ''
rm /tmp/keycloak-backup.sql
'';
};
};
}

View file

@ -334,26 +334,25 @@ in
};
};
pub-solar-os.backups.restic.matrix-synapse = {
paths = [
"/var/lib/matrix-synapse"
"/var/lib/matrix-appservice-irc"
"/var/lib/mautrix-telegram"
"/tmp/matrix-synapse-backup.sql"
"/tmp/matrix-authentication-service-backup.sql"
];
timerConfig = {
OnCalendar = "*-*-* 05:00:00 Etc/UTC";
pub-solar-os.backups = {
resources.matrix-db.resourceCreateCommand = ''
${pkgs.sudo}/bin/sudo -u postgres ${pkgs.postgresql}/bin/pg_dump -d matrix -f /tmp/matrix-synapse-backup.sql
${pkgs.sudo}/bin/sudo -u postgres ${pkgs.postgresql}/bin/pg_dump -d matrix-authentication-service -f /tmp/matrix-authentication-service-backup.sql
'';
restic.matrix-synapse = {
resources = [ "matrix-db" ];
paths = [
"/var/lib/matrix-synapse"
"/var/lib/matrix-appservice-irc"
"/var/lib/mautrix-telegram"
"/tmp/matrix-synapse-backup.sql"
"/tmp/matrix-authentication-service-backup.sql"
];
timerConfig = {
OnCalendar = "*-*-* 05:00:00 Etc/UTC";
};
initialize = true;
};
initialize = true;
backupPrepareCommand = ''
${pkgs.sudo}/bin/sudo -u postgres ${pkgs.postgresql}/bin/pg_dump -d matrix > /tmp/matrix-synapse-backup.sql
${pkgs.sudo}/bin/sudo -u postgres ${pkgs.postgresql}/bin/pg_dump -d matrix-authentication-service > /tmp/matrix-authentication-service-backup.sql
'';
backupCleanupCommand = ''
rm /tmp/matrix-synapse-backup.sql
rm /tmp/matrix-authentication-service-backup.sql
'';
};
};
}

View file

@ -239,21 +239,24 @@ in
};
};
pub-solar-os.backups.restic.mediawiki = {
paths = [
"/var/lib/mediawiki/images"
"/var/lib/mediawiki/uploads"
"/tmp/mediawiki-backup.sql"
];
timerConfig = {
OnCalendar = "*-*-* 00:00:00 Etc/UTC";
pub-solar-os.backups = {
resources.mediawiki-db = {
resourceCreateCommand = ''
${pkgs.sudo}/bin/sudo -u postgres ${pkgs.postgresql}/bin/pg_dump -d mediawiki -f /tmp/mediawiki-backup.sql
'';
};
restic.mediawiki = {
resources = [ "mediawiki-db" ];
paths = [
"/var/lib/mediawiki/images"
"/var/lib/mediawiki/uploads"
"/tmp/mediawiki-backup.sql"
];
timerConfig = {
OnCalendar = "*-*-* 00:00:00 Etc/UTC";
};
initialize = true;
};
initialize = true;
backupPrepareCommand = ''
${pkgs.sudo}/bin/sudo -u postgres ${pkgs.postgresql}/bin/pg_dump -d mediawiki > /tmp/mediawiki-backup.sql
'';
backupCleanupCommand = ''
rm /tmp/mediawiki-backup.sql
'';
};
}

View file

@ -148,25 +148,25 @@ in
};
};
pub-solar-os.backups.restic.obs-portal = {
paths = [
"/var/lib/obs-portal/data"
"/tmp/obs-portal-backup.sql"
];
timerConfig = {
OnCalendar = "*-*-* 06:30:00 Etc/UTC";
pub-solar-os.backups = {
resources.obs-db.resourceCreateCommand = ''
${pkgs.docker}/bin/docker exec -i --user postgres obs-portal-db pg_dump -d obs -f /tmp/obs-portal-backup.sql
'';
restic.obs-portal = {
resources = [ "obs-db" ];
paths = [
"/var/lib/obs-portal/data"
"/tmp/obs-portal-backup.sql"
];
timerConfig = {
OnCalendar = "*-*-* 06:30:00 Etc/UTC";
};
initialize = true;
pruneOpts = [
"--keep-daily 7"
"--keep-weekly 4"
"--keep-monthly 3"
];
};
initialize = true;
backupPrepareCommand = ''
${pkgs.docker}/bin/docker exec -i --user postgres obs-portal-db pg_dump obs > /tmp/obs-portal-backup.sql
'';
backupCleanupCommand = ''
rm /tmp/obs-portal-backup.sql
'';
pruneOpts = [
"--keep-daily 7"
"--keep-weekly 4"
"--keep-monthly 3"
];
};
}