mirror of
https://github.com/nix-community/home-manager
synced 2024-11-22 20:53:14 +00:00
borgmatic: add module
This commit is contained in:
parent
da3b8049fd
commit
04f5399978
12 changed files with 473 additions and 0 deletions
5
.github/CODEOWNERS
vendored
5
.github/CODEOWNERS
vendored
|
@ -343,6 +343,11 @@ Makefile @thiagokokada
|
|||
|
||||
/modules/services/betterlockscreen.nix @SebTM
|
||||
|
||||
/modules/programs/borgmatic.nix @DamienCassou
|
||||
/modules/services/borgmatic.nix @DamienCassou
|
||||
/tests/modules/programs/borgmatic @DamienCassou
|
||||
/tests/modules/services/borgmatic @DamienCassou
|
||||
|
||||
/modules/services/caffeine.nix @uvNikita
|
||||
|
||||
/modules/services/cbatticon.nix @pmiddend
|
||||
|
|
|
@ -748,6 +748,20 @@ in
|
|||
A new module is available: 'programs.discocss'.
|
||||
'';
|
||||
}
|
||||
|
||||
{
|
||||
time = "2022-10-16T19:49:46+00:00";
|
||||
condition = hostPlatform.isLinux;
|
||||
message = ''
|
||||
Two new modules are available:
|
||||
|
||||
- 'programs.borgmatic' and
|
||||
- 'services.borgmatic'.
|
||||
|
||||
use the first to configure the borgmatic tool and the second if you
|
||||
want to automatically run scheduled backups.
|
||||
'';
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ let
|
|||
./programs/bashmount.nix
|
||||
./programs/bat.nix
|
||||
./programs/beets.nix
|
||||
./programs/borgmatic.nix
|
||||
./programs/bottom.nix
|
||||
./programs/broot.nix
|
||||
./programs/browserpass.nix
|
||||
|
@ -197,6 +198,7 @@ let
|
|||
./services/barrier.nix
|
||||
./services/betterlockscreen.nix
|
||||
./services/blueman-applet.nix
|
||||
./services/borgmatic.nix
|
||||
./services/caffeine.nix
|
||||
./services/cbatticon.nix
|
||||
./services/clipmenu.nix
|
||||
|
|
196
modules/programs/borgmatic.nix
Normal file
196
modules/programs/borgmatic.nix
Normal file
|
@ -0,0 +1,196 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.programs.borgmatic;
|
||||
|
||||
mkNullableOption = args:
|
||||
lib.mkOption (args // {
|
||||
type = lib.types.nullOr args.type;
|
||||
default = null;
|
||||
});
|
||||
|
||||
mkRetentionOption = frequency:
|
||||
mkNullableOption {
|
||||
type = types.int;
|
||||
description =
|
||||
"Number of ${frequency} archives to keep. Use -1 for no limit.";
|
||||
example = 3;
|
||||
};
|
||||
|
||||
extraConfigOption = mkOption {
|
||||
type = with types; attrsOf (oneOf [ str bool path int ]);
|
||||
default = { };
|
||||
description = "Extra settings.";
|
||||
};
|
||||
|
||||
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";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
configModule = types.submodule {
|
||||
options = {
|
||||
location = {
|
||||
sourceDirectories = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = "Directories to backup.";
|
||||
example = literalExpression "[config.home.homeDirectory]";
|
||||
};
|
||||
|
||||
repositories = mkOption {
|
||||
type = types.listOf types.str;
|
||||
description = "Paths to repositories.";
|
||||
example =
|
||||
literalExpression ''["ssh://myuser@myrepo.myserver.com/./repo"]'';
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
removeNullValues = attrSet: filterAttrs (key: value: value != null) attrSet;
|
||||
|
||||
writeConfig = config:
|
||||
generators.toYAML { } {
|
||||
location = removeNullValues {
|
||||
source_directories = config.location.sourceDirectories;
|
||||
repositories = config.location.repositories;
|
||||
} // config.location.extraConfig;
|
||||
storage = removeNullValues {
|
||||
encryption_passcommand = config.storage.encryptionPasscommand;
|
||||
} // config.storage.extraConfig;
|
||||
retention = removeNullValues {
|
||||
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;
|
||||
} // config.retention.extraConfig;
|
||||
consistency = removeNullValues { checks = config.consistency.checks; }
|
||||
// config.consistency.extraConfig;
|
||||
};
|
||||
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)
|
||||
];
|
||||
|
||||
xdg.configFile = with lib.attrsets;
|
||||
mapAttrs' (configName: config:
|
||||
nameValuePair ("borgmatic.d/" + configName + ".yaml") {
|
||||
text = writeConfig config;
|
||||
}) cfg.backups;
|
||||
|
||||
home.packages = [ cfg.package ];
|
||||
};
|
||||
}
|
87
modules/services/borgmatic.nix
Normal file
87
modules/services/borgmatic.nix
Normal file
|
@ -0,0 +1,87 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
serviceConfig = config.services.borgmatic;
|
||||
programConfig = config.programs.borgmatic;
|
||||
in {
|
||||
meta.maintainers = [ maintainers.DamienCassou ];
|
||||
|
||||
options = {
|
||||
services.borgmatic = {
|
||||
enable = mkEnableOption "Borgmatic service";
|
||||
|
||||
frequency = mkOption {
|
||||
type = types.str;
|
||||
default = "hourly";
|
||||
description = ''
|
||||
How often to run borgmatic when
|
||||
<code language="nix">services.borgmatic.enable = true</code>.
|
||||
This value is passed to the systemd timer configuration as
|
||||
the onCalendar option. See
|
||||
<citerefentry>
|
||||
<refentrytitle>systemd.time</refentrytitle>
|
||||
<manvolnum>7</manvolnum>
|
||||
</citerefentry>
|
||||
for more information about the format.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf serviceConfig.enable {
|
||||
assertions = [
|
||||
(lib.hm.assertions.assertPlatform "services.borgmatic" pkgs
|
||||
lib.platforms.linux)
|
||||
];
|
||||
|
||||
systemd.user = {
|
||||
services.borgmatic = {
|
||||
Unit = {
|
||||
Description = "borgmatic backup";
|
||||
# Prevent borgmatic from running unless the machine is
|
||||
# plugged into power:
|
||||
ConditionACPower = true;
|
||||
};
|
||||
Service = {
|
||||
Type = "oneshot";
|
||||
|
||||
# Lower CPU and I/O priority:
|
||||
Nice = 19;
|
||||
CPUSchedulingPolicy = "batch";
|
||||
IOSchedulingClass = "best-effort";
|
||||
IOSchedulingPriority = 7;
|
||||
IOWeight = 100;
|
||||
|
||||
Restart = "no";
|
||||
LogRateLimitIntervalSec = 0;
|
||||
|
||||
# Delay start to prevent backups running during boot:
|
||||
ExecStartPre = "sleep 3m";
|
||||
|
||||
ExecStart = ''
|
||||
${pkgs.systemd}/bin/systemd-inhibit \
|
||||
--who="borgmatic" \
|
||||
--why="Prevent interrupting scheduled backup" \
|
||||
${programConfig.package}/bin/borgmatic \
|
||||
--stats \
|
||||
--verbosity -1 \
|
||||
--list \
|
||||
--syslog-verbosity 1
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
timers.borgmatic = {
|
||||
Unit.Description = "Run borgmatic backup";
|
||||
Timer = {
|
||||
OnCalendar = serviceConfig.frequency;
|
||||
Persistent = true;
|
||||
RandomizedDelaySec = "10m";
|
||||
};
|
||||
Install.WantedBy = [ "timers.target" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -141,6 +141,7 @@ import nmt {
|
|||
./modules/misc/xsession
|
||||
./modules/programs/abook
|
||||
./modules/programs/autorandr
|
||||
./modules/programs/borgmatic
|
||||
./modules/programs/firefox
|
||||
./modules/programs/foot
|
||||
./modules/programs/getmail
|
||||
|
@ -160,6 +161,7 @@ import nmt {
|
|||
./modules/programs/xmobar
|
||||
./modules/programs/yt-dlp
|
||||
./modules/services/barrier
|
||||
./modules/services/borgmatic
|
||||
./modules/services/devilspie2
|
||||
./modules/services/dropbox
|
||||
./modules/services/emacs
|
||||
|
|
110
tests/modules/programs/borgmatic/basic-configuration.nix
Normal file
110
tests/modules/programs/borgmatic/basic-configuration.nix
Normal file
|
@ -0,0 +1,110 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
let
|
||||
boolToString = bool: if bool then "true" else "false";
|
||||
backups = config.programs.borgmatic.backups;
|
||||
in {
|
||||
config = {
|
||||
programs.borgmatic = {
|
||||
enable = true;
|
||||
backups = {
|
||||
main = {
|
||||
location = {
|
||||
sourceDirectories = [ "/my-stuff-to-backup" ];
|
||||
repositories = [ "/mnt/disk1" "/mnt/disk2" ];
|
||||
extraConfig = { one_file_system = true; };
|
||||
};
|
||||
|
||||
storage = {
|
||||
encryptionPasscommand = "fetch-the-password.sh";
|
||||
extraConfig = { checkpoint_interval = 200; };
|
||||
};
|
||||
|
||||
retention = {
|
||||
keepWithin = "14d";
|
||||
keepSecondly = 12;
|
||||
extraConfig = { prefix = "hostname"; };
|
||||
};
|
||||
|
||||
consistency = {
|
||||
checks = [
|
||||
{
|
||||
name = "repository";
|
||||
frequency = "2 weeks";
|
||||
}
|
||||
{
|
||||
name = "archives";
|
||||
frequency = "4 weeks";
|
||||
}
|
||||
];
|
||||
|
||||
extraConfig = { prefix = "hostname"; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
test.stubs.borgmatic = { };
|
||||
|
||||
nmt.script = ''
|
||||
config_file=$TESTED/home-files/.config/borgmatic.d/main.yaml
|
||||
assertFileExists $config_file
|
||||
|
||||
declare -A expectations
|
||||
|
||||
expectations[location.source_directories[0]]="${
|
||||
builtins.elemAt backups.main.location.sourceDirectories 0
|
||||
}"
|
||||
expectations[location.repositories[0]]="${
|
||||
builtins.elemAt backups.main.location.repositories 0
|
||||
}"
|
||||
expectations[location.repositories[1]]="${
|
||||
builtins.elemAt backups.main.location.repositories 1
|
||||
}"
|
||||
expectations[location.one_file_system]="${
|
||||
boolToString backups.main.location.extraConfig.one_file_system
|
||||
}"
|
||||
|
||||
expectations[storage.encryption_passcommand]="${backups.main.storage.encryptionPasscommand}"
|
||||
expectations[storage.checkpoint_interval]="${
|
||||
toString backups.main.storage.extraConfig.checkpoint_interval
|
||||
}"
|
||||
|
||||
expectations[retention.keep_within]="${backups.main.retention.keepWithin}"
|
||||
expectations[retention.keep_secondly]="${
|
||||
toString backups.main.retention.keepSecondly
|
||||
}"
|
||||
expectations[retention.prefix]="${backups.main.retention.extraConfig.prefix}"
|
||||
|
||||
expectations[consistency.checks[0].name]="${
|
||||
(builtins.elemAt backups.main.consistency.checks 0).name
|
||||
}"
|
||||
expectations[consistency.checks[0].frequency]="${
|
||||
(builtins.elemAt backups.main.consistency.checks 0).frequency
|
||||
}"
|
||||
expectations[consistency.checks[1].name]="${
|
||||
(builtins.elemAt backups.main.consistency.checks 1).name
|
||||
}"
|
||||
expectations[consistency.checks[1].frequency]="${
|
||||
(builtins.elemAt backups.main.consistency.checks 1).frequency
|
||||
}"
|
||||
expectations[consistency.prefix]="${backups.main.consistency.extraConfig.prefix}"
|
||||
|
||||
yq=${pkgs.yq-go}/bin/yq
|
||||
|
||||
for filter in "''${!expectations[@]}"; do
|
||||
expected_value="''${expectations[$filter]}"
|
||||
actual_value="$($yq ".$filter" $config_file)"
|
||||
|
||||
if [[ "$actual_value" != "$expected_value" ]]; then
|
||||
fail "Expected '$filter' to be '$expected_value' but was '$actual_value'"
|
||||
fi
|
||||
done
|
||||
|
||||
one_file_system=$($yq ".location.one_file_system" $config_file)
|
||||
if [[ $one_file_system != "true" ]]; then
|
||||
fail "Expected one_file_system to be true but it was $one_file_system"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
}
|
1
tests/modules/programs/borgmatic/default.nix
Normal file
1
tests/modules/programs/borgmatic/default.nix
Normal file
|
@ -0,0 +1 @@
|
|||
{ borgmatic-program-basic-configuration = ./basic-configuration.nix; }
|
22
tests/modules/services/borgmatic/basic-configuration.nix
Normal file
22
tests/modules/services/borgmatic/basic-configuration.nix
Normal file
|
@ -0,0 +1,22 @@
|
|||
{ config, pkgs, ... }:
|
||||
|
||||
{
|
||||
config = {
|
||||
services.borgmatic = {
|
||||
enable = true;
|
||||
frequency = "weekly";
|
||||
};
|
||||
|
||||
test.stubs.borgmatic = { };
|
||||
|
||||
nmt.script = ''
|
||||
assertFileContent \
|
||||
$(normalizeStorePaths home-files/.config/systemd/user/borgmatic.service) \
|
||||
${./basic-configuration.service}
|
||||
|
||||
assertFileContent \
|
||||
home-files/.config/systemd/user/borgmatic.timer \
|
||||
${./basic-configuration.timer}
|
||||
'';
|
||||
};
|
||||
}
|
23
tests/modules/services/borgmatic/basic-configuration.service
Normal file
23
tests/modules/services/borgmatic/basic-configuration.service
Normal file
|
@ -0,0 +1,23 @@
|
|||
[Service]
|
||||
CPUSchedulingPolicy=batch
|
||||
ExecStart=/nix/store/00000000000000000000000000000000-systemd/bin/systemd-inhibit \
|
||||
--who="borgmatic" \
|
||||
--why="Prevent interrupting scheduled backup" \
|
||||
@borgmatic@/bin/borgmatic \
|
||||
--stats \
|
||||
--verbosity -1 \
|
||||
--list \
|
||||
--syslog-verbosity 1
|
||||
|
||||
ExecStartPre=sleep 3m
|
||||
IOSchedulingClass=best-effort
|
||||
IOSchedulingPriority=7
|
||||
IOWeight=100
|
||||
LogRateLimitIntervalSec=0
|
||||
Nice=19
|
||||
Restart=no
|
||||
Type=oneshot
|
||||
|
||||
[Unit]
|
||||
ConditionACPower=true
|
||||
Description=borgmatic backup
|
10
tests/modules/services/borgmatic/basic-configuration.timer
Normal file
10
tests/modules/services/borgmatic/basic-configuration.timer
Normal file
|
@ -0,0 +1,10 @@
|
|||
[Install]
|
||||
WantedBy=timers.target
|
||||
|
||||
[Timer]
|
||||
OnCalendar=weekly
|
||||
Persistent=true
|
||||
RandomizedDelaySec=10m
|
||||
|
||||
[Unit]
|
||||
Description=Run borgmatic backup
|
1
tests/modules/services/borgmatic/default.nix
Normal file
1
tests/modules/services/borgmatic/default.nix
Normal file
|
@ -0,0 +1 @@
|
|||
{ borgmatic-service-basic-configuration = ./basic-configuration.nix; }
|
Loading…
Reference in a new issue