2022-09-25 17:27:04 +00:00
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
|
|
|
with lib;
|
|
|
|
|
|
|
|
let
|
|
|
|
cfg = config.programs.borgmatic;
|
|
|
|
|
2023-03-20 19:20:33 +00:00
|
|
|
yamlFormat = pkgs.formats.yaml { };
|
|
|
|
|
2022-09-25 17:27:04 +00:00
|
|
|
mkNullableOption = args:
|
|
|
|
lib.mkOption (args // {
|
|
|
|
type = lib.types.nullOr args.type;
|
|
|
|
default = null;
|
|
|
|
});
|
|
|
|
|
2023-11-01 13:41:58 +00:00
|
|
|
cleanRepositories = repos:
|
|
|
|
map (repo:
|
|
|
|
if builtins.isString repo then {
|
|
|
|
path = repo;
|
|
|
|
} else
|
|
|
|
removeNullValues repo) repos;
|
|
|
|
|
2022-09-25 17:27:04 +00:00
|
|
|
mkRetentionOption = frequency:
|
|
|
|
mkNullableOption {
|
|
|
|
type = types.int;
|
|
|
|
description =
|
|
|
|
"Number of ${frequency} archives to keep. Use -1 for no limit.";
|
|
|
|
example = 3;
|
|
|
|
};
|
|
|
|
|
|
|
|
extraConfigOption = mkOption {
|
2023-03-20 19:20:33 +00:00
|
|
|
type = yamlFormat.type;
|
2022-09-25 17:27:04 +00:00
|
|
|
default = { };
|
|
|
|
description = "Extra settings.";
|
|
|
|
};
|
|
|
|
|
2023-11-01 13:41:58 +00:00
|
|
|
repositoryOption = types.submodule {
|
|
|
|
options = {
|
|
|
|
path = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
example = "ssh://myuser@myrepo.myserver.com/./repo";
|
|
|
|
description = "Path of the repository.";
|
|
|
|
};
|
|
|
|
|
|
|
|
label = mkOption {
|
|
|
|
type = types.nullOr types.str;
|
|
|
|
default = null;
|
|
|
|
example = "remote";
|
|
|
|
description = ''
|
|
|
|
Short text describing the repository. Can be used with the
|
|
|
|
`--repository` flag to select a repository.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2022-09-25 17:27:04 +00:00
|
|
|
consistencyCheckModule = types.submodule {
|
|
|
|
options = {
|
|
|
|
name = mkOption {
|
|
|
|
type = types.enum [ "repository" "archives" "data" "extract" ];
|
|
|
|
description = "Name of consistency check to run.";
|
|
|
|
example = "repository";
|
|
|
|
};
|
|
|
|
|
|
|
|
frequency = mkNullableOption {
|
|
|
|
type = types.strMatching "([[:digit:]]+ .*)|always";
|
|
|
|
description = "Frequency of this type of check";
|
|
|
|
example = "2 weeks";
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2023-03-19 10:28:02 +00:00
|
|
|
configModule = types.submodule ({ config, ... }: {
|
|
|
|
config.location.extraConfig.exclude_from =
|
|
|
|
mkIf config.location.excludeHomeManagerSymlinks
|
|
|
|
(mkAfter [ (toString hmExcludeFile) ]);
|
2022-09-25 17:27:04 +00:00
|
|
|
options = {
|
|
|
|
location = {
|
2024-02-21 01:03:20 +00:00
|
|
|
sourceDirectories = mkNullableOption {
|
2022-09-25 17:27:04 +00:00
|
|
|
type = types.listOf types.str;
|
2024-02-21 01:03:20 +00:00
|
|
|
default = null;
|
|
|
|
description = ''
|
|
|
|
Directories to backup.
|
|
|
|
|
|
|
|
Mutually exclusive with [](#opt-programs.borgmatic.backups._name_.location.patterns).
|
|
|
|
'';
|
2022-09-25 17:27:04 +00:00
|
|
|
example = literalExpression "[config.home.homeDirectory]";
|
|
|
|
};
|
|
|
|
|
2024-02-21 01:03:20 +00:00
|
|
|
patterns = mkNullableOption {
|
|
|
|
type = types.listOf types.str;
|
|
|
|
default = null;
|
|
|
|
description = ''
|
|
|
|
Patterns to include/exclude.
|
|
|
|
|
|
|
|
See the output of `borg help patterns` for the syntax. Pattern paths
|
|
|
|
are relative to `/` even when a different recursion root is set.
|
|
|
|
|
|
|
|
Mutually exclusive with [](#opt-programs.borgmatic.backups._name_.location.sourceDirectories).
|
|
|
|
'';
|
|
|
|
example = literalExpression ''
|
|
|
|
[
|
|
|
|
"R /home/user"
|
|
|
|
"- home/user/.cache"
|
|
|
|
"- home/user/Downloads"
|
|
|
|
"+ home/user/Videos/Important Video"
|
|
|
|
"- home/user/Videos"
|
|
|
|
]
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
2022-09-25 17:27:04 +00:00
|
|
|
repositories = mkOption {
|
2023-11-01 13:41:58 +00:00
|
|
|
type = types.listOf (types.either types.str repositoryOption);
|
|
|
|
apply = cleanRepositories;
|
|
|
|
example = literalExpression ''
|
|
|
|
[
|
|
|
|
{
|
|
|
|
"path" = "ssh://myuser@myrepo.myserver.com/./repo";
|
|
|
|
"label" = "server";
|
|
|
|
}
|
|
|
|
{
|
|
|
|
"path" = "/var/lib/backups/local.borg";
|
|
|
|
"label" = "local";
|
|
|
|
}
|
|
|
|
]
|
|
|
|
'';
|
|
|
|
description = ''
|
|
|
|
List of local or remote repositories with paths and optional labels.
|
|
|
|
'';
|
2022-09-25 17:27:04 +00:00
|
|
|
};
|
|
|
|
|
2023-03-19 10:28:02 +00:00
|
|
|
excludeHomeManagerSymlinks = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
description = ''
|
|
|
|
Whether to exclude Home Manager generated symbolic links from
|
|
|
|
the backups. This facilitates restoring the whole home
|
|
|
|
directory when the Nix store doesn't contain the latest
|
|
|
|
Home Manager generation.
|
|
|
|
'';
|
|
|
|
default = false;
|
|
|
|
example = true;
|
|
|
|
};
|
|
|
|
|
2022-09-25 17:27:04 +00:00
|
|
|
extraConfig = extraConfigOption;
|
|
|
|
};
|
|
|
|
|
|
|
|
storage = {
|
|
|
|
encryptionPasscommand = mkNullableOption {
|
|
|
|
type = types.str;
|
|
|
|
description = "Command writing the passphrase to standard output.";
|
|
|
|
example =
|
|
|
|
literalExpression ''"''${pkgs.password-store}/bin/pass borg-repo"'';
|
|
|
|
};
|
|
|
|
extraConfig = extraConfigOption;
|
|
|
|
};
|
|
|
|
|
|
|
|
retention = {
|
|
|
|
keepWithin = mkNullableOption {
|
|
|
|
type = types.strMatching "[[:digit:]]+[Hdwmy]";
|
|
|
|
description = "Keep all archives within this time interval.";
|
|
|
|
example = "2d";
|
|
|
|
};
|
|
|
|
|
|
|
|
keepSecondly = mkRetentionOption "secondly";
|
|
|
|
keepMinutely = mkRetentionOption "minutely";
|
|
|
|
keepHourly = mkRetentionOption "hourly";
|
|
|
|
keepDaily = mkRetentionOption "daily";
|
|
|
|
keepWeekly = mkRetentionOption "weekly";
|
|
|
|
keepMonthly = mkRetentionOption "monthly";
|
|
|
|
keepYearly = mkRetentionOption "yearly";
|
|
|
|
|
|
|
|
extraConfig = extraConfigOption;
|
|
|
|
};
|
|
|
|
|
|
|
|
consistency = {
|
|
|
|
checks = mkOption {
|
|
|
|
type = types.listOf consistencyCheckModule;
|
|
|
|
default = [ ];
|
|
|
|
description = "Consistency checks to run";
|
|
|
|
example = literalExpression ''
|
|
|
|
[
|
|
|
|
{
|
|
|
|
name = "repository";
|
|
|
|
frequency = "2 weeks";
|
|
|
|
}
|
|
|
|
{
|
|
|
|
name = "archives";
|
|
|
|
frequency = "4 weeks";
|
|
|
|
}
|
|
|
|
{
|
|
|
|
name = "data";
|
|
|
|
frequency = "6 weeks";
|
|
|
|
}
|
|
|
|
{
|
|
|
|
name = "extract";
|
|
|
|
frequency = "6 weeks";
|
|
|
|
}
|
|
|
|
];
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
extraConfig = extraConfigOption;
|
|
|
|
};
|
2023-06-18 05:57:41 +00:00
|
|
|
|
|
|
|
output = { extraConfig = extraConfigOption; };
|
|
|
|
|
|
|
|
hooks = { extraConfig = extraConfigOption; };
|
2022-09-25 17:27:04 +00:00
|
|
|
};
|
2023-03-19 10:28:02 +00:00
|
|
|
});
|
2022-09-25 17:27:04 +00:00
|
|
|
|
|
|
|
removeNullValues = attrSet: filterAttrs (key: value: value != null) attrSet;
|
|
|
|
|
2023-03-19 10:28:02 +00:00
|
|
|
hmFiles = builtins.attrValues config.home.file;
|
|
|
|
hmSymlinks = (lib.filter (file: !file.recursive) hmFiles);
|
|
|
|
hmExcludePattern = file: ''
|
|
|
|
${config.home.homeDirectory}/${file.target}
|
|
|
|
'';
|
|
|
|
hmExcludePatterns = lib.concatMapStrings hmExcludePattern hmSymlinks;
|
|
|
|
hmExcludeFile = pkgs.writeText "hm-symlinks.txt" hmExcludePatterns;
|
|
|
|
|
2022-09-25 17:27:04 +00:00
|
|
|
writeConfig = config:
|
2023-10-14 22:38:55 +00:00
|
|
|
generators.toYAML { } (removeNullValues ({
|
|
|
|
source_directories = config.location.sourceDirectories;
|
2024-02-21 01:03:20 +00:00
|
|
|
patterns = config.location.patterns;
|
2023-10-14 22:38:55 +00:00
|
|
|
repositories = config.location.repositories;
|
|
|
|
encryption_passcommand = config.storage.encryptionPasscommand;
|
|
|
|
keep_within = config.retention.keepWithin;
|
|
|
|
keep_secondly = config.retention.keepSecondly;
|
|
|
|
keep_minutely = config.retention.keepMinutely;
|
|
|
|
keep_hourly = config.retention.keepHourly;
|
|
|
|
keep_daily = config.retention.keepDaily;
|
|
|
|
keep_weekly = config.retention.keepWeekly;
|
|
|
|
keep_monthly = config.retention.keepMonthly;
|
|
|
|
keep_yearly = config.retention.keepYearly;
|
|
|
|
checks = config.consistency.checks;
|
|
|
|
} // config.location.extraConfig // config.storage.extraConfig
|
|
|
|
// config.retention.extraConfig // config.consistency.extraConfig
|
|
|
|
// config.output.extraConfig // config.hooks.extraConfig));
|
2022-09-25 17:27:04 +00:00
|
|
|
in {
|
|
|
|
meta.maintainers = [ maintainers.DamienCassou ];
|
|
|
|
|
|
|
|
options = {
|
|
|
|
programs.borgmatic = {
|
|
|
|
enable = mkEnableOption "Borgmatic";
|
|
|
|
|
|
|
|
package = mkPackageOption pkgs "borgmatic" { };
|
|
|
|
|
|
|
|
backups = mkOption {
|
|
|
|
type = types.attrsOf configModule;
|
|
|
|
description = ''
|
|
|
|
Borgmatic allows for several named backup configurations,
|
|
|
|
each with its own source directories and repositories.
|
|
|
|
'';
|
|
|
|
example = literalExpression ''
|
|
|
|
{
|
|
|
|
personal = {
|
|
|
|
location = {
|
|
|
|
sourceDirectories = [ "/home/me/personal" ];
|
|
|
|
repositories = [ "ssh://myuser@myserver.com/./personal-repo" ];
|
|
|
|
};
|
|
|
|
};
|
|
|
|
work = {
|
|
|
|
location = {
|
|
|
|
sourceDirectories = [ "/home/me/work" ];
|
|
|
|
repositories = [ "ssh://myuser@myserver.com/./work-repo" ];
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
config = mkIf cfg.enable {
|
|
|
|
assertions = [
|
|
|
|
(lib.hm.assertions.assertPlatform "programs.borgmatic" pkgs
|
|
|
|
lib.platforms.linux)
|
2024-02-21 01:03:20 +00:00
|
|
|
] ++ (mapAttrsToList (backup: opts: {
|
|
|
|
assertion = opts.location.sourceDirectories == null
|
|
|
|
|| opts.location.patterns == null;
|
|
|
|
message = ''
|
|
|
|
Borgmatic backup configuration "${backup}" cannot specify both 'location.sourceDirectories' and 'location.patterns'.
|
|
|
|
'';
|
|
|
|
}) cfg.backups) ++ (mapAttrsToList (backup: opts: {
|
|
|
|
assertion = !(opts.location.sourceDirectories == null
|
|
|
|
&& opts.location.patterns == null);
|
|
|
|
message = ''
|
|
|
|
Borgmatic backup configuration "${backup}" must specify one of 'location.sourceDirectories' or 'location.patterns'.
|
|
|
|
'';
|
|
|
|
}) cfg.backups);
|
2022-09-25 17:27:04 +00:00
|
|
|
|
|
|
|
xdg.configFile = with lib.attrsets;
|
|
|
|
mapAttrs' (configName: config:
|
|
|
|
nameValuePair ("borgmatic.d/" + configName + ".yaml") {
|
|
|
|
text = writeConfig config;
|
|
|
|
}) cfg.backups;
|
|
|
|
|
|
|
|
home.packages = [ cfg.package ];
|
|
|
|
};
|
|
|
|
}
|