From 18e30ed70c1cd975cd4dfc2bcb3d5cb6481e402e Mon Sep 17 00:00:00 2001 From: Gabriel Arazas Date: Mon, 23 Sep 2024 12:54:20 +0800 Subject: [PATCH] home-manager/services/borgbackup: init Pretty much just a ported version of NixOS' BorgBackup service module. --- modules/home-manager/default.nix | 1 + modules/home-manager/services/borgbackup.nix | 249 +++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 modules/home-manager/services/borgbackup.nix diff --git a/modules/home-manager/default.nix b/modules/home-manager/default.nix index 8de58c65..614641f8 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/borgbackup.nix ./services/borgmatic.nix ./services/bleachbit.nix ./services/distant.nix diff --git a/modules/home-manager/services/borgbackup.nix b/modules/home-manager/services/borgbackup.nix new file mode 100644 index 00000000..21d98a6a --- /dev/null +++ b/modules/home-manager/services/borgbackup.nix @@ -0,0 +1,249 @@ +# A home-manager port of NixOS' `services.borgbackup` module. I tried to make +# it as close to it as possible plus some other options such as adding pattern +# files (since it is my preference of indicating which files are included). +{ config, lib, pkgs, ... }: + +let + cfg = config.services.borgbackup; + + borgJobsModule = { name, lib, config, ... }: { + options = { + exportWrapperScript = lib.mkEnableOption "export wrapper script as part of the environment" // { + default = true; + }; + + extraArgs = lib.mkOption { + type = with lib.types; listOf str; + description = '' + Extra arguments to be passed to all Borg procedures in the resulting + script. + + ::: {.caution} + Be careful with this option as it can affect all commands. See the + `extraArgs` equivalent of those specific operations first because + adding values here. + ::: + ''; + default = [ ]; + example = [ + "--remote-path=/path/to/borg/repo" + ]; + }; + + extraCreateArgs = lib.mkOption { + type = with lib.types; listOf str; + description = '' + Additional arguments for `borg create`. + ''; + default = [ ]; + example = [ + "--stats" + "--checkpoint-interval" "600" + ]; + }; + + extraInitArgs = lib.mkOption { + type = with lib.types; listOf str; + description = '' + Extra arguments to be passed to `borg init`, when applicable. + ''; + default = [ ]; + example = [ + "--make-parent-dirs" + "--append-only" + ]; + }; + + patternFiles = lib.mkOption { + type = with lib.types; listOf path; + description = '' + List of paths containing patterns for the Borg job. + ''; + default = [ ]; + example = lib.literalExpression '' + [ + ./config/borg/patterns/home + ./config/borg/patterns/server + ./config/borg/patterns/games + ] + ''; + }; + + doInit = lib.mkEnableOption "initialization of the BorgBackup repo"; + + startAt = lib.mkOption { + type = lib.types.nonEmptyStr; + description = '' + Indicates how much the backup procedure occurs. + ''; + default = "daily"; + example = "04:30"; + }; + + environment = lib.mkOption { + type = with lib.types; attrsOf str; + description = '' + Extra environment variables to be set along to the backup service. + You could indicate SSH-related settings here for example. + ''; + default = { }; + example = lib.literalExpression '' + { + BORG_RSH = "ssh -i ''${config.home.homeDirectory}/.ssh/borg-key.pub"; + } + ''; + }; + + encryption.passCommand = lib.mkOption { + type = with lib.types; nullOr str; + description = '' + Command used to retrieve the password of the repository. + + ::: {.note} + Mutually exclusive with {option}`encryption.passphrase`. + ::: + ''; + default = null; + example = lib.literalExpression '' + cat ''${config.home.homeDirectory}/borg-secret + ''; + }; + + encryption.passphrase = lib.mkOption { + type = with lib.types; nullOr str; + description = '' + Passphrase used to lock the repository. + + ::: {.note} + This will also store the password as plain-text file in the Nix store + directory. If you don't want that, use + {option}`encryption.passCommand` instead. + + Mutually exclusive with {option}`encryption.passCommand`. + ::: + ''; + default = null; + }; + }; + }; + + mkPassEnv = v: + # Prefer the pass command option since it is the safer option. + if v.encryption.passCommand != null + then { BORG_PASSCOMMAND = v.encryption.passCommand; } + else if v.encryption.passphrase != null + then { BORG_PASSPHRASE = v.encryption.passphrase; } + else { }; + makeJobName = name: "borg-job-${name}"; + + mkBorgWrapperScripts = n: v: + let + executableName = makeJobName n; + setEnv = { BORG_REPO = v.repo; } // (mkPassEnv v) // v.environment; + mkWrapperFlag = n: v: + ''--set ${lib.escapeShellArg n} "${v}"''; + in + pkgs.runCommand "${n}-wrapper" { + nativeBuildInputs = [ pkgs.makeWrapper ]; + } '' + makeWrapper "${lib.getExe' cfg.package "borg"} "$out/bin/${executableName}" \ + ${lib.concatStringsSep " \\\n" (lib.mapAttrsToList mkWrapperFlag setEnv)} + ''; + + mkBorgServiceUnit = n: v: + lib.nameValuePair (makeJobName n) { + Unit = { + Description = "Periodic BorgBackup job '${n}'"; + }; + + Service = { + CPUSchedulingPolicy = "idle"; + IOSchedulingClass = "idle"; + Environment = + lib.attrsToList (n: v: "${n}=${v}") ( + { + inherit (v) extraArgs extraInitArgs extraCreateArgs; + } + // v.environment // (mkPassEnv v) + ) + ++ [ + "BORG_REPO=${v.repo}" + ]; + + ExecStart = + let + borgScript = pkgs.writeShellApplication { + name = "borg-job-${n}-script"; + runtimeInputs = [ cfg.package ]; + text = '' + on_exit() { + } + trap on_exit EXIT + ''; + }; + in + lib.getExe borgScript; + }; + }; + + mkBorgTimerUnit = n: v: + lib.nameValuePair (makeJobName n) { + Unit.Description = "Periodic BorgBackup job '${n}'"; + + Timer = { + Persistent = true; + RandomizedDelaySec = "1min"; + OnCalendar = v.startAt; + }; + + Install.WantedBy = [ "timers.target" ]; + }; +in +{ + options.services.borgbackup = { + enable = lib.mkEnableOption "periodic backups with BorgBackup"; + + package = lib.mkPackageOption pkgs "borgbackup" { }; + + jobs = lib.mkOption { + type = with lib.types; attrsOf (submodule borgJobsModule); + description = '' + A set of Borg backup jobs to be done within the home environment. + + Each job can have a wrapper script `borg-job-{name}` as part of your + home environment to make maintenance easier. + ''; + default = { }; + example = lib.literalExpression '' + { + personal = { + doInit = true; + encryption = { + mode = "repokey"; + passCommand = "cat ''${config.xdg.configHome}/backup/secret"; + }; + patternFiles = [ + ./config/borg/patterns/data + ./config/borg/patterns/games + ]; + startAt = "05:30; + }; + } + ''; + }; + }; + + config = lib.mkIf cfg.enable { + home.packages = + let + jobs' = lib.filterAttrs (n: v: v.exportWrapperScript) cfg.jobs; + in + lib.mapAttrsToList mkBorgWrapperScripts jobs'; + + systemd.user.services = + lib.mapAttrs' mkBorgServiceUnit cfg.jobs; + + systemd.user.timers = + lib.mapAttrs' mkBorgTimerUnit cfg.jobs; + }; +}