Configuring NixOS

Last modified:

Linux

There are a lot of Linux distros to choose from. It can be important to keep in mind what you want to use it for. I have used many distros. While I like Arch Linux, there is time required to set it up. NixOS resolves that issue. Although for beginners, I would recommend something like OpenSUSE or EndeavourOS. OpenSUSE is very solid; I would recommend it for advanced users too. It is easy to install and set up.

NixOS

The big idea around NixOS is to have a reproducible system. This means that you can get the exact same system running elsewhere easily.

This is overkill for most people. Although rollbacks are useful, which NixOS provides too. The time you invest to setup a new system isn't always enough to make NixOS worth to consider. I do not expect to reinstall my system often, only when something goes wrong or my hardware gets too outdated. But it is nice that not much effort is required to reinstall.

There is a catch though, NixOS can be hard to learn. The basics do not have to be hard if you have a bit of Linux experience. Linux can be tweaked a lot. You can make your system run better or worse depending on of you are aware of what you are doing. It is nice to not have to do those tweaks every installation. For most users the default options of an operating system are fine. Often it is more useful to spend more time on the tasks you do with the system instead.

Common Linux mistakes

Linux is often seeks as something for computer geeks. Although it can be easy to use depending on which operating system you select, and what your needs are. Many computer geeks like to tweak, and much can break by doing that.

Some common mistakes:

  • Configuring zswap without a swap file
  • Using irqbalance - new kernels have this behavior already. Although irqbalance can still be useful on servers
  • Modifying kernel variables without knowing what they do. Custom kernels often already set certain variables. Often people try to set too much.
  • Making a system unbootable accidentally. While not having rollbacks, backups or a recovery USB.
  • ...

My first configuration

My first configuration of NixOS does not make use of flakes. Flakes make NixOS more reproducable, and is one of the main reasons people use NixOS. The benefits just do not seemed to outweight the cons for me.

I merged the nixos-hardware.nix file into my configuration, as I prefer having one big file over separating stuff. It is easier to modify in my opinion.

This configuration does have issues, which I want to resolve. Although the essentials work.

  • kwallet autounlock does not work
  • kde automount drives does not work
  • scripts on logon are not running
  • blocky does not work
# Edit this configuration file to define what should be installed on
# your system.  Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running ‘nixos-help’).

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

