How to write a nix function that create a borgbackup job?

Hi all,

Using nixos 23.05.

As I’m creating a lot of borgbackup jobs and I don’t want to have a very long .nix file (you know, the DRY thing…), I would like to create a nix function (or whatever else is most suited for my need) that:

  • create a borgbackup job using services.borgbackup.jobs.<jobname>
  • add a onFailure property to the created systemd “borgbackup-job-${name}” service

This is what I’ve done so far:

{ pkgs, nixpkgs, ... }:

let
  mkBackupJob = name: secret: paths: {
    services.borgbackup.jobs."${name}" = {
      paths = paths;
      encryption = {
        mode = "repokey";
        passphrase = secret;
      };
      repo = "/data/borgbackup/repos/${name}";
      extraInitArgs = "--make-parent-dirs";
      startAt = "daily";
      persistentTimer = true;
    };
    systemd.services."borgbackup-job-${name}".onFailure = [ "service-failure-notification@%i.service" ];
  };
in
  {
    mkBackupJob "job1" "secret1" [ "/path1" "/path1.1" ];
    mkBackupJob "job2" "secret2" [ "/path2" "/path2.1" ];
  }

But I got this error:

[root@nixos:~]# nixos-rebuild switch
error: syntax error, unexpected '"', expecting '.' or '='

       at /etc/nixos/borgbackup.nix:20:17:

           19|   {
           20|     mkBackupJob "job1" "secret1" [ "/path1" "/path1.1" ];
             |                 ^
           21|     mkBackupJob "job2" "secret2" [ "/path2" "/path2.1" ];
(use '--show-trace' to show detailed location information)
building Nix...

I think I’m doing something wrong, in the sense that I’m not doing it the nix way to achieve what I want. But being new to nixos and nix, I must confess that I’m a bit lost in the nix documentation and I’m not yet used with nix patterns.

Any help will be appreciated.

1 Like

You can use lib.mkMerge to merge many configurations:

{ lib, pkgs, ... }: let
  mkBackupJob = name: secret: paths: …
in {
  config = lib.mkMerge [
    (mkBackupJob "job1" "secret1" [ "/path1" "/path1.1" ])
    (mkBackupJob "job2" "secret2" [ "/path2" "/path2.1" ])
  ];
}
2 Likes

Many thanks @olmokramer, your solution works like a charm!

Can you explain how lib.mkMerge works and why I need to use it with config? Haven’t found this function in the nix documentation nor the nixpkgs documentation, only a slight example in the NixOS manual.

1 Like

I do not know how it works under the hood, but mkMerge takes a list of attrsets and merges them recursively, while taking into account the types defined in the NixOS modules when merging values. For example

lib.mkMerge [
  { a = { b = 1; c = [ 2 ]; }; }
  { a = { c = [ 3 ]; }; }
]

will most likely result in

{ a = { b = 1; c = [ 2 3 ]; }; }

because usually options taking a list value will just concatenate the lists.

I do not know if the config key is actually required. This answer explains a bit more about it.

1 Like

As mentioned in the answer:

Note however that if a NixOS module doesn’t have either a config or options section, the whole thing is assumed to be a config section.

this module:

{ lib, pkgs, ... }: let
  mkBackupJob = name: secret: paths: …
in {
  config = lib.mkMerge [
    (mkBackupJob "job1" "secret1" [ "/path1" "/path1.1" ])
    (mkBackupJob "job2" "secret2" [ "/path2" "/path2.1" ])
  ];
}

could be rewritten like this:

{ lib, pkgs, ... }: let
  mkBackupJob = name: secret: paths: …
in lib.mkMerge [
  (mkBackupJob "job1" "secret1" [ "/path1" "/path1.1" ])
  (mkBackupJob "job2" "secret2" [ "/path2" "/path2.1" ])
];

and both should give the same result.