diff --git a/modules/home-manager/default.nix b/modules/home-manager/default.nix index f5f609a7..8de58c65 100644 --- a/modules/home-manager/default.nix +++ b/modules/home-manager/default.nix @@ -7,6 +7,7 @@ ./programs/zed-editor.nix ./programs/borgmatic.nix ./services/archivebox.nix + ./services/borgmatic.nix ./services/bleachbit.nix ./services/distant.nix ./services/gallery-dl.nix diff --git a/modules/home-manager/services/borgmatic.nix b/modules/home-manager/services/borgmatic.nix new file mode 100644 index 00000000..a5145e6d --- /dev/null +++ b/modules/home-manager/services/borgmatic.nix @@ -0,0 +1,214 @@ +# A re-implementation of the Borgmatic service home-manager module. The +# reimplementation basically separates all of the configurations instead of a oneshot where it will execute Borgmatic with all present configurations +# (which is fine but too overwhelming for my taste). +# +# It has an added integration for individual Borgmatic configurations from +# `programs.borgmatic.backups` (also a reimplemented version from the upstream) +# to be added to the jobset and has more control over each service unit. +# +# As a design constraint, you should still be able to do what the upstream +# service module with a little bit of elbow grease. +{ config, lib, pkgs, ... }: + +let + cfg = config.services.borgmatic; + programCfg = config.programs.borgmatic; + settingsFormat = pkgs.formats.yaml { }; + + borgmaticProgramModule = { name, lib, ... }: { + options = { + initService = { + enable = lib.mkEnableOption "include this particular backup as part of Borgmatic jobset at {option}`services.borgmatic.jobs`"; + + startAt = lib.mkOption { + type = lib.types.nonEmptyStr; + description = '' + Indicates how often the associated service occurs. Accepts value as + found from {manpage}`systemd.time(7)`. + ''; + default = "daily"; + example = "04:30"; + }; + }; + }; + }; + + borgmaticJobModule = { config, lib, name, ... }: let + settingsFile = settingsFormat.generate "borgmatic-job-config-${name}" config.settings; + in { + options = { + settings = lib.mkOption { + type = settingsFormat.type; + description = '' + Configuration settings associated with the job. If this is set, the + generated output is added as an additional argument (i.e., `--config + SETTINGSFILE`) in the service script. + ''; + default = { }; + example = { + }; + }; + + startAt = lib.mkOption { + type = lib.types.nonEmptyStr; + description = '' + Indicates how often backup will occur. This is to be used as value + for `Timer.OnCalendar=` in the systemd unit. See + {manpage}`systemd.time(7)` for more details. + ''; + default = "daily"; + example = "04:30"; + }; + + extraArgs = lib.mkOption { + type = with lib.types; listOf str; + description = '' + List of arguments to be passed to the Borgmatic backup service. + ''; + default = [ ]; + example = lib.literalExpression '' + [ + "--stats" + "--verbosity" "1" + "--syslog-verbosity" "1" + "--list" + ] + ''; + }; + }; + + config = { + extraArgs = lib.mkMerge [ + cfg.extraArgs + + (lib.optionals (config.settings != {}) ( + lib.mkBefore [ + "--config" settingsFile + ] + )) + ]; + }; + }; + + formatUnitName = name: "borgmatic-job-${name}"; + mkBorgmaticServiceUnit = n: v: + lib.nameValuePair (formatUnitName n) { + Unit = { + Description = "Borgmatic backup job '${n}'"; + Documentation = [ + "https://torsion.org/borgmatic/docs/reference/configuration" + ]; + ConditionACPower = true; + }; + + Service = { + # TODO: Just cargo-culted from the upstream home-manager module. Will + # need more info on this. + Nice = 19; + CPUSchedulingPolicy = "batch"; + IOSchedulingClass = "best-effort"; + IOSchedulingPriority = 7; + IOWeight = 100; + + Restart = "on-failure"; + ExecStart = '' + ${lib.getExe' cfg.package "borgmatic"} ${lib.concatStringsSep " " v.extraArgs} + ''; + + PrivateTmp = true; + }; + }; + + mkBorgmaticTimerUnit = n: v: + lib.nameValuePair (formatUnitName n) { + Unit.Description = "Borgmatic backup job '${n}'"; + + Timer = { + OnCalendar = v.startAt; + Persistent = lib.mkDefault true; + RandomizedDelaySec = lib.mkDefault "10m"; + }; + + Install.WantedBy = [ "timers.target" ]; + }; + + mkBorgmaticServiceFromConfig = n: v: + lib.nameValuePair "borgmatic-config-${n}" { + inherit (v.initService) startAt; + extraArgs = [ + "--config" "${config.xdg.configHome}/borgmatic.d/${n}" + ]; + }; +in +{ + disabledModules = [ "services/borgmatic.nix" ]; + options.programs.borgmatic.backups = lib.mkOption { + type = with lib.types; attrsOf (submodule borgmaticProgramModule); + }; + + options.services.borgmatic = { + package = lib.mkPackageOption pkgs "borgmatic" { }; + + extraArgs = lib.mkOption { + type = with lib.types; listOf str; + description = '' + Global list of additional arguments for all of the jobs. + ''; + default = [ ]; + example = [ + "--stats" + "--verbosity" "1" + ]; + }; + + jobs = lib.mkOption { + type = with lib.types; attrsOf (submodule borgmaticJobModule); + default = { }; + example = lib.literalExpression '' + { + personal = { + startAt = "05:30"; + settings = { + source_directories = [ + "''${config.xdg.configHome}" + "''${config.xdg.userDirs.extraConfig.XDG_PROJECTS_DIR}" + "''${config.home.homeDirectory}/.thunderbird" + "''${config.home.homeDirectory}/Zotero" + ]; + + repositories = [ + { + path = "ssh://k8pDxu32@k8pDxu32.repo.borgbase.com/./repo"; + label = "borgbase"; + } + + { + path = "/var/lib/backups/local.borg"; + label = "local"; + } + ]; + + keep_daily = 7; + keep_weekly = 4; + keep_monthly = 6; + }; + }; + } + ''; + }; + }; + + config = { + systemd.user.services = + lib.mapAttrs' mkBorgmaticServiceUnit cfg.jobs; + + systemd.user.timers = + lib.mapAttrs' mkBorgmaticTimerUnit cfg.jobs; + + services.borgmatic.jobs = + let + validService = lib.filterAttrs (n: v: v.initService.enable) programCfg.backups; + in + lib.mapAttrs' mkBorgmaticServiceFromConfig validService; + }; +} diff --git a/tests/modules/home-manager/default.nix b/tests/modules/home-manager/default.nix index 371a1808..6c15faa3 100644 --- a/tests/modules/home-manager/default.nix +++ b/tests/modules/home-manager/default.nix @@ -61,6 +61,7 @@ import nmt { ] ++ lib.optionals isLinux [ ./services/archivebox + #./services/borgmatic ./services/bleachbit ./services/gallery-dl ./services/gonic diff --git a/tests/modules/home-manager/services/borgmatic/basic.nix b/tests/modules/home-manager/services/borgmatic/basic.nix new file mode 100644 index 00000000..1a36ade0 --- /dev/null +++ b/tests/modules/home-manager/services/borgmatic/basic.nix @@ -0,0 +1,16 @@ +{ config, lib, pkgs, ... }: + +{ + services.borgmatic.jobs.personal = { + settings = { + hello = "WORLD"; + }; + }; + + test.stubs.borgmatic = { }; + + nmt.script = '' + assertFileExists home-files/.config/systemd/user/borgmatic-job-personal.service + assertFileExists home-files/.config/systemd/user/borgmatic-job-personal.timer + ''; +} diff --git a/tests/modules/home-manager/services/borgmatic/default.nix b/tests/modules/home-manager/services/borgmatic/default.nix new file mode 100644 index 00000000..18bc62d4 --- /dev/null +++ b/tests/modules/home-manager/services/borgmatic/default.nix @@ -0,0 +1,4 @@ +{ + borgmatic-service-basic = ./basic.nix; + borgmatic-service-with-program-config = ./with-program-config.nix; +} diff --git a/tests/modules/home-manager/services/borgmatic/with-program-config.nix b/tests/modules/home-manager/services/borgmatic/with-program-config.nix new file mode 100644 index 00000000..8943b4ce --- /dev/null +++ b/tests/modules/home-manager/services/borgmatic/with-program-config.nix @@ -0,0 +1,18 @@ +{ config, lib, pkgs, ... }: + +{ + programs.borgmatic.enable = true; + + programs.borgmatic.backups.personal = { + settings.hello = "WORLD"; + initService.enable = true; + }; + + test.stubs.borgmatic = { }; + + nmt.script = '' + assertFileExists home-files/.config/borgmatic.d/personal.yaml + assertFileExists home-files/.config/systemd/user/borgmatic-job-borgmatic-config-personal.service + assertFileExists home-files/.config/systemd/user/borgmatic-job-borgmatic-config-personal.timer + ''; +}