nixos-configs/modules/borgbackup/module.nix

269 lines
7.6 KiB
Nix
Raw Normal View History

2024-03-02 22:04:23 -05:00
{
config,
lib,
pkgs,
...
}: let
inherit (lib) all any attrValues length mapAttrs mkIf mkOption types;
inherit (builtins) listToAttrs removeAttrs;
inherit (config.sops) secrets;
inherit (config.vars) hostName;
cfg = config.services.borgbackup;
in {
options.services.borgbackup = {
configs = mkOption {
# Taken from https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/backup/borgbackup.nix
type = types.attrsOf (types.submodule (
let
globalConfig = config;
in
{
name,
config,
...
}: {
options = {
paths = mkOption {
type = with types; nullOr (coercedTo str lib.singleton (listOf str));
default = null;
};
dumpCommand = mkOption {
type = with types; nullOr path;
default = null;
};
repo = mkOption {
type = types.str;
default = name;
};
removableDevice = mkOption {
type = types.bool;
default = false;
};
archiveBaseName = mkOption {
type = types.nullOr (types.strMatching "[^/{}]+");
default = "${globalConfig.networking.hostName}-${name}";
};
dateFormat = mkOption {
type = types.str;
default = "+%Y-%m-%dT%H:%M:%S";
};
startAt = mkOption {
type = with types; either str (listOf str);
# Run every 3 hours
default = "00/3:00";
};
persistentTimer = mkOption {
default = false;
type = types.bool;
};
inhibitsSleep = mkOption {
default = false;
type = types.bool;
};
user = mkOption {
type = types.str;
default = "root";
};
group = mkOption {
type = types.str;
default = "root";
};
encryption.mode = mkOption {
type = types.enum [
"repokey"
"keyfile"
"repokey-blake2"
"keyfile-blake2"
"authenticated"
"authenticated-blake2"
"none"
];
default = "none";
};
encryption.passCommand = mkOption {
type = with types; nullOr str;
default = null;
};
encryption.passphrase = mkOption {
type = with types; nullOr str;
default = null;
};
compression = mkOption {
type = types.strMatching "none|(auto,)?(lz4|zstd|zlib|lzma)(,[[:digit:]]{1,2})?";
default = "auto,lz4";
};
exclude = mkOption {
type = with types; listOf str;
default = [];
};
patterns = mkOption {
type = with types; listOf str;
default = [];
};
readWritePaths = mkOption {
type = with types; listOf path;
default = [];
};
privateTmp = mkOption {
type = types.bool;
default = true;
};
doInit = mkOption {
type = types.bool;
default = true;
};
appendFailedSuffix = mkOption {
type = types.bool;
default = true;
};
prune.keep = mkOption {
type = with types; attrsOf (either int (strMatching "[[:digit:]]+[Hdwmy]"));
default = {};
};
prune.prefix = mkOption {
type = types.nullOr (types.str);
default = config.archiveBaseName;
};
environment = mkOption {
type = with types; attrsOf str;
default = {};
};
preHook = mkOption {
type = types.lines;
default = "";
};
postInit = mkOption {
type = types.lines;
default = "";
};
postCreate = mkOption {
type = types.lines;
default = "";
};
postPrune = mkOption {
type = types.lines;
default = "";
};
postHook = mkOption {
type = types.lines;
default = "";
};
extraArgs = mkOption {
type = types.str;
default = "";
};
extraInitArgs = mkOption {
type = types.str;
default = "";
};
extraCreateArgs = mkOption {
type = types.str;
default = "";
};
extraPruneArgs = mkOption {
type = types.str;
default = "";
};
extraCompactArgs = mkOption {
type = types.str;
default = "";
};
};
}
));
default = {};
};
existingRepos = mkOption {
type = with types;
listOf (submodule {
options = {
name = mkOption {
type = str;
};
authorizedKeys = mkOption {
type = listOf str;
default = [];
};
};
});
default = [];
};
};
config = mkIf (cfg.configs != {}) {
assertions = [
{
assertion = all (
conf:
any (repo: conf.repo == repo.name) cfg.existingRepos
) (attrValues cfg.configs);
message = ''
The repo you want to backup to needs to exist.
'';
}
];
services.borgbackup = let
backupDir = "/data/borgbackups";
in {
repos =
mkIf (hostName == "nos" && length cfg.existingRepos > 0)
(listToAttrs (map (r: {
inherit (r) name;
value = {
authorizedKeysAppendOnly = r.authorizedKeys;
path = "${backupDir}/${r.name}";
};
})
cfg.existingRepos));
jobs = mapAttrs (n: v: let
otherAttrs = removeAttrs v [
"environment"
"paths"
"preHook"
"postHook"
"repo"
];
pathPrefix = "/root/snaps";
snapPath = "${pathPrefix}/${n}";
in
{
environment =
v.environment
// (mkIf (hostName != "nos") {
BORG_RSH = "ssh -o 'StrictHostKeyChecking=no' -i ${secrets.borg-ssh.path}";
});
2024-03-02 22:04:23 -05:00
paths = map (x: snapPath + x) v.paths;
preHook =
v.preHook
+ ''
if [[ ! -d ${pathPrefix} ]]; then
mkdir -p ${pathPrefix}
fi
${pkgs.btrfs-progs}/bin/btrfs subvolume snapshot -r / ${snapPath}
'';
postHook =
v.postHook
+ ''
${pkgs.btrfs-progs}/bin/btrfs subvolume delete ${snapPath}
'';
repo =
if (hostName != "nos")
then "ssh://borg@nos${backupDir}/${v.repo}"
2024-03-02 22:04:23 -05:00
else "${backupDir}/${v.repo}";
}
// otherAttrs)
cfg.configs;
};
};
}