Please review my new setup with flakes & home manager

Hey everyone!

I have been setting up Nixos with the intention of running it with the same configuration across all my devices. I intend to use disko for hard drive partitioning, flakes to ensure reproducibility and home manager to make transferring (some) gui configs easier.

I would like ask you to have a brief look and tell me, if this is a sane (or even smart) way of doing this. The configs build and run on a spare laptop right now, so my question isn’t about finding actual errors, but about the general logic of what I’m doing.

Less importantly, I do have two specific questions, too.

First, I would love to replace the explicit hostnames in flake.nix with ${HOSTNAME}. I could even have a ${HOSTNAME}-disks.nix. That would allow me to only have a single entry instead of one for every machine. However, I assume I would run into trouble if I ever chroot into one of the machines and run nixos-rebuild or a similar command. Is there a way to do this sanely?

Second, I’m wondering how to best integrate enabling unfree software into my flake. I haven’t found a way to place config.allowUnfree = true; or variations of it in what I have written so far.

First of all an overview of the file structure:

flake.nix               - sets the release version for Nix packages
  ┃                     - enables home-manager
  ┃                     - enables disko
  ┃                     - imports configuration.nix,
  ┃                         system.nix, wayland.nix,
  ┃                         home.nix, disks.nix
  ┃
  ┣━ configuration.nix  - imports hardware-configuration.nix
  ┃   ┃                 - determines the hostname
  ┃   ┃                 - defines the boot loader
  ┃   ┃                 - defines the user
  ┃   ┃                 - enables flakes support
  ┃   ┃
  ┃   ┗━ hardware-configuration.nix  - enables kernel modules
  ┃                                  - enables microcode
  ┃                                  - blacklists kernel modules
  ┃                                  - enables OpenTabletDriver
  ┃
  ┣━ system.nix         - enables garbage collection for Nixos
  ┃                     - sets time zone
  ┃                     - enables networkmanager
  ┃                     - installs basic terminal programs
  ┃
  ┣━ wayland.nix        - enables Wayland
  ┃                     - sets keyboard layout and enables touchpads
  ┃                     - enables printing
  ┃                     - enables sound
  ┃                     - sets up hyprland
  ┃                     - installs graphical programs
  ┃
  ┣━ ${HOSTNAME}.nix      - device-specific settings  
  ┃
  ┣━ home.nix           - sets bash shell aliases
  ┃                     - enables hyprland on tty login
  ┃
  ┣━ disks.nix          - governs hard disk partitioning
  ┃
  ┗━ flake.lock         - autogenerated by the flake, locking app versions

I will share only the first three of these files and home.nix. Just FYI, system.nix contains the base system configs and tty programs, wayland.nix contains the graphical user interface related stuff, and the ${HOSTNAME}.nix files contain machine specific settings and applications.

flake.nix:

{
  description = "NixOS flake governing the entire Nixos system";

  inputs = {
    # Set branch of NixOS packages.
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
    home-manager = {
      url = "github:nix-community/home-manager/release-26.05";
      # Make home-manager remain consistent with inputs.nixpkgs through inheritance.
      inputs.nixpkgs.follows = "nixpkgs";
    };
    disko = {
      url = "github:nix-community/disko/latest";
      # Make disko remain consistent with inputs.nixpkgs through inheritance.
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };


  outputs = { nixpkgs, home-manager, disko, ... }: {
    nixosConfigurations = {
      # Here go specific sections for each machine, identified by hostname, enabling custom configs.

      nixpad = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          # Configuration.nix is reduced to calling hardware-configuration.nix, setting hostname & activating Nixos flakes.
          ./configuration.nix
          ./system.nix
          ./wayland.nix
          ./nixpad.nix
          home-manager.nixosModules.home-manager
          {
            home-manager.useGlobalPkgs = true;
            home-manager.useUserPackages = true;
            home-manager.backupFileExtension = "backup";
            home-manager.users.garnix = import ./home.nix;
          }
          # agenix.nixosModules.default
          disko.nixosModules.disko
          ./disks.nix
        ];
      };

      nixbook = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          # Configuration.nix is reduced to calling hardware-configuration.nix, setting hostname & activating Nixos flakes.
          ./configuration.nix
          ./system.nix
          ./wayland.nix
          ./nixbook.nix
          home-manager.nixosModules.home-manager
          {
            home-manager.useGlobalPkgs = true;
            home-manager.useUserPackages = true;
            home-manager.backupFileExtension = "backup";
            home-manager.users.garnix = import ./home.nix;
          }
          # agenix.nixosModules.default
          disko.nixosModules.disko
          ./disks.nix
        ];
      };

    };
  };
}

configuration.nix:

{ config, lib, pkgs, ... }:

