I’m writing a custom NixOS module that defines a list of backup jobs via a submodule option. In the config block, I use map over the list from config to generate systemd units. This causes an infinite recursion error when I try to build.
Module definition
# modules/borg-backup.nix
{ config, lib, pkgs, ... }:
let
moduleConfig = config.services.myBorgBackup;
in
{
options.services.myBorgBackup = {
backupJobs = lib.mkOption {
type = lib.types.listOf (lib.types.submodule {
options = {
enable = lib.mkEnableOption "Borg backup job";
repoPath = lib.mkOption {
type = lib.types.str;
example = "/backups/hostname";
description = "Path to the borg repository.";
};
};
});
default = [ ];
description = "List of borg backup jobs.";
};
};
config =
let
mkJob = jobConfig: {
systemd.sockets."borg-backup-socket" = {
enable = true;
description = "Socket for borg backup to ${jobConfig.repoPath}";
# ...
};
};
in
lib.mkMerge (map mkJob moduleConfig.backupJobs);
}
Usage in a host configuration
# hosts/my-server.nix
{ config, ... }:
{
services.myBorgBackup.backupJobs = [
{
enable = true;
repoPath = "/backups/my-server";
}
];
}
Error
nix build .#nixosConfigurations.my-server.config.system.build.topLevel returns
error:
… while calling the 'seq' builtin
at /nix/store/34r6krygvwvxll74244xxhbxq91f9gr9-source/lib/modules.nix:361:18:
360| options = checked options;
361| config = checked (removeAttrs config [ "_module" ]);
| ^
362| _module = checked (config._module);
… while evaluating a branch condition
at /nix/store/34r6krygvwvxll74244xxhbxq91f9gr9-source/lib/modules.nix:297:9:
296| checkUnmatched =
297| if config._module.check && config._module.freeformType == null && merged.unmatchedDefns != [ ] then
| ^
298| let
… while evaluating the option `_module.freeformType':
… while evaluating the module argument `config' in "/nix/store/v3z1ng2j129anrhllqpd1dpzv8w0syhd-source/modules/mixins/borg-backup/test.nix":
… if you get an infinite recursion here, you probably reference `config` in `imports`. If you are trying to achieve a conditional import behavior dependent on `config`, consider importing unconditionally, and using `mkEnableOption` and `mkIf` to control its effect.
(stack trace truncated; use '--show-trace' to show the full, detailed trace)
error: infinite recursion encountered
What I understand
The config block of my module calls map mkJob moduleConfig.backupJobs. This forces Nix to evaluate the length of the backupJobs list in order to produce the list of attribute sets for lib.mkMerge. But determining the value of backupJobs requires evaluating the merged config of all modules – including this module’s own config block. Right?
What is the idiomatic NixOS module pattern way here?