NixOS Specialisations - How do you use them?

Hey folks,

I am going through my yearly “try out other DEs and neovim again” exercise and decided to implement it with nixos specialisations this time: NixOS Search

My understanding is that these are sort of like “runtime-system-profiles” so that you can switch between different configurations within a single generation. So, maybe by default I boot without any GUI configured, and then I “switch” into my sway or gnome or plasma specialisation based on how I feel (or which ones are actually working :P).

Anyway, the exercise has me curious, how many folks use specialisations? Do you have any particularly novel use-cases?

edit: :laughing: I guess I might not be using specialisation for now: specialisation + flakes results in infinite recursion · Issue #97255 · NixOS/nixpkgs · GitHub

6 Likes

I use specialisation so I can easily switch to a system where dedicated graphic card is disabled, CPU undervolting and all options related to power saving are enabled, when I need to use my laptop without power supply.

4 Likes

I had no idea this option existed. I’m looking forward to hearing back from now people.

6 Likes

I recently learned about specialisation too: I saw a PR in nixpkgs from someone who too rediscovered the feature and renamed it. The orignal name “nesting” was so criptic probably only a couple people knew about it.

Anyway, I have two specialisations. The first is for when I connect my computer to a tv: it sets up autologin for my user, enables wifi, sets default audio output to HDMI and starts the controller daemon. The other is similar but without the autologin, which I use when travelling but I have access to a keyboard.

I use system.nixos.tags to tag each one with same name as of specialisation.<name>, so they appear in grub menu and I can switch at boot. Btw, grub now has pasword support in NixOS so the autologin completely isn’t insecure.

7 Likes

I recently learned about specialisation too: I saw a PR in nixpkgs from someone who too rediscovered the feature and renamed it. The orignal name “nesting” was so criptic probably only a couple people knew about it.

«nesting» was also a rename from «children»… it’s a bit of a wandering feature.

2 Likes

Same here, it seems a very interesting idea, but unfortunately I wasn’t able to find any examples.
@rnhmjoj are your specialisations open source?

1 Like

am i getting confused with nixos profiles? are these the same or something different?

am i getting confused with nixos profiles? are these the same or something different?

Profile: bunch of things to apply at once

Specialisation: a boot entry built together with the main system but with slightly different configuration

3 Likes

I haven’t published my configurations yet, but here’s the specialisation bit

{
  specialisation = {
    on-the-go.configuration =
      { system.nixos.tags = [ "on-the-go" ];
        networking.wireless.enable = true;

        environment.variables.AUDIO = "hdmi1";
        systemd.services.mpd.environment.AUDIO = "hdmi1";
      };

    tv.configuration =
      { system.nixos.tags = [ "tv" ];
        networking.wireless.enable = true;

        environment.variables.AUDIO = "hdmi1";
        systemd.services.mpd.environment.AUDIO = "hdmi1";

        services.mingetty.autologinUser = "rnhmjoj";
        programs.fish.loginShellInit = ''
          if not pgrep -f sc-controller > /dev/null
            xinit &
            sleep 1
            env DISPLAY=:0 scc daemon start
          end
        '';
     };
  };

}

It’s nothing too fancy, really. The $AUDIO thing works because I have this in my asound.conf:

  pcm.!default {
    @func getenv
    vars [ AUDIO ]
    default pcm.sysdefault
  }
2 Likes

I was able to get specialisations working with flakes, and I managed to achieve what I had set out for (initial boot is just my limited “interactive” profile without any DE or GUI apps, but I have plasma/sway specialisations available to switch to).

But, how do you go back to the parent configuration? It changes /run/current-system and I can’t find any back-reference to the parent toplevel.

That’s a good question. I haven’t found a solution yet, but I haven’t really looked much. One way could be to switch using

$ sudo nix-env --switch-generation 123 -p /nix/var/nix/profiles/system

It would be good if specialisations had a link back to the parent, I don’t think it’s too difficult to do.

1 Like

Another rough edge: when you nixos-rebuild ... switch the system, it switches back to the “parent” configuration instead of the current “specialisation”. Considering the implementation, it’s not surprising, but it’s not the best UX (and could potentially be very disruptive… especially if display-manager disappears from underneath you…).