{
  imports =
    [
      ./hardware-configuration.nix
    ];

  # Use the systemd-boot EFI boot loader.
  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;

  networking.hostName = "nixpad"; # Define your hostname.

  # Define a user account. Don't forget to set a password with ‘passwd’.
  users.users.garnix = {
     isNormalUser = true;
     home = "/home/garnix";
     shell = pkgs.bash;
     extraGroups = [ "wheel" "networkmanager" ];
     packages = with pkgs; [
     ];
  };

  # enable flakes
  nix.settings.experimental-features = [ "nix-command" "flakes" ];

  system.stateVersion = "26.05";

}

hardware-configuration.nix:

{ config, lib, pkgs, modulesPath, ... }:

{
  imports =
    [ (modulesPath + "/installer/scan/not-detected.nix")
    ];

  boot.initrd.availableKernelModules = [ "xhci_pci" "ahci" "usb_storage" "sd_mod" "rtsx_pci_sdmmc" ];
  boot.initrd.kernelModules = [ ];
  boot.kernelModules = [ "kvm-intel" "uinput" ]; # uinput is required by OpenTabletDriver below
  boot.blacklistedKernelModules = [ "wacom" "hid_uclogic" ]; # blacklist modules interfering with access to the uinput tablet
  boot.extraModulePackages = [ ];

  nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
  hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;

  # Enable OpenTabletDriver
  hardware.opentabletdriver.enable = true;
  hardware.uinput.enable = true;

  # Enable hardware graphics for Steam
  hardware.graphics.enable = true;
  hardware.graphics.enable32Bit = true;
}

home.nix:

{ config, libs, pkgs, ... }:

{

  home.username = "garnix";
  home.homeDirectory = "/home/garnix";
  home.stateVersion = "26.05";
  programs.home-manager.enable = true;

  programs.bash = {
    enable = true;
    shellAliases = {
      testhome = "echo home.nix successfully loaded";
    };
    # Make a login of user garnix into TTY2 test for the existence of a Wayland session.
    # Start hyprland if no Wayland session exists and replace TTY2 with the graphical session
    # and log the user out when the graphical session is exited.
    profileExtra = ''
      if [ -z "$WAYLAND_DISPLAY" ] && [ "$XDG_VTNR" = 2 ]; then
        exec uwsm start hyprland-uwsm.desktop
      fi
    '';
  };

  programs.fzf.enableBashIntegration = true;

}

Thanks a bunch to anyone reading or skimming this far! I’d love to hear your thoughts.

Hi :slight_smile:
If my understanding is good you will manage more than one machines. If you fix the base (with some configuration.nix) you may have a painfull reorganisation the day you want a machine which don’t fit in the template you did. And generaly this case arrives faster than we can imagine.
It’s more flexible to have a free configuration.nix and a /lib with modules you load as you wish.
To shift to this idea you just have to put all in a lib directory, create a host/[hostname]/configuration.nix, put the actual “configuration.nix” in the lib/ renamed “main_template.nix” and import it from your new configuration.nix.
This way you will improve modularity with a stable organisation.

Emptying out configuration.nix makes sense. Placing common stuff in lib and distinguishing .nix files by placing them in folders named according to hostnames makes sense to me, too. Thank you for your input! :slight_smile:

I would personally avoid introducing variables into hostnames. There are many options for avoiding repetition, but it all depends on how in-depth you want to go with your configuration’s structure. In any case, I would recommend reading through this thread.

Although, some of the syntax used there is not very beginner-friendly, which is another problem that could probably be solved by coming up with a decent cheat sheet for things such as:

  • What are the two option assignment syntaxes?
  • How to pass arguments to functions?
  • What does a Nix file return?
  • How to use let in, import, imports, with, some common builtins, etc?

I sure could have used one back when I was new to Nix. With no prior functional or lisp language experience, understanding as little as how the Flake and Home Manager templates I was using even worked was pretty painful. I’ll look into drafting something up on the weekend.

Adding the following option to your configuration.nix should do the trick:

nixpkgs.config.allowUnfree = true;

One small nit: system.nix has recently been reserved by nixos-rebuild: Curious about system.nix

Probably not a big deal since you are a flake user but still better to make the choice to use a reserved name for something else intentionally rather than unknowingly.

There is also nixpkgs.config.allowUnfreePredicate which can allow more fine-grained control of unfree behavior.

That did solve it! I was trying to put it in flake.nix, which didn’t work the way I imagined. This is neat and simple! :slight_smile:

You are right about the lisp syntax being a stumbling block. I am slowly finding my way, but any pointers at more or less entry-level guides that explain the language as it pertains to .nix files would of course be appreciated!

Thank you for pointing that out! I will change the name of the file. No need to run into conflicts down the road.

Yeah, I saw the Predicate variant. I will have a look at that later, now that I actually got it working. :slight_smile: