preHook and postHook commands failing with borgbackup

I’m having some trouble getting borgbackup to work with LVM snapshots on NixOS. The preHook and postHook portions of this module are mostly adapted from a bash script I have working on another host running Debian, and the commands within them work fine when run manually from a terminal as root.

My broken module:

#
# BorgBackup Module
#

{ config, ... }:

let

  keysPath = "/etc/nixos/keys/borg";
  repoUser = "anon";
  repoHost = "anon";
  hostPath = "~/Backup";
  snapshotPath = "/backup/snapshot";
  snapshotName = "nixos-snapshot";
  snapshotVG = "vg.system";
  snapshotLV = "nixos";
  sleepTime = "5";

in {

  services.borgbackup.jobs.default = {
    paths = map (x: "${snapshotPath}" + x) [
      "/home"
      "/root"
      "/etc/nixos"
    ];
    exclude = [
      "**/.*cache*"
      "**/*cache*"
    ];
    encryption = {
      mode = "repokey-blake2";
      passCommand = "cat ${keysPath}/passphrase";
    };
    prune.keep = {
      daily = 7;
      weekly = 4;
      monthly = 3;
    };
    preHook = ''
      # create snapshot
      lvcreate --snapshot --extents=100%FREE --name=${snapshotName} \
        /dev/${snapshotVG}/${snapshotLV}
      sleep ${sleepTime}
      # mount snapshot
      mkdir --parents ${snapshotPath}
      mount --options ro /dev/${snapshotVG}/${snapshotName} ${snapshotPath}
      sleep ${sleepTime}
    '';
    postHook = ''
      # unmount snapshot
      umount ${snapshotPath}
      sleep ${sleepTime}
      # remove snapshot
      lvremove --force ${snapshotVG}/${snapshotName}
    '';
    doInit = true;
    repo = "${repoUser}@${repoHost}:${hostPath}/${config.networking.hostName}";
    environment = { BORG_RSH = "ssh -i ${keysPath}/id_ed25519"; };
    compression = "auto,lzma,6";
    startAt = [ "14:00" ];
    extraCreateArgs = "--exclude-caches --exclude-if-present '.nobackup'";
    extraPruneArgs = "--save-space";
  };

}

The problems I’m having, according to journalctl:

May 01 14:00:06 anon systemd[1]: Started BorgBackup job default.
May 01 14:00:06 anon borgbackup-job-default-start[11880]: /nix/store/ab4xmq0jh5v5q1zkjkidhbjy5z7zm175-unit-script-borgbackup-job-default-start/bin/borgbackup-job-default-start: line 20: lvcreate: command not found
May 01 14:00:06 anon borgbackup-job-default-start[11881]: /nix/store/ab4xmq0jh5v5q1zkjkidhbjy5z7zm175-unit-script-borgbackup-job-default-start/bin/borgbackup-job-default-start: line 8: umount: command not found
May 01 14:00:06 anon systemd[1]: borgbackup-job-default.service: Main process exited, code=exited, status=127/n/a
May 01 14:00:06 anon systemd[1]: borgbackup-job-default.service: Failed with result ‘exit-code’.

My guess is that the PATH variable isn’t being passed to the environment which runs the preHook and postHook commands, but even if that is the case, I’m not familiar with the correct way of adding it “the NixOS way”.

Good guess :slight_smile: You can set the path variable, but it’s usually just easier to do something like this:

    preHook = ''
      # create snapshot
      ${pkgs.lvm2}/bin/lvcreate --snapshot --extents=100%FREE --name=${snapshotName} \
        /dev/${snapshotVG}/${snapshotLV}
      sleep ${sleepTime}
      # mount snapshot
      mkdir --parents ${snapshotPath}
      mount --options ro /dev/${snapshotVG}/${snapshotName} ${snapshotPath}
      sleep ${sleepTime}
    '';

    postHook = ''
      # unmount snapshot
      umount ${snapshotPath}
      sleep ${sleepTime}
      # remove snapshot
      ${pkgs.lvm2}/bin/lvremove --force ${snapshotVG}/${snapshotName}
    '';

That syntax will rereplace itself with the store path of the relevant packages. Nix will also automatically pick up on these references, and install the packages if they’re not there yet.

I think the other commands should all be available in the default env (sleep is a bash built-in, and I’ve never had to explicitly add mount anywhere, presumably made available through kernel?).

In general, you can’t expect “globally” installed binaries to actually be available in any particular shell. Especially things launched by systemd will usually have their own special environment. This can be annoying, but it also means there is never any accidental cross-pollination that could change the behavior of certain services unexpectedly.