{
  # START HARDWARE
  # Lenovo Legion T5-26AMR5

  boot = {
    initrd = {
      availableKernelModules = [ "nvme" "xhci_pci" "ahci" "usb_storage" "usbhid" "sd_mod" ];
    };
    kernelPackages = pkgs.linuxPackages_zen;
    kernelModules = [ "kvm-amd" "iwlwifi" ];
    # Blacklist nouveau to make sure we enforce the closed source nvidia driver
    blacklistedKernelModules = [ "nouveau" ];
    # mitigations=off if more performance, less security is wanted, not relevant for modern CPUs etc
    # Other zswap settings are inherited of zen kernel  
    # amd stuff is for amd cpu
    kernelParams = [ "zswap.enabled=1" "quiet" "splash" "amd_pstate=guide" ];
    kernel.sysctl = {
      # DEFAULTS ARE FINE IF YOU DO NOT KNOW WHAT YOU ARE DOING
      # CHANGING IT CAN DO MORE WRONG THAN RIGHT - LEAVING SYSCTL ALONE IS FINE

      # https://github.com/tolgaerok/nixos-kde/blob/main/core/modules/system-tweaks/kernel-tweaks/8GB-SYSTEM/8GB-SYSTEM.nix
      # https://github.com/tolgaerok/nixos-kde/blob/main/core/modules/system-tweaks/storage-tweaks/SSD/SSD-tweak.nix
      #   Network and memory-related optimizationss for 8GB
      "kernel.sysrq" = 1; # Enable SysRQ for rebooting the machine properly if it freezes. [Source](https://oglo.dev/tutorials/sysrq/index.html)
      "net.core.netdev_max_backlog" = 30000; # Help prevent packet loss during high traffic periods.
      "net.core.rmem_default" = 262144; # Default socket receive buffer size, improve network performance & applications that use sockets. Adjusted for 8GB RAM.
      "net.core.rmem_max" = 33554432; # Maximum socket receive buffer size, determine the amount of data that can be buffered in memory for network operations. Adjusted for 8GB RAM.
      "net.core.wmem_default" = 262144; # Default socket send buffer size, improve network performance & applications that use sockets. Adjusted for 8GB RAM.
      "net.core.wmem_max" = 33554432; # Maximum socket send buffer size, determine the amount of data that can be buffered in memory for network operations. Adjusted for 8GB RAM.
      "net.ipv4.ipfrag_high_threshold" = 5242880; # Reduce the chances of fragmentation. Adjusted for SSD.
      "net.ipv4.tcp_keepalive_intvl" = 30; # TCP keepalive interval between probes to detect if a connection is still alive.
      "net.ipv4.tcp_keepalive_probes" = 5; # TCP keepalive probes to detect if a connection is still alive.
      "net.ipv4.tcp_keepalive_time" = 300; # TCP keepalive interval in seconds to detect if a connection is still alive.
      "vm.dirty_background_bytes" = 134217728; # 128 MB
      "vm.dirty_bytes" = 402653184; # 384 MB
      "vm.min_free_kbytes" = 131072; # Minimum free memory for safety (in KB), helping prevent memory exhaustion situations. Adjusted for 8GB RAM.
      "vm.swappiness" = 10; # Adjust how aggressively the kernel swaps data from RAM to disk. Lower values prioritize keeping data in RAM. Adjusted for 8GB RAM.
      "vm.vfs_cache_pressure" = 90; # Adjust vfs_cache_pressure (0-1000) to manage memory used for caching filesystem objects. Adjusted for 8GB RAM.

      # Nobara Tweaks  
      "fs.aio-max-nr" = 1000000; # defines the maximum number of asynchronous I/O requests that can be in progress at a given time.     1048576
      "fs.inotify.max_user_watches" = 65536; # sets the maximum number of file system watches, enhancing file system monitoring capabilities.       Default: 8192  TWEAKED: 524288
      "kernel.panic" = 5; # Reboot after 5 seconds on kernel panic                                                               Default: 0
      "kernel.pid_max" = 131072; # allows a large number of processes and threads to be managed      

      #   SSD tweaks: Adjust settings for an SSD to optimize performance.
      "vm.dirty_background_ratio" = "40"; # Set the ratio of dirty memory at which background writeback starts (5%). Adjusted for SSD.
      "vm.dirty_expire_centisecs" = "3000"; # Set the time at which dirty data is old enough to be eligible for writeout (6000 centiseconds). Adjusted for SSD.
      "vm.dirty_ratio" = "80"; # Set the ratio of dirty memory at which a process is forced to write out dirty data (10%). Adjusted for SSD.
      "vm.dirty_time" = "0"; # Disable dirty time accounting.
      "vm.dirty_writeback_centisecs" = "300"; # Set the interval between two consecutive background writeback passes (500 centiseconds).
    };

    # Low swap value, since I use an SSD - although you really shouldn't worry about it, SSDs have quiet a long lifespan
    extraModulePackages = [ ];
    tmp = {
      cleanOnBoot = true;
      useTmpfs = true;
    };
  };

  fileSystems."/" =
    {
      device = "/dev/disk/by-uuid/6205c622-bf48-4250-8497-61024cf78f9b";
      fsType = "f2fs";
      options = [ "rw" "noatime" "lazytime" "compress_algorithm=zstd:6" "compress_chksum" "atgc,gc_merge" ];
    };

  fileSystems."/boot" =
    {
      device = "/dev/disk/by-uuid/B6B7-81CE";
      fsType = "vfat";
    };

  # Zswap requires a swapfile or partition to work correctly
  swapDevices = [{
    device = "/var/lib/swapfile";
    # Swap is used when your RAM is full. It shouldn't happen often, 
    # but you will be thankful that you have it when it is needed.

    # RAM size (8 GB) + 2 GB (since I have enough storage space)
    size = (1024 * 8) + (1024 * 2); # RAM size + 2 GB (since I have enough storage space)
  }];

  # Allow closed-source firmware
  hardware.enableRedistributableFirmware = true;

  # Enables DHCP on each ethernet and wireless interface. This is the recommended approach.
  networking.useDHCP = lib.mkForce true;

  nixpkgs.hostPlatform = "x86_64-linux";

  # AMD microcode
  hardware.cpu.amd.updateMicrocode = true;

  # TPM2 module
  security.tpm2.enable = true;

  # Enable OpenGL
  hardware.opengl = {
    enable = true;
    driSupport = true;
    driSupport32Bit = true;

    ## amdvlk: an open-source Vulkan driver from AMD & nvidia accelerated video
    extraPackages = [ pkgs.amdvlk pkgs.nvidia-vaapi-driver ];
    extraPackages32 = [ pkgs.driversi686Linux.amdvlk ];
  };

  # Load nvidia driver for Xorg and Wayland
  services.xserver.videoDrivers = [ "nvidia" ];
  hardware.nvidia = {

    # Modesetting is required.
    modesetting.enable = true;

    # Nvidia power management. Experimental, and can cause sleep/suspend to fail.
    # Enable this if you have graphical corruption issues or application crashes after waking
    # up from sleep. This fixes it by saving the entire VRAM memory to /tmp/ instead 
    # of just the bare essentials.
    powerManagement.enable = false;

    # Fine-grained power management. Turns off GPU when not in use.
    # Experimental and only works on modern Nvidia GPUs (Turing or newer).
    powerManagement.finegrained = false;

    # Use the NVidia open source kernel module (not to be confused with the
    # independent third-party "nouveau" open source driver).
    # Support is limited to the Turing and later architectures. Full list of 
    # supported GPUs is at: 
    # https://github.com/NVIDIA/open-gpu-kernel-modules#compatible-gpus 
    # Only available from driver 515.43.04+
    # Currently alpha-quality/buggy, so false is currently the recommended setting.
    open = true;

    # Enable the Nvidia settings menu,
    # accessible via `nvidia-settings`.
    nvidiaSettings = true;

    # Optionally, you may need to select the appropriate driver version for your specific GPU.
    package = config.boot.kernelPackages.nvidiaPackages.stable;
  };

  # END HARDWARE

  # START CONFIG

  # Bootloader
  boot = {
    loader = {
      systemd-boot = {
        enable = true;
        configurationLimit = 5;
      };
      efi.canTouchEfiVariables = true;
    };
    supportedFilesystems = [ "ntfs" ];
  };

  networking = {
    hostName = "nixos"; # Define your hostname.
    networkmanager.enable = true;
  };

  # Set your time zone.
  time.timeZone = "Europe/Brussels";

  # Select internationalisation properties.
  i18n.defaultLocale = "en_US.UTF-8";

  # Runs scripts every startup - script seems to run, but does not work
  systemd.user.services.startup = {
  script = ''
    # Update firmware
    fwupdmgr refresh --offline --assume-yes && fwupdmgr update --offline --assume-yes
   
    # Disable internet during weekdays -- DOES NOT SEEM TO WORK
    # Define day and time periods for blocking
    OFFTIME="--weekdays Mon,Tue,Wed,Thu"
    # Fri = Drawing

    # Submit rules - it does not hurt to block input as well
    sudo iptables -A INPUT -p all --match time $OFFTIME  -j DROP
    sudo iptables -A OUTPUT -p all --match time $OFFTIME -j DROP

    # Review the rules just added
    # sudo iptables -L | grep -P -A2 '.+policy.+'

    # Save rules (reboot may be required)
    sudo iptables-save | sudo tee /etc/iptables.rules >/dev/null
    echo 'iptables-restore < /etc/iptables.rules' | sudo tee -a /etc/rc.local >/dev/null

    # Remove all rules
    # sudo iptables -F
    # sudo rm /etc/iptables.rules && sudo rm /etc/rc.local
  '';
  wantedBy = [ "multi-user.target" ];
};

  # KDE
  services.displayManager = {
    autoLogin = {
      enable = true;
      user = "rmw";
    };
    # I am not using wayland now
    defaultSession = "plasmax11";
  };

  # nvidia force closed source driver
  environment.variables = {
    GBM_BACKEND = "nvidia-drm";
    LIBVA_DRIVER_NAME = "nvidia";
    __GLX_VENDOR_LIBRARY_NAME = "nvidia";
    # this envs are useful for electron wayland
    # ELECTRON_OZONE_PLATFORM_HINT = "auto";
    # variable for qt (wayland with fallback to x11)
    # QT_QPA_PLATFORM = "wayland;xcb";   
    # set sessiontype
    # XDG_SESSION_TYPE = "wayland";
  };

  hardware.bluetooth.enable = true;
  services.desktopManager.plasma6.enable = true;
  # pam_wallet will attempt to unlock the user's default KDE wallet upon login. 
  # If the user has no kdewallet, or the login password does not match their wallet password, 
  # KDE will prompt separately after login. sddm is the displaymanager
  security.pam.services.sddm.kwallet.enable = true;

  environment.plasma6.excludePackages = with pkgs.kdePackages; [
    gwenview
    okular
    oxygen
    khelpcenter
    plasma-browser-integration
    print-manager
  ];

  # Drawing tablet
  hardware.opentabletdriver.enable = true;

  # Configure keymap in X11
  services.xserver = {
    # Enable the X11 windowing system.
    enable = true;

    # Keyboard
    xkb.layout = "us";
    xkb.variant = "";

    # Drawing tablet
    # wacom.enable = true;
    # using opentabletdriver instead
  };
  # Mouse
  services.libinput = {
      enable = true;

      # disabling mouse acceleration
      mouse = {
        accelProfile = "flat";
      };

      # disabling touchpad acceleration
      touchpad = {
        accelProfile = "flat";
      };
    };

  nix = {
    optimise.automatic = true;
    gc = {
      automatic = true;
      dates = "weekly";
      options = "--delete-older-than 7d";
    };
    settings = {
      auto-optimise-store = true;
      max-jobs = "auto";
      substituters = [
        "https://nix-community.cachix.org"
        "https://cache.nixos.org/"
      ];
      trusted-public-keys = [
        "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
      ];
    };
  };

  # Don't install documentation I don't use it
  documentation.enable = false; # documentation of packages

  # Enable ananicy
  services.ananicy = {
    enable = true;
    package = pkgs.ananicy-cpp;
    rulesProvider = pkgs.ananicy-cpp;
    settings = {
      apply_nice = true;
    };
  };


  # update firmware
  # list of supported devices: https://fwupd.org/lvfs/devices/
  services.fwupd.enable = true;
  # Command to update: fwupdmgr refresh && fwupdmgr update

  # Earlyoom killer
  systemd.oomd.enable = false;
  services.earlyoom.enable = true;

  # journald is disabled by default, coredump is not
  systemd.coredump.enable = false;

  # disable suspend, hibernate and sleep
  systemd.sleep.extraConfig = ''
    AllowSuspend=no
    AllowHibernation=no
    AllowHybridSleep=no
    AllowSuspendThenHibernate=no
  '';

  # Make nixos boot slightly faster by turning these off during boot
  systemd.services.NetworkManager-wait-online.enable = false;
  systemd.services.systemd-udev-settle.enable = false;

  # Schedulers from https://wiki.archlinux.org/title/improving_performance
  services.udev.extraRules = ''
    # HDD
    ACTION=="add|change", KERNEL=="sd[a-z]*", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="bfq"
    # SSD
    ACTION=="add|change", KERNEL=="sd[a-z]*|mmcblk[0-9]*", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="bfq"
    # NVMe SSD
    ACTION=="add|change", KERNEL=="nvme[0-9]*", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="none"
  '';

  # cloudflare is ranked as fastest dns
  # isp's know your queries too, so speed over security here.

  # DoH and DoT will protect you from your ISP, at the costs of handing all your DNS data to your DoH or DoT provider.
  services.blocky = {
    enable = true;
    settings = {
      upstream.default = [
        "https://one.one.one.one/dns-query" # Using Cloudflare's DNS over HTTPS server for resolving queries.
      ];
      # For initially solving DoH/DoT Requests when no system Resolver is available.
      bootstrapDns = {
        upstream = "https://one.one.one.one/dns-query";
        ips = [ "1.1.1.1" "1.0.0.1" ];
      };
      #Enable Blocking of certian domains.
      blocking = {
        blackLists.default = [
          #Adblocking
          "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"
          #Block adult sites
          "https://blocklistproject.github.io/Lists/porn.txt"
          #You can add additional categories
        ];
        loading = {
          # refetching every 4h is too much, only download it once, never refresh
          refreshPeriod = 0;
          downloads = {
            timeout = "15s"; # 5s really isn't much time
          };
        };
      };
    };
  };

  # Enable sound with pipewire.
  sound.enable = true;
  hardware.pulseaudio.enable = false;
  security.rtkit.enable = true;
  services.pipewire = {
    enable = true;
    alsa.enable = true;
    alsa.support32Bit = true;
    pulse.enable = true;
  };

  # Define a user account. Don't forget to set a password with ‘passwd’.
  users.users.rmw = {
    initialPassword = "r@@t";
    isNormalUser = true;
    description = "Robin Wils";
    extraGroups = [ "networkmanager" "network" "wheel" "storage" ];
    packages = with pkgs; [
      # Make sure that every character can be displayed by adding this font
      noto-fonts

      # used to remove metadata of images
      exiftool

      kate
      discord
      krita
      keepassxc
      brave
      vscodium
      freetube
      stremio
      htop
      mpv
      bun # similar to npm, but different
      yt-dlp

      # not often needed, but sometimes useful
      universal-android-debloater

      archiver # lots of archivers & unarchivers into one package
      # Archive syntax: arc archive [archive name] [input files...]
      # Extract syntax: arc unarchive [archive name] [destination]
      # list syntax: arc ls [archive name]

      # nix config file formatter
      nixpkgs-fmt

      # GAMING (I am not a big gamer, only for party games)
      r2modman # modmanager, using it for lethal company
      # steam package
      xorg.libxcb # required for steam according to some people, but steam worked without it too
      obs-studio
      shotcut

      # automount
      udiskie
    ];
  };

  # Automount (tray mode, automount)
  #services.xserver.displayManager.sessionCommands = ''
  #  udiskie -Ns
  #'';

  # List packages installed in system profile. To search, run:
  # $ nix search wget
  environment.systemPackages = with pkgs; [
    # Do not forget to add an editor to edit configuration.nix! The Nano editor is installed by default.
    emacs
    # qt recommends this system package for wayland
    qt6.qtwayland
  ];

  services.emacs.defaultEditor = true;
  programs = {
    # xwayland is often useful in wayland
    # xwayland.enable = true;
    kdeconnect.enable = true;
    steam = {
      enable = true;
      remotePlay.openFirewall = true; # Open ports in the firewall for Steam Remote Play
      dedicatedServer.openFirewall = true; # Open ports in the firewall for Source Dedicated Server
      # steam input in wayland
      # translate X11 input events to uinput events
      # extest.enable = true;
    };
    git = {
      enable = true;
      config = {
        user.name = "Robin Wils";
        user.email = "mrwils@tutanota.com";
      };
    };
    ssh.startAgent = true;
  };

  # Allow unfree packages
  nixpkgs.config.allowUnfree = true;

  # wheel does not need password, enable sudo
  security.sudo = {
    enable = true;
    wheelNeedsPassword = false;
  };

  # This value determines the NixOS release from which the default
  # settings for stateful data, like file locations and database versions
  # on your system were taken. It‘s perfectly fine and recommended to leave
  # this value at the release version of the first install of this system.
  # Before changing this value read the documentation for this option
  # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
  system.stateVersion = "23.11"; # Did you read the comment?

  # END CONFIG
}

