2024-03-02 22:04:23 -05:00
|
|
|
{
|
|
|
|
config,
|
|
|
|
lib,
|
|
|
|
pkgs,
|
|
|
|
...
|
|
|
|
}: let
|
2024-09-13 01:58:00 -04:00
|
|
|
inherit (lib) all any attrValues findSingle length mapAttrs mkIf mkOption types;
|
|
|
|
inherit (builtins) filter hasAttr listToAttrs removeAttrs;
|
2024-03-02 22:04:23 -05:00
|
|
|
|
|
|
|
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 {
|
2024-05-05 23:07:06 -04:00
|
|
|
type = types.nullOr (types.coercedTo types.str lib.singleton (types.listOf types.str));
|
2024-03-02 22:04:23 -05:00
|
|
|
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 {
|
2024-05-05 23:07:06 -04:00
|
|
|
type = types.listOf (types.submodule {
|
|
|
|
options = {
|
|
|
|
name = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
};
|
2024-09-13 01:58:00 -04:00
|
|
|
host = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
};
|
2024-05-05 23:07:06 -04:00
|
|
|
authorizedKeys = mkOption {
|
|
|
|
type = types.listOf types.str;
|
|
|
|
default = [];
|
2024-03-02 22:04:23 -05:00
|
|
|
};
|
2024-05-05 23:07:06 -04:00
|
|
|
};
|
|
|
|
});
|
2024-03-02 22:04:23 -05:00
|
|
|
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
|
2024-09-13 01:58:00 -04:00
|
|
|
backupDir = {
|
|
|
|
nos = "/data/borgbackups";
|
|
|
|
servivi = "/home/backups";
|
|
|
|
};
|
2024-03-02 22:04:23 -05:00
|
|
|
in {
|
|
|
|
repos =
|
2024-09-13 01:58:00 -04:00
|
|
|
mkIf (length cfg.existingRepos > 0)
|
2024-03-02 22:04:23 -05:00
|
|
|
(listToAttrs (map (r: {
|
|
|
|
inherit (r) name;
|
|
|
|
value = {
|
|
|
|
authorizedKeysAppendOnly = r.authorizedKeys;
|
2024-09-13 01:58:00 -04:00
|
|
|
path = "${backupDir.${hostName}}/${r.name}";
|
2024-03-02 22:04:23 -05:00
|
|
|
};
|
|
|
|
})
|
2024-09-13 01:58:00 -04:00
|
|
|
(filter (x: x.host == hostName) cfg.existingRepos)));
|
2024-03-02 22:04:23 -05:00
|
|
|
|
|
|
|
jobs = mapAttrs (n: v: let
|
2024-09-13 01:58:00 -04:00
|
|
|
existingRepo = findSingle (x: x.name == v.repo) null null cfg.existingRepos;
|
2024-03-02 22:04:23 -05:00
|
|
|
otherAttrs = removeAttrs v [
|
|
|
|
"environment"
|
|
|
|
"paths"
|
|
|
|
"preHook"
|
|
|
|
"postHook"
|
|
|
|
"repo"
|
|
|
|
];
|
|
|
|
pathPrefix = "/root/snaps";
|
|
|
|
snapPath = "${pathPrefix}/${n}";
|
|
|
|
in
|
|
|
|
{
|
2024-03-05 11:32:53 -05:00
|
|
|
environment =
|
|
|
|
v.environment
|
2024-09-13 01:58:00 -04:00
|
|
|
// (mkIf (hasAttr "borg-ssh" secrets) {
|
2024-03-05 11:32:53 -05:00
|
|
|
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 =
|
2024-09-13 01:58:00 -04:00
|
|
|
if (hostName != existingRepo.host)
|
|
|
|
then "ssh://borg@${existingRepo.host}${backupDir.${existingRepo.host}}/${v.repo}"
|
|
|
|
else "${backupDir.${existingRepo.host}}/${v.repo}";
|
2024-03-02 22:04:23 -05:00
|
|
|
}
|
|
|
|
// otherAttrs)
|
|
|
|
cfg.configs;
|
|
|
|
};
|
|
|
|
};
|
2024-08-02 22:32:29 -04:00
|
|
|
|
|
|
|
# For accurate stack trace
|
|
|
|
_file = ./module.nix;
|
2024-03-02 22:04:23 -05:00
|
|
|
}
|