When having backup jobs that persist to a removable device like an external HDD, the directory shouldn't be created by an activation script as this might confuse auto-mounting tools such as udiskie(8). In this case the job will simply fail, with the former approach udiskie ran into some issues as the path `/run/media/ma27/backup` was already there and owned by root.
		
			
				
	
	
		
			629 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
			
		
		
	
	
			629 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Nix
		
	
	
	
	
	
{ config, lib, pkgs, ... }:
 | 
						|
 | 
						|
with lib;
 | 
						|
 | 
						|
let
 | 
						|
 | 
						|
  isLocalPath = x:
 | 
						|
    builtins.substring 0 1 x == "/"      # absolute path
 | 
						|
    || builtins.substring 0 1 x == "."   # relative path
 | 
						|
    || builtins.match "[.*:.*]" == null; # not machine:path
 | 
						|
 | 
						|
  mkExcludeFile = cfg:
 | 
						|
    # Write each exclude pattern to a new line
 | 
						|
    pkgs.writeText "excludefile" (concatStringsSep "\n" cfg.exclude);
 | 
						|
 | 
						|
  mkKeepArgs = cfg:
 | 
						|
    # If cfg.prune.keep e.g. has a yearly attribute,
 | 
						|
    # its content is passed on as --keep-yearly
 | 
						|
    concatStringsSep " "
 | 
						|
      (mapAttrsToList (x: y: "--keep-${x}=${toString y}") cfg.prune.keep);
 | 
						|
 | 
						|
  mkBackupScript = cfg: ''
 | 
						|
    on_exit()
 | 
						|
    {
 | 
						|
      exitStatus=$?
 | 
						|
      # Reset the EXIT handler, or else we're called again on 'exit' below
 | 
						|
      trap - EXIT
 | 
						|
      ${cfg.postHook}
 | 
						|
      exit $exitStatus
 | 
						|
    }
 | 
						|
    trap 'on_exit' INT TERM QUIT EXIT
 | 
						|
 | 
						|
    archiveName="${cfg.archiveBaseName}-$(date ${cfg.dateFormat})"
 | 
						|
    archiveSuffix="${optionalString cfg.appendFailedSuffix ".failed"}"
 | 
						|
    ${cfg.preHook}
 | 
						|
  '' + optionalString cfg.doInit ''
 | 
						|
    # Run borg init if the repo doesn't exist yet
 | 
						|
    if ! borg list $extraArgs > /dev/null; then
 | 
						|
      borg init $extraArgs \
 | 
						|
        --encryption ${cfg.encryption.mode} \
 | 
						|
        $extraInitArgs
 | 
						|
      ${cfg.postInit}
 | 
						|
    fi
 | 
						|
  '' + ''
 | 
						|
    borg create $extraArgs \
 | 
						|
      --compression ${cfg.compression} \
 | 
						|
      --exclude-from ${mkExcludeFile cfg} \
 | 
						|
      $extraCreateArgs \
 | 
						|
      "::$archiveName$archiveSuffix" \
 | 
						|
      ${escapeShellArgs cfg.paths}
 | 
						|
  '' + optionalString cfg.appendFailedSuffix ''
 | 
						|
    borg rename $extraArgs \
 | 
						|
      "::$archiveName$archiveSuffix" "$archiveName"
 | 
						|
  '' + ''
 | 
						|
    ${cfg.postCreate}
 | 
						|
  '' + optionalString (cfg.prune.keep != { }) ''
 | 
						|
    borg prune $extraArgs \
 | 
						|
      ${mkKeepArgs cfg} \
 | 
						|
      --prefix ${escapeShellArg cfg.prune.prefix} \
 | 
						|
      $extraPruneArgs
 | 
						|
    ${cfg.postPrune}
 | 
						|
  '';
 | 
						|
 | 
						|
  mkPassEnv = cfg: with cfg.encryption;
 | 
						|
    if passCommand != null then
 | 
						|
      { BORG_PASSCOMMAND = passCommand; }
 | 
						|
    else if passphrase != null then
 | 
						|
      { BORG_PASSPHRASE = passphrase; }
 | 
						|
    else { };
 | 
						|
 | 
						|
  mkBackupService = name: cfg: 
 | 
						|
    let
 | 
						|
      userHome = config.users.users.${cfg.user}.home;
 | 
						|
    in nameValuePair "borgbackup-job-${name}" {
 | 
						|
      description = "BorgBackup job ${name}";
 | 
						|
      path = with pkgs; [
 | 
						|
        borgbackup openssh
 | 
						|
      ];
 | 
						|
      script = mkBackupScript cfg;
 | 
						|
      serviceConfig = {
 | 
						|
        User = cfg.user;
 | 
						|
        Group = cfg.group;
 | 
						|
        # Only run when no other process is using CPU or disk
 | 
						|
        CPUSchedulingPolicy = "idle";
 | 
						|
        IOSchedulingClass = "idle";
 | 
						|
        ProtectSystem = "strict";
 | 
						|
        ReadWritePaths =
 | 
						|
          [ "${userHome}/.config/borg" "${userHome}/.cache/borg" ]
 | 
						|
          ++ cfg.readWritePaths
 | 
						|
          # Borg needs write access to repo if it is not remote
 | 
						|
          ++ optional (isLocalPath cfg.repo) cfg.repo;
 | 
						|
        PrivateTmp = cfg.privateTmp;
 | 
						|
      };
 | 
						|
      environment = {
 | 
						|
        BORG_REPO = cfg.repo;
 | 
						|
        inherit (cfg) extraArgs extraInitArgs extraCreateArgs extraPruneArgs;
 | 
						|
      } // (mkPassEnv cfg) // cfg.environment;
 | 
						|
      inherit (cfg) startAt;
 | 
						|
    };
 | 
						|
 | 
						|
  # Paths listed in ReadWritePaths must exist before service is started
 | 
						|
  mkActivationScript = name: cfg:
 | 
						|
    let
 | 
						|
      install = "install -o ${cfg.user} -g ${cfg.group}";
 | 
						|
    in
 | 
						|
      nameValuePair "borgbackup-job-${name}" (stringAfter [ "users" ] (''
 | 
						|
        # Ensure that the home directory already exists
 | 
						|
        # We can't assert createHome == true because that's not the case for root
 | 
						|
        cd "${config.users.users.${cfg.user}.home}"
 | 
						|
        ${install} -d .config/borg
 | 
						|
        ${install} -d .cache/borg
 | 
						|
      '' + optionalString (isLocalPath cfg.repo && !cfg.removableDevice) ''
 | 
						|
        ${install} -d ${escapeShellArg cfg.repo}
 | 
						|
      ''));
 | 
						|
 | 
						|
  mkPassAssertion = name: cfg: {
 | 
						|
    assertion = with cfg.encryption;
 | 
						|
      mode != "none" -> passCommand != null || passphrase != null;
 | 
						|
    message =
 | 
						|
      "passCommand or passphrase has to be specified because"
 | 
						|
      + '' borgbackup.jobs.${name}.encryption != "none"'';
 | 
						|
  };
 | 
						|
 | 
						|
  mkRepoService = name: cfg:
 | 
						|
    nameValuePair "borgbackup-repo-${name}" {
 | 
						|
      description = "Create BorgBackup repository ${name} directory";
 | 
						|
      script = ''
 | 
						|
        mkdir -p ${escapeShellArg cfg.path}
 | 
						|
        chown ${cfg.user}:${cfg.group} ${escapeShellArg cfg.path}
 | 
						|
      '';
 | 
						|
      serviceConfig = {
 | 
						|
        # The service's only task is to ensure that the specified path exists
 | 
						|
        Type = "oneshot";
 | 
						|
      };
 | 
						|
      wantedBy = [ "multi-user.target" ];
 | 
						|
    };
 | 
						|
 | 
						|
  mkAuthorizedKey = cfg: appendOnly: key:
 | 
						|
    let
 | 
						|
      # Because of the following line, clients do not need to specify an absolute repo path
 | 
						|
      cdCommand = "cd ${escapeShellArg cfg.path}";
 | 
						|
      restrictedArg = "--restrict-to-${if cfg.allowSubRepos then "path" else "repository"} .";
 | 
						|
      appendOnlyArg = optionalString appendOnly "--append-only";
 | 
						|
      quotaArg = optionalString (cfg.quota != null) "--storage-quota ${cfg.quota}";
 | 
						|
      serveCommand = "borg serve ${restrictedArg} ${appendOnlyArg} ${quotaArg}";
 | 
						|
    in
 | 
						|
      ''command="${cdCommand} && ${serveCommand}",restrict ${key}'';
 | 
						|
 | 
						|
  mkUsersConfig = name: cfg: {
 | 
						|
    users.${cfg.user} = {
 | 
						|
      openssh.authorizedKeys.keys =
 | 
						|
        (map (mkAuthorizedKey cfg false) cfg.authorizedKeys
 | 
						|
        ++ map (mkAuthorizedKey cfg true) cfg.authorizedKeysAppendOnly);
 | 
						|
      useDefaultShell = true;
 | 
						|
    };
 | 
						|
    groups.${cfg.group} = { };
 | 
						|
  };
 | 
						|
 | 
						|
  mkKeysAssertion = name: cfg: {
 | 
						|
    assertion = cfg.authorizedKeys != [ ] || cfg.authorizedKeysAppendOnly != [ ];
 | 
						|
    message =
 | 
						|
      "borgbackup.repos.${name} does not make sense"
 | 
						|
      + " without at least one public key";
 | 
						|
  };
 | 
						|
 | 
						|
  mkRemovableDeviceAssertions = name: cfg: {
 | 
						|
    assertion = !(isLocalPath cfg.repo) -> !cfg.removableDevice;
 | 
						|
    message = ''
 | 
						|
      borgbackup.repos.${name}: repo isn't a local path, thus it can't be a removable device!
 | 
						|
    '';
 | 
						|
  };
 | 
						|
 | 
						|