Home manager & flakes

Flakes and home manager do not have to be hard. Adding extra complexity can be overkill.

Flakes

You only need two steps to start using flakes. One reason you may want to use flakes is to make the system more reproducable. Another reason is that many github projects only provide a NixOS package in the flake format.

You just need to make a simple flake.nix file, and modify nix in the configuration.nix file.

# /etc/nixos/flake.nix
{
  description = "flake for yourHostNameGoesHere";

  inputs = {
    nixpkgs = {
      url = "github:NixOS/nixpkgs/nixos-unstable";
    };
  };

  outputs = { self, nixpkgs }: {
    nixosConfigurations = {
      yourHostNameGoesHere = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          ./configuration.nix
        ];
      };
    };
  };
}
 nix = {
  extraOptions = ''
    experimental-features = nix-command flakes
  '';
};

When you use flakes you do need to use a new command to build your system:

nixos-rebuild --flake .#yourHostNameGoesHere switch

Home manager

Home manager makes even more things reproducible like your dotfiles (configuration for specific programs). The basic things do not require home manager, but people highly recommend it.

The code in the following article assumes you enabled flakes. I currently do not use flakes or home manager yet, and I am temporarily linking to an article for this. I likely could recommend his NixOS book once it is released, and may use home manager in the future. I appreciate configuration suggestions etc.

I may additionally move to wayland once I figure out how to make it work well for my use case, drawing tablets tend to give issues under wayland.

Drake Rossman - Home manager

Share

Diaspora X Facebook LinkedIn

Donate