Specialisation

From NixOS Wiki

Specialisations allow you to define variations of your config within itself (even completely different ones). Within a generation of your config, you can then choose between specialisations at boot-time or switch them at runtime using activation scripts.

Previously this feature was called "children" because a specialised configuration is defined by its "parent" configuration. Later, it was called "nesting", because this feature essentially allows nested configurations. Finally, it was renamed to todays "specialisation". [2]

Config

Specialisations are defined with the following options [1]: https://search.nixos.org/options?from=0&size=50&sort=relevance&query=specialisation

specialisation = {
  chani.configuration = {
    services.xserver.desktopManager.plasma5.enable = true;
  };

  paul = {
    inheritParentConfig = false;
    configuration = {
      system.nixos.tags = [ "paul" ];
      services.xserver.desktopManager.gnome.enable = true;
      users.users.paul = {
        isNormalUser = true;
        uid = 1002;
        extraGroups = [ "networkmanager" "video" ];
      };
      services.xserver.displayManager.autoLogin = {
        enable = true;
        user = "paul";
      };
      environment.systemPackages = with pkgs; [
        dune-release
      ];
    };
  };
};

In this example, the chani specialisation inherits the parent config (that contains the specialisation directive), but additionally activates the plasma5 desktop. The paul specialisation on the other hand does not inheritParentConfig and defines its own one from scratch instead.

Note: At times, you may want to overwrite values in specialisations which you have already defined in your parent configuration. To solve this problem in chani example, the parent configuration could define services.xserver.desktopManager.plasma5.enable = false; in an overwritable manner using mkDefault and similar [3]: services.xserver.desktopManager.plasma5.enable = mkDefault false;

Special case: the default non-specialized entry

Specializations are receiving options in addition to your default configuration, but what if you want to have options in your default configuration that shouldn't be pulled by the specializations?

Use the conditional config.specialisation != {} to declare values for the non-specialized case. For example, you could write a module (as variable, or separate file), imported from configuration.nix via imports = [...] like this:

({ lib, config, pkgs, ... }: {
  config = lib.mkIf (config.specialisation != {}) {
    # Config that should only apply to the default system, not the specialised ones

    # example
    hardware.opengl.extraPackages = with pkgs; [ vaapiIntel vaapiVdpau ];
  };
})

Boot entries

For every generation of your config, a boot menu entry is generated. When using specialisations, an additional entry is generated for it, per generation.

TODO: how to use grub submenus

Runtime activation

Taking the chani specialisation from our example, we can activate it at runtime (comparable to nixos-rebuild switch), by running /run/current-system/specialisation/chani/bin/switch-to-configuration switch. nixos-rebuild switch also supports a --specialisation flag, which we can use like this: nixos-rebuild switch --specialisation chani.

Further reading

[1] https://www.tweag.io/blog/2022-08-18-nixos-specialisations/

[2] https://discourse.nixos.org/t/nixos-specialisations-how-do-you-use-them/10367/4

[3] https://discourse.nixos.org/t/what-does-mkdefault-do-exactly/9028