1 55 NixOS Modules and Option Merging
Nimmo edited this page 2026-09-13 13:05:54 +01:00

NixOS Modules and Option Merging

  • Type: Tutorial
  • Status: Current
  • Scope: Reading imports, option declarations, enable gates, and merge priorities in nixos-config
  • Canonical sources: modules/common/base.nix; modules/common/default-config.nix; modules/services/jellyfin.nix; modules/services/service-failure-monitor.nix; hosts/lyra/default.nix; hosts/electra/default.nix; Nix module system tutorial
  • Last verified: Source commit 556df88494686003b1c4f20c8e0b99b5afc16a6e (2026-09-12); source HEAD b2831d33132967b4c4fb69e6068b2b05fff8fdff changes only flake.lock. Cited examples checked 2026-09-13; no module changed.
  • Review triggers: Module imports/options, service enable gates, specialisation overrides, or upstream module-system explanation

Learning goal and prerequisites

Understand how several files contribute to one host configuration without being executed as a shell script in order. Start after Nix Language Essentials and Flakes and Inputs. Use the official module-system tutorial for general mechanics.

Import graph and option values

flake.nix imports hosts/lyra/default.nix through makeNixosSystem. Lyra's imports includes common/server modules, hardware, and selected services. modules/common/base.nix imports default options, SOPS identity, maintenance, and the service-failure monitor. Imports determine which definitions participate; they are not activation commands or an override sequence. Repository Structure and Import Chain maps the full path.

modules/common/default-config.nix declares nixosConfig.host.role as an enum whose default is "unspecified". Lyra sets the value to "server". The module system combines the option declaration and definitions into one typed result. A misspelled or undeclared option produces an error; two incompatible definitions of a single-valued option conflict rather than letting the later file win.

modules/services/jellyfin.nix declares an enable option and places its service definitions under lib.mkIf cfg.enable. Importing the module makes the option known; Lyra separately sets nixosConfig.jellyfin.enable = true. By contrast, modules/hardware/bluetooth.nix sets Bluetooth options when imported, without a local enable gate. Adding a Module or Profile explains when each pattern fits.

Merging and priority in this fleet

Jellyfin contributes its data directory to nixosConfig.backup.dataPaths and its unit name to services.serviceFailureMonitor.services using lib.mkAfter. These list contributions merge with those of other services; mkAfter controls ordering, not whether the unit runs. The monitor module declares its service-name option as types.listOf types.str with an empty default and uses the merged names to generate failure hooks.

lib.mkDefault makes a low-priority definition: Lyra's generated hardware configuration uses it for nixpkgs.hostPlatform. lib.mkForce is a strong override: Electra uses it inside igpu and dgpu specialisations for values intentionally different from the base, including their labels. Neither modifier is ordinary Nix assignment order. Use mkForce for an explained override, not to hide an accidental conflict. List and attrset options have their own merge rules, too; ordinary Nix // is not the same thing as a NixOS module merge.

The final config is a fixed point. Definitions can refer to other merged values, as Jellyfin's module does with config.services.jellyfin.dataDir. It is not a mutable object filled in line by line. Circular dependencies can produce infinite recursion; trace the option dependency rather than blindly adding a priority modifier.

Guided check

In a read-only checkout, trace nixosConfig.jellyfin.enable from its declaration to Lyra's value and the guarded service configuration. Find Jellyfin's two mkAfter contributions and the declaration of services.serviceFailureMonitor.services. Explain why importing the monitor module alone does not monitor every service.

Compare the mkDefault host-platform value in hosts/lyra/hardware-configuration.nix with an Electra specialisation mkForce value. Do not change hardware mode as an exercise. For actual edits, follow Adding a Module or Profile and Testing and CI Contracts.

Common mistakes: assuming an import enables an option-gated service, assuming a later file wins a conflict, using mkForce to silence a design error, or treating a backup-list entry as evidence of a usable snapshot.