Poking around, I think the specialisation module actually reaches into some boot.loader.grub.configurationName option, so if you use systemd-boot, and don’t specify this per-specialisation, it winds up being unset.

I think instead, maybe, it should be inferred from the key in the specialisation option. Then, it would be guaranteed to be available in /run/current-system/configuration-name and then a smarter script can know the current active specialisation and could switch into the built specialisation with the same name.

2 Likes

So, I’ve just tried to fix these rough edges of specialisations.
Setting a tag or the configuration name based on the specialisation.<name> is trivial and I did it. What I haven’t been able to do, though, is adding a link to the parent. I think there is a circular dependency problem:

  1. children configurations are built before the parent (part of its closure)
  2. to link the parent to a children I need the store path of the parent
  3. the parent can’t be built until the children are

note: I can’t simply modify the children during the parent built because the system configurations are already built and read-only.

This is what I have tried so far:

--- a/nixos/modules/system/activation/top-level.nix
+++ b/nixos/modules/system/activation/top-level.nix
@@ -16,9 +16,14 @@ let
         inherit baseModules;
         system = config.nixpkgs.initialSystem;
         modules =
-           (optionals childConfig.inheritParentConfig modules)
-        ++ [ ./no-clone.nix ]
-        ++ [ childConfig.configuration ];
+          (optionals childConfig.inheritParentConfig modules)
+          ++ [ ./no-clone.nix ]
+          ++ [ childConfig.configuration ]
+          ++ [ ({...}: {
+                system.name = childName;
+                system.parent = system.outPath;
+               })
+             ];
       }).config.system.build.toplevel
     ) config.specialisation;
 
@@ -75,6 +80,7 @@ let
       mkdir $out/specialisation
       ${concatStringsSep "\n"
       (mapAttrsToList (name: path: "ln -s ${path} $out/specialisation/${name}") children)}
+      ${optionalString (config.system.parent != null) "ln -s ${config.system.parent} $out/specialisation/parent"}
 
       mkdir $out/bin
       export localeArchive="${config.i18n.glibcLocales}/lib/locale/locale-archive"
@@ -110,7 +116,7 @@ let
     activationScript = config.system.activationScripts.script;
     nixosLabel = config.system.nixos.label;
 
-    configurationName = config.boot.loader.grub.configurationName;
+    configurationName = config.system.name;
 
     # Needed by switch-to-configuration.
 
@@ -279,6 +285,15 @@ in
       '';
     };
 
+    system.parent = mkOption {
+      type = types.nullOr types.path;
+      internal = true;
+      default = null;
+      description = ''
+        The path of the parent system, if the system is a specialisation child.
+      '';
+    };
+
   };
 

but it fails with a infinite recursion :frowning:

Technically, the same (internally) child specialisation might even be used as a child for multiple parent specialisations…

I wonder if it is worth it, in the bootloader generation, make each entry record the root of specialisation tree in something like /run/current-unspecialised-system/ ?

Any idea about breaking the cycling dependency?

Any idea about breaking the cycling dependency?

As I mentioned, recording that dependency while generating boot entries, and then using that in runtime

I have a laptop with an integrated intel gpu and a nvidia gpu. I would also like to use specialisation to get a system with low power usage. Could you share what settings you are using?

2 Likes

You can just use the latest system link in /nix/var/nix/profiles/ to switch between specialisations and to the base configuration.

I use the following script to do that:

let
      toybox_ls = "${pkgs.toybox}/bin/ls";
      wofi_package = "${config.programs.wofi.package}/bin/wofi";
      current_system = "/nix/var/nix/profiles/$(${toybox_ls} /nix/var/nix/profiles | sort --version-sort | tail -n 1)";
      specSwitcher = pkgs.writeShellScript "specSwitcher" ''
        apfelmus=$(${toybox_ls} ${current_system}/specialisation | ${wofi_package} --dmenu -W 300 -H 400 --prompt 'switch to specialisation')
        sudo ${current_system}/specialisation/$apfelmus/bin/switch-to-configuration test
        if [$? == 0];
        then ${pkgs.libnotify}/bin/notify-send "Switched to Specialisation: $apfelmus!"
        fi
      '';
in
2 Likes

Note there’s also /run/booted-system so if you boot your main system, you can always access the original specialisations through that path.

2 Likes