infrastructure/nixos/backups/restic.nix
2024-10-05 12:08:15 -07:00

133 lines
4 KiB
Nix

{
pkgs,
config,
systemConfig,
lib,
...
}: let
inherit (lib.modules) mkIf mkMerge mkDefault;
inherit (lib.attrsets) mapAttrs' mapAttrsToList nameValuePair;
inherit (lib.lists) concatMap toList;
inherit (lib.strings) replaceStrings concatMapStringsSep;
inherit (config.sops.secrets) restic-shared-repo-b2 restic-shared-password restic-shared-env-b2;
group = "backups";
mkSharedPath = subpath: "/mnt/shared/${subpath}";
# TODO: this properly as a module or something
sharedServices = {
hass.config = config.services.home-assistant;
grocy.config = config.services.grocy;
barcodebuddy.config = config.services.barcodebuddy;
# XXX: kanidm currently unused
/*kanidm = {
config = config.services.kanidm;
enable = config.services.kanidm.enableServer;
subpath = "kanidm/kanidm.db";
};*/
mosquitto.config = config.services.mosquitto;
plex = {
config = config.services.plex;
compression = "auto";
subpath = [
"plex/Plex Media Server/Preferences.xml"
#"plex/Databases" # omitted, see dynamicFilesFrom to select only the latest backup...
];
settings = {
dynamicFilesFrom = let
databases = [
"com.plexapp.plugins.library.blobs.db"
"com.plexapp.plugins.library.db"
];
ls = "${pkgs.coreutils}/bin/ls";
tail = "${pkgs.coreutils}/bin/tail";
mkLatestDb = database: ''${ls} ${mkSharedPath "plex/Databases/${database}"}* | ${tail} -n1'';
in
concatMapStringsSep " &&\n" mkLatestDb databases;
};
};
postgresql = {
# TODO: synchronize with postgresqlBackup service via flock or After=
config = config.services.postgresql;
subpath = "postgresql/dump";
};
taskchampion.config = config.services.taskchampion-sync-server;
unifi = {
config = config.services.unifi;
subpath = "unifi/data/backup";
};
zigbee2mqtt.config = config.services.zigbee2mqtt;
vaultwarden.config = config.services.vaultwarden;
"minecraft/bedrock".config = config.services.minecraft-bedrock-server;
minecraft-java = {
config = config.services.minecraft-java-server;
subpath = "minecraft/java/marka-server";
};
};
in {
services.restic.backups = let
isBackup = config.networking.hostName == "hakurei";
mkBackupB2 = name: subpath': {
config,
enable ? config.enable,
user ? config.user or null,
subpath ? subpath',
compression ? "max",
settings ? {},
}: let
tags = [
"infra"
"shared-${name}"
"system-${systemConfig.name}"
];
conf = {
user = mkIf (enable && user != null) user;
repositoryFile = restic-shared-repo-b2.path;
passwordFile = restic-shared-password.path;
environmentFile = restic-shared-env-b2.path;
paths = map mkSharedPath (toList subpath);
extraBackupArgs = mkMerge [
(mkIf (compression != "auto") [
"--compression"
compression
])
(concatMap (tag: ["--tag" tag]) tags)
];
timerConfig = {
OnCalendar = "03:30";
Persistent = true;
RandomizedDelaySec = "4h";
};
};
in
mkIf (enable || isBackup) (mkMerge [conf settings]);
backups = mapAttrs' (subpath: service: let
name = replaceStrings ["/"] ["-"] subpath;
in
nameValuePair "${name}-b2" (mkBackupB2 name subpath service))
sharedServices;
in
backups;
users.groups.${group} = {
members = mapAttrsToList (_: {
config,
enable ? config.enable,
user ? config.user or null,
...
}:
mkIf (enable && user != null) user)
sharedServices;
};
sops.secrets = let
sopsFile = mkDefault ../secrets/restic.yaml;
mode = "0640";
in {
restic-shared-env-b2 = {
inherit group mode;
};
restic-shared-password = {
inherit sopsFile group mode;
};
restic-shared-repo-b2 = {
inherit sopsFile group mode;
};
};
}