in {
 | 
						|
  meta.maintainers = with maintainers; [ dotlambda ];
 | 
						|
 | 
						|
  ###### interface
 | 
						|
 | 
						|
  options.services.borgbackup.jobs = mkOption {
 | 
						|
    description = "Deduplicating backups using BorgBackup.";
 | 
						|
    default = { };
 | 
						|
    example = literalExample ''
 | 
						|
      {
 | 
						|
        rootBackup = {
 | 
						|
          paths = "/";
 | 
						|
          exclude = [ "/nix" ];
 | 
						|
          repo = "/path/to/local/repo";
 | 
						|
          encryption = {
 | 
						|
            mode = "repokey";
 | 
						|
            passphrase = "secret";
 | 
						|
          };
 | 
						|
          compression = "auto,lzma";
 | 
						|
          startAt = "weekly";
 | 
						|
        };
 | 
						|
      }
 | 
						|
    '';
 | 
						|
    type = types.attrsOf (types.submodule (let globalConfig = config; in
 | 
						|
      { name, config, ... }: {
 | 
						|
        options = {
 | 
						|
 | 
						|
          paths = mkOption {
 | 
						|
            type = with types; coercedTo str lib.singleton (listOf str);
 | 
						|
            description = "Path(s) to back up.";
 | 
						|
            example = "/home/user";
 | 
						|
          };
 | 
						|
 | 
						|
          repo = mkOption {
 | 
						|
            type = types.str;
 | 
						|
            description = "Remote or local repository to back up to.";
 | 
						|
            example = "user@machine:/path/to/repo";
 | 
						|
          };
 | 
						|
 | 
						|
          removableDevice = mkOption {
 | 
						|
            type = types.bool;
 | 
						|
            default = false;
 | 
						|
            description = "Whether the repo (which must be local) is a removable device.";
 | 
						|
          };
 | 
						|
 | 
						|
          archiveBaseName = mkOption {
 | 
						|
            type = types.strMatching "[^/{}]+";
 | 
						|
            default = "${globalConfig.networking.hostName}-${name}";
 | 
						|
            defaultText = "\${config.networking.hostName}-<name>";
 | 
						|
            description = ''
 | 
						|
              How to name the created archives. A timestamp, whose format is
 | 
						|
              determined by <option>dateFormat</option>, will be appended. The full
 | 
						|
              name can be modified at runtime (<literal>$archiveName</literal>).
 | 
						|
              Placeholders like <literal>{hostname}</literal> must not be used.
 | 
						|
            '';
 | 
						|
          };
 | 
						|
 | 
						|
          dateFormat = mkOption {
 | 
						|
            type = types.str;
 | 
						|
            description = ''
 | 
						|
              Arguments passed to <command>date</command>
 | 
						|
              to create a timestamp suffix for the archive name.
 | 
						|
            '';
 | 
						|
            default = "+%Y-%m-%dT%H:%M:%S";
 | 
						|
            example = "-u +%s";
 | 
						|
          };
 | 
						|
 | 
						|
          startAt = mkOption {
 | 
						|
            type = with types; either str (listOf str);
 | 
						|
            default = "daily";
 | 
						|
            description = ''
 | 
						|
              When or how often the backup should run.
 | 
						|
              Must be in the format described in
 | 
						|
              <citerefentry><refentrytitle>systemd.time</refentrytitle>
 | 
						|
              <manvolnum>7</manvolnum></citerefentry>.
 | 
						|
              If you do not want the backup to start
 | 
						|
              automatically, use <literal>[ ]</literal>.
 | 
						|
            '';
 | 
						|
          };
 | 
						|
 | 
						|
          user = mkOption {
 | 
						|
            type = types.str;
 | 
						|
            description = ''
 | 
						|
              The user <command>borg</command> is run as.
 | 
						|
              User or group need read permission
 | 
						|
              for the specified <option>paths</option>.
 | 
						|
            '';
 | 
						|
            default = "root";
 | 
						|
          };
 | 
						|
 | 
						|
          group = mkOption {
 | 
						|
            type = types.str;
 | 
						|
            description = ''
 | 
						|
              The group borg is run as. User or group needs read permission
 | 
						|
              for the specified <option>paths</option>.
 | 
						|
            '';
 | 
						|
            default = "root";
 | 
						|
          };
 | 
						|
 | 
						|
          encryption.mode = mkOption {
 | 
						|
            type = types.enum [
 | 
						|
              "repokey" "keyfile"
 | 
						|
              "repokey-blake2" "keyfile-blake2"
 | 
						|
              "authenticated" "authenticated-blake2"
 | 
						|
              "none"
 | 
						|
            ];
 | 
						|
            description = ''
 | 
						|
              Encryption mode to use. Setting a mode
 | 
						|
              other than <literal>"none"</literal> requires
 | 
						|
              you to specify a <option>passCommand</option>
 | 
						|
              or a <option>passphrase</option>.
 | 
						|
            '';
 | 
						|
          };
 | 
						|
 | 
						|
          encryption.passCommand = mkOption {
 | 
						|
            type = with types; nullOr str;
 | 
						|
            description = ''
 | 
						|
              A command which prints the passphrase to stdout.
 | 
						|
              Mutually exclusive with <option>passphrase</option>.
 | 
						|
            '';
 | 
						|
            default = null;
 | 
						|
            example = "cat /path/to/passphrase_file";
 | 
						|
          };
 | 
						|
 | 
						|
          encryption.passphrase = mkOption {
 | 
						|
            type = with types; nullOr str;
 | 
						|
            description = ''
 | 
						|
              The passphrase the backups are encrypted with.
 | 
						|
              Mutually exclusive with <option>passCommand</option>.
 | 
						|
              If you do not want the passphrase to be stored in the
 | 
						|
              world-readable Nix store, use <option>passCommand</option>.
 | 
						|
            '';
 | 
						|
            default = null;
 | 
						|
          };
 | 
						|
 | 
						|
          compression = mkOption {
 | 
						|
            # "auto" is optional,
 | 
						|
            # compression mode must be given,
 | 
						|
            # compression level is optional
 | 
						|
            type = types.strMatching "none|(auto,)?(lz4|zstd|zlib|lzma)(,[[:digit:]]{1,2})?";
 | 
						|
            description = ''
 | 
						|
              Compression method to use. Refer to
 | 
						|
              <command>borg help compression</command>
 | 
						|
              for all available options.
 | 
						|
            '';
 | 
						|
            default = "lz4";
 | 
						|
            example = "auto,lzma";
 | 
						|
          };
 | 
						|
 | 
						|
          exclude = mkOption {
 | 
						|
            type = with types; listOf str;
 | 
						|
            description = ''
 | 
						|
              Exclude paths matching any of the given patterns. See
 | 
						|
              <command>borg help patterns</command> for pattern syntax.
 | 
						|
            '';
 | 
						|
            default = [ ];
 | 
						|
            example = [
 | 
						|
              "/home/*/.cache"
 | 
						|
              "/nix"
 | 
						|
            ];
 | 
						|
          };
 | 
						|
 | 
						|
          readWritePaths = mkOption {
 | 
						|
            type = with types; listOf path;
 | 
						|
            description = ''
 | 
						|
              By default, borg cannot write anywhere on the system but
 | 
						|
              <literal>$HOME/.config/borg</literal> and <literal>$HOME/.cache/borg</literal>.
 | 
						|
              If, for example, your preHook script needs to dump files
 | 
						|
              somewhere, put those directories here.
 | 
						|
            '';
 | 
						|
            default = [ ];
 | 
						|
            example = [
 | 
						|
              "/var/backup/mysqldump"
 | 
						|
            ];
 | 
						|
          };
 | 
						|
 | 
						|
          privateTmp = mkOption {
 | 
						|
            type = types.bool;
 | 
						|
            description = ''
 | 
						|
              Set the <literal>PrivateTmp</literal> option for
 | 
						|
              the systemd-service. Set to false if you need sockets
 | 
						|
              or other files from global /tmp.
 | 
						|
            '';
 | 
						|
            default = true;
 | 
						|
          };
 | 
						|
 | 
						|
          doInit = mkOption {
 | 
						|
            type = types.bool;
 | 
						|
            description = ''
 | 
						|
              Run <command>borg init</command> if the
 | 
						|
              specified <option>repo</option> does not exist.
 | 
						|
              You should set this to <literal>false</literal>
 | 
						|
              if the repository is located on an external drive
 | 
						|
              that might not always be mounted.
 | 
						|
            '';
 | 
						|
            default = true;
 | 
						|
          };
 | 
						|
 | 
						|
          appendFailedSuffix = mkOption {
 | 
						|
            type = types.bool;
 | 
						|
            description = ''
 | 
						|
              Append a <literal>.failed</literal> suffix
 | 
						|
              to the archive name, which is only removed if
 | 
						|
              <command>borg create</command> has a zero exit status.
 | 
						|
            '';
 | 
						|
            default = true;
 | 
						|
          };
 | 
						|
 | 
						|
          prune.keep = mkOption {
 | 
						|
            # Specifying e.g. `prune.keep.yearly = -1`
 | 
						|
            # means there is no limit of yearly archives to keep
 | 
						|
            # The regex is for use with e.g. --keep-within 1y
 | 
						|
            type = with types; attrsOf (either int (strMatching "[[:digit:]]+[Hdwmy]"));
 | 
						|
            description = ''
 | 
						|
              Prune a repository by deleting all archives not matching any of the
 | 
						|
              specified retention options. See <command>borg help prune</command>
 | 
						|
              for the available options.
 | 
						|
            '';
 | 
						|
            default = { };
 | 
						|
            example = literalExample ''
 | 
						|
              {
 | 
						|
                within = "1d"; # Keep all archives from the last day
 | 
						|
                daily = 7;
 | 
						|
                weekly = 4;
 | 
						|
                monthly = -1;  # Keep at least one archive for each month
 | 
						|
              }
 | 
						|
            '';
 | 
						|
          };
 | 
						|
 | 
						|
          prune.prefix = mkOption {
 | 
						|
            type = types.str;
 | 
						|
            description = ''
 | 
						|
              Only consider archive names starting with this prefix for pruning.
 | 
						|
              By default, only archives created by this job are considered.
 | 
						|
              Use <literal>""</literal> to consider all archives.
 | 
						|
            '';
 | 
						|
            default = config.archiveBaseName;
 | 
						|
            defaultText = "\${archiveBaseName}";
 | 
						|
          };
 | 
						|
 | 
						|
          environment = mkOption {
 | 
						|
            type = with types; attrsOf str;
 | 
						|
            description = ''
 | 
						|
              Environment variables passed to the backup script.
 | 
						|
              You can for example specify which SSH key to use.
 | 
						|
            '';
 | 
						|
            default = { };
 | 
						|
            example = { BORG_RSH = "ssh -i /path/to/key"; };
 | 
						|
          };
 | 
						|
 | 
						|
          preHook = mkOption {
 | 
						|
            type = types.lines;
 | 
						|
            description = ''
 | 
						|
              Shell commands to run before the backup.
 | 
						|
              This can for example be used to mount file systems.
 | 
						|
            '';
 | 
						|
            default = "";
 | 
						|
            example = ''
 | 
						|
              # To add excluded paths at runtime
 | 
						|
              extraCreateArgs="$extraCreateArgs --exclude /some/path"
 | 
						|
            '';
 | 
						|
          };
 | 
						|
 | 
						|
          postInit = mkOption {
 | 
						|
            type = types.lines;
 | 
						|
            description = ''
 | 
						|
              Shell commands to run after <command>borg init</command>.
 | 
						|
            '';
 | 
						|
            default = "";
 | 
						|
          };
 | 
						|
 | 
						|
          postCreate = mkOption {
 | 
						|
            type = types.lines;
 | 
						|
            description = ''
 | 
						|
              Shell commands to run after <command>borg create</command>. The name
 | 
						|
              of the created archive is stored in <literal>$archiveName</literal>.
 | 
						|
            '';
 | 
						|
            default = "";
 | 
						|
          };
 | 
						|
 | 
						|
          postPrune = mkOption {
 | 
						|
            type = types.lines;
 | 
						|
            description = ''
 | 
						|
              Shell commands to run after <command>borg prune</command>.
 | 
						|
            '';
 | 
						|
            default = "";
 | 
						|
          };
 | 
						|
 | 
						|
          postHook = mkOption {
 | 
						|
            type = types.lines;
 | 
						|
            description = ''
 | 
						|
              Shell commands to run just before exit. They are executed
 | 
						|
              even if a previous command exits with a non-zero exit code.
 | 
						|
              The latter is available as <literal>$exitStatus</literal>.
 | 
						|
            '';
 | 
						|
            default = "";
 | 
						|
          };
 | 
						|
 | 
						|
          extraArgs = mkOption {
 | 
						|
            type = types.str;
 | 
						|
            description = ''
 | 
						|
              Additional arguments for all <command>borg</command> calls the
 | 
						|
              service has. Handle with care.
 | 
						|
            '';
 | 
						|
            default = "";
 | 
						|
            example = "--remote-path=/path/to/borg";
 | 
						|
          };
 | 
						|
 | 
						|
          extraInitArgs = mkOption {
 | 
						|
            type = types.str;
 | 
						|
            description = ''
 | 
						|
              Additional arguments for <command>borg init</command>.
 | 
						|
              Can also be set at runtime using <literal>$extraInitArgs</literal>.
 | 
						|
            '';
 | 
						|
            default = "";
 | 
						|
            example = "--append-only";
 | 
						|
          };
 | 
						|
 | 
						|
          extraCreateArgs = mkOption {
 | 
						|
            type = types.str;
 | 
						|
            description = ''
 | 
						|
              Additional arguments for <command>borg create</command>.
 | 
						|
              Can also be set at runtime using <literal>$extraCreateArgs</literal>.
 | 
						|
            '';
 | 
						|
            default = "";
 | 
						|
            example = "--stats --checkpoint-interval 600";
 | 
						|
          };
 | 
						|
 | 
						|
          extraPruneArgs = mkOption {
 | 
						|
            type = types.str;
 | 
						|
            description = ''
 | 
						|
              Additional arguments for <command>borg prune</command>.
 | 
						|
              Can also be set at runtime using <literal>$extraPruneArgs</literal>.
 | 
						|
            '';
 | 
						|
            default = "";
 | 
						|
            example = "--save-space";
 | 
						|
          };
 | 
						|
 | 
						|
        };
 | 
						|
      }
 | 
						|
    ));
 | 
						|
  };
 | 
						|
 | 
						|
  options.services.borgbackup.repos = mkOption {
 | 
						|
    description = ''
 | 
						|
      Serve BorgBackup repositories to given public SSH keys,
 | 
						|
      restricting their access to the repository only.
 | 
						|
      Also, clients do not need to specify the absolute path when accessing the repository,
 | 
						|
      i.e. <literal>user@machine:.</literal> is enough. (Note colon and dot.)
 | 
						|
    '';
 | 
						|
    default = { };
 | 
						|
    type = types.attrsOf (types.submodule (
 | 
						|
      { ... }: {
 | 
						|
        options = {
 | 
						|
          path = mkOption {
 | 
						|
            type = types.path;
 | 
						|
            description = ''
 | 
						|
              Where to store the backups. Note that the directory
 | 
						|
              is created automatically, with correct permissions.
 | 
						|
            '';
 | 
						|
            default = "/var/lib/borgbackup";
 | 
						|
          };
 | 
						|
 | 
						|
          user = mkOption {
 | 
						|
            type = types.str;
 | 
						|
            description = ''
 | 
						|
              The user <command>borg serve</command> is run as.
 | 
						|
              User or group needs write permission
 | 
						|
              for the specified <option>path</option>.
 | 
						|
            '';
 | 
						|
            default = "borg";
 | 
						|
          };
 | 
						|
 | 
						|
          group = mkOption {
 | 
						|
            type = types.str;
 | 
						|
            description = ''
 | 
						|
              The group <command>borg serve</command> is run as.
 | 
						|
              User or group needs write permission
 | 
						|
              for the specified <option>path</option>.
 | 
						|
            '';
 | 
						|
            default = "borg";
 | 
						|
          };
 | 
						|
 | 
						|
          authorizedKeys = mkOption {
 | 
						|
            type = with types; listOf str;
 | 
						|
            description = ''
 | 
						|
              Public SSH keys that are given full write access to this repository.
 | 
						|
              You should use a different SSH key for each repository you write to, because
 | 
						|
              the specified keys are restricted to running <command>borg serve</command>
 | 
						|
              and can only access this single repository.
 | 
						|
            '';
 | 
						|
            default = [ ];
 | 
						|
          };
 | 
						|
 | 
						|
          authorizedKeysAppendOnly = mkOption {
 | 
						|
            type = with types; listOf str;
 | 
						|
            description = ''
 | 
						|
              Public SSH keys that can only be used to append new data (archives) to the repository.
 | 
						|
              Note that archives can still be marked as deleted and are subsequently removed from disk
 | 
						|
              upon accessing the repo with full write access, e.g. when pruning.
 | 
						|
            '';
 | 
						|
            default = [ ];
 | 
						|
          };
 | 
						|
 | 
						|
          allowSubRepos = mkOption {
 | 
						|
            type = types.bool;
 | 
						|
            description = ''
 | 
						|
              Allow clients to create repositories in subdirectories of the
 | 
						|
              specified <option>path</option>. These can be accessed using
 | 
						|
              <literal>user@machine:path/to/subrepo</literal>. Note that a
 | 
						|
              <option>quota</option> applies to repositories independently.
 | 
						|
              Therefore, if this is enabled, clients can create multiple
 | 
						|
              repositories and upload an arbitrary amount of data.
 | 
						|
            '';
 | 
						|
            default = false;
 | 
						|
          };
 | 
						|
 | 
						|
          quota = mkOption {
 | 
						|
            # See the definition of parse_file_size() in src/borg/helpers/parseformat.py
 | 
						|
            type = with types; nullOr (strMatching "[[:digit:].]+[KMGTP]?");
 | 
						|
            description = ''
 | 
						|
              Storage quota for the repository. This quota is ensured for all
 | 
						|
              sub-repositories if <option>allowSubRepos</option> is enabled
 | 
						|
              but not for the overall storage space used.
 | 
						|
            '';
 | 
						|
            default = null;
 | 
						|
            example = "100G";
 | 
						|
          };
 | 
						|
 | 
						|
        };
 | 
						|
      }
 | 
						|
    ));
 | 
						|
  };
 | 
						|
 | 
						|
  ###### implementation
 | 
						|
 | 
						|
  config = mkIf (with config.services.borgbackup; jobs != { } || repos != { })
 | 
						|
    (with config.services.borgbackup; {
 | 
						|
      assertions =
 | 
						|
        mapAttrsToList mkPassAssertion jobs
 | 
						|
        ++ mapAttrsToList mkKeysAssertion repos
 | 
						|
        ++ mapAttrsToList mkRemovableDeviceAssertions jobs;
 | 
						|
 | 
						|
      system.activationScripts = mapAttrs' mkActivationScript jobs;
 | 
						|
 | 
						|
      systemd.services =
 | 
						|
        # A job named "foo" is mapped to systemd.services.borgbackup-job-foo
 | 
						|
        mapAttrs' mkBackupService jobs
 | 
						|
        # A repo named "foo" is mapped to systemd.services.borgbackup-repo-foo
 | 
						|
        // mapAttrs' mkRepoService repos;
 | 
						|
 | 
						|
      users = mkMerge (mapAttrsToList mkUsersConfig repos);
 | 
						|
 | 
						|
      environment.systemPackages = with pkgs; [ borgbackup ];
 | 
						|
    });
 | 
						|
}
 |