Getting Started with NixOS
Table of Contents
Introduction #
NixOS is a Linux distribution based on the Nix package manager. The main reason for using NixOS is the declarative reproducibility of your system.
NixOS allows you to declare pretty much everything about your system inside a single document. This document allows for the reproducable setup that can be used on any system.
Whenever this document gets updates, NixOS will generate a new generation which can be booted into. Since NixOS is known for being unbreakable, it will also store the previous generations just in case the new changes are causing issues.
Unlike the average Linux destribution, NixOS stores all it’s packages inside the /nix/store
. Directory /lib
, /usr/lib
, /bin
and /usr/bin
are pretty much non-existant.
In this guide I would go into to much detail about setting up the installatia media but by following the steps below, you’ll end up with a decent understanding on how to configure your own declarative reproducible NixOS setup. I highly recommend that you use these notes in conjunction with the mini-course video to learn more about NixOS.
Getting Started #
- Download the iso and burn it to a bootable device .
- Boot into device with iso.
Either follow the Calamares Installer or the step below:
Partitioning
- Open the terminal and switch to the root user
su
- Run the commands below:
- This will format the disk and create the partitions
- Change 8GiB swap to your personal preference. Or remove it and end the root partition at 100%.
- Choose between the two options between “< >”. Left option is legacy boot, right option is UEFI.
- Change
/dev/sda
to the disk used to install NixOS.
- This will format the disk and create the partitions
# parted /dev/sda -- mklabel <msdos/gpt>
# parted /dev/sda -- mkpart primary <1MiB -8GiB/512MiB -8GiB>
# parted /dev/sda -- mkpart primary linux-swap -8GiB 100%
/* extra for UEFI */
# parted /dev/sda -- mkpart ESP fat32 1Mib 512MiB
# parted /dev/sda -- set 3 esp on
# mkfs.ext4 -L nixos /dev/sda1
# mkswap -L swap /dev/sda2
/* extra for UEFI */
# mkfs.fat -F 32 -n boot /dev/sda3
Mounting
# mount /dev/disk/by-label/nixos /mnt
/* extra for UEFI */
# mkdir -p /mnt/boot
# mount /dev/disk/by-label/boot /mnt/boot
# swapon /dev/sda2
Initial Configuration #
- Generate a default configuration:
# nixos-generate-config --root /mnt
- Location of configuration.nix:
# cd /mnt/etc/nixos
Configuration.nix #
General
- Argument on how to evaluate config:
{config, pkgs, ...}:
- Pull in other files used within the config:
import = [./hardware-configuration.nix];
Boot
Legacy
- Only viable if dualbooting linux distributions*
# Default Grub setup
boot.loader.grub.enable = true;
boot.loader.grub.version = 2;
boot.loader.grub.device = "/dev/vda";
# Dual booting made easy (Optional)
boot.loader.grub.useOSProber = true;
# Dual booting made a bit harder (Extra Optional)
boot.loader.grub.extraEntries = ''
menuentry "Windows 10" {
chainloader (hd0,1)+1
}
'';
UEFI
- Used for larger boot drives and dual booting with Windows*
# Default UEFI setup
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
# Dual Booting using grub
boot.loader = {
efi = {
canTouchEfiVariables = true;
efiSysMountPoint = "/boot/efi"; # /boot will probably work too
};
grub = { # Using grub means first 2 lines can be removed
enable = true;
#device = ["nodev"]; # Generate boot menu but not actually installed
devices = ["nodev"]; # Install grub
efiSupport = true;
useOSProber = true; # Or use extraEntries like seen with Legacy
}; # OSProber will probably not find windows partition on first install
};
Extras
- Some extra useful boot parameters
{ pkgs, ... }:
{
boot ={
kernelPackages = pkgs.linuxPackages_latest; # Get latest kernel
initrd.kernelModules = ["amdgpu"]; # More on this later on (setting it for xserver)
loader = {
grub = {
configurationLimit = 5; # Limit stored system configurations.
}; # Also exists for systemd-boot
timeout = 5; # Work for grub and efi boot, time before auto-boot
};
};
}
Internationalisation
- Locales, Layouts and Options
# Clock
time.timeZone = "Belgium/Brussels";
# Locale
i18n.defaultLocale = "en_US.UTF-8";
i18n.extraLocaleSettings = {
LC_TIME = "nl_BE.UTF-8";
LC_MONETARY = "nl_BE.UTF-8";
};
# TTY layout
console = {
font = "...";
keyMap = "..."; # us / fr / azerty / etc...
};
# XServer layout (possibly also sets console now)
services.xserver.layout = "..." # us / fr / be / etc..
# Extra keyboard settings:
services.xserver.xkbOptions = "eurosign:e"; # For example adds €
DE/WM
- Default
services.xserver.enable = true;
services.xserver.displayManager.sddm.enable = true;
services.xserver.desktopManager.plasma5.enable = true;
- Customized
services = {
xserver = {
enable = true;
displayManager = {
lightdm.enable = true;
defaultSession = “none+bspwm”;
};
desktopManager.xfce.enable = true;
windowManager.bspwm.enable = true;
};
};
Hardware
- Example for sound and bluetooth
sound = {
enable = true;
mediaKeys.enable = true;
};
services = {
pipewire = {
enable = true;
alsa = {
enable = true;
support32Bit = true;
};
pulse.enable = true;
};
};
hardware = {
bluetooth = {
enable = true;
hsphfpd.enable = true; # HSP & HFP daemon
settings = {
General = {
Enable = "Source,Sink,Media,Socket";
};
};
};
};
- Example of libinput for touchpads
services.xserver.libinput = {
enable = true;
#tapping = true;
#naturalScrolling = true;
#...
Users
- Example on how to create a user and add them to groups.
users.users.<name> = {
isNormalUser = true;
extraGroups = [ "wheel" "video" "audio" "networkmanager" "lp" "scanner"]
#initialPassword = "password";
#shell = pkgs.zsh;
};
Packages
- Example of how to declare packages
environment.systemPackages = with pkgs; [
vim
wget
git
#pkgs.firefox
firefox
StateVersion
- No need to touch this.
- Nothing to do with the version of the system.
- Just tells the version of state/config
- Can be updated to a stable version if you are really sure.
Hardware-configuration.nix #
Generate
- Also get automatically generated with:
# nixos-generate-config --root /mnt
- Should detect mounted drives, device parts, kernelModules, etc.. that are needed
- Can be deleted and regenerated with:
# nixos-generate-config
File System
$ sudo blkid
- or just look in gparted
fileSystems."/" =
{ device = "/dev/disk/by-uuid/e97ad9a8-d84f-4710-b8c9-cfa7707510ca";
fsType = "ext4";
};
#fileSystem."/" =
# { device = "/dev/disk/by-label/nixos";
# fsType = "ext4";
# };
Networking
- Network card details
- Deprecated but keep:
networking.useDHCP = false;
- Just internet via ethernet:
networking.interfaces.<networkcard-id>.useDHCP = true;
- Deprecated but keep:
networking = {
#hostName = "nixos";
#networkmanager.enable = true;
interfaces ={
enp0s3 = {
#useDHCP = true;
ipv4.addresses = [ { # Ofcourse not compatible with networkmanager
address = "192.168.0.50";
prefixLength = 24;
} ];
};
};
defaultGateway = "192.168.0.1";
nameservers = [ "1.1.1.1" ];
};
Installation #
- For the initial installation:
# nixos-install
- After applying changes to your configuration.nix:
# nixos-rebuild switch
- At the end of the installation process, pick a root password.
- Reboot
- If
users.users.<user>.initialPassword
was not set, you will need to do this now via the TTY- Ctr + Alt + F<1-12> -> Log in with root
# passwd <user>
- Ctrl + Alt + F1 or F7 -> Log into graphical environment with user
Package Management #
Nix packages and options can be found here: packages | options
- Install individually with Nix Package Manager
$ nix-env -iA nixos.<package>
$ nix-env --uninstall <package>
- Temporarily install packages using
$ nix-shell -p <package>
Declaring Packages
- Install systemwide packages with configuration.nix
environment = {
systemPackages = with pkgs; [
plex
superTux
];
};
nixpkgs.config.allowUnfree = true;
Declaring Options
- Some packages will also have options to configure it further
services = {
plex = {
enable = true;
openFirewall = true;
};
};
Declaring Options
- Values that can change often or you want to use multiple times
let
rofi-theme = {
"*" = {
bg = "#FFFFFF";
};
};
in
{
programs.rofi = {
enable = true;
theme = rofi-theme;
};
}
Declaring Options
- Change packages that already exist in nix
nixpkgs.overlays = [
(self: super: {
sl = super.sl.overrideAttrs (old: {
src = super.fetchFromGitHub {
owner = "mtoyoda";
repo = "sl";
rev = "923e7d7ebc5c1f009755bdeb789ac25658ccce03";
sha256 = "0000000000000000000000000000000000000000000000000000";
};
});
})
(self: super: {
discord = super.discord.overrideAttrs (
_: { src = builtins.fetchTarball {
url = "https://discord.com/api/download?platform=linux&format=tar.gz";
sha256 = "0000000000000000000000000000000000000000000000000000"; #52 0's
}; }
);
})
];
Applying
$ sudo nixos-rebuild switch
Extras #
Update & Upgrade
$ nix-channel --add https://nixos.org/channels/nixos-21.11
OR$ nix-channel --update
- Next rebuild,use the –upgrade flag
$ sudo nixos-rebuild --upgrade
If Apps are installed through nix-env $ nix-env -u '*'
Garbage Collections
- Remove undeclared packages, dependencies and symlinks:
$ nix-collect-garbage
- Remove above of older generations:
$ nix-collect-garbage --delete-old
- List generations:
$ nix-env --list-generations
- List generations:
- Remove specific generations or older than … days:
$ nix-env --delete-generations 14d
$ nix-env --delete-generations 10 11
- Optimize store:
$ nix-store --gc
- All in one:
$ nix-collect-garbage -d
Inside configurations.nix
nix = {
settings.auto-optimise-store = true;
gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 7d"
};
};
Home-Manager #
Home-Manager
is configuration.nix but for the user environment.
Instead of installing packages system-wide, it will install packages only for a specified user.
It has plenty more options to declare if your configuration.nix and it’s a great way to configure and manage your ~/.config
dotfiles.
All configurations options can be found in the
Home-Manager
Appendixes
.
Home-Manager can be set up in multiple way. Below you will find some of the option on how to implement it in your configuration.
Before getting started with Home-Manager, a nix-channel will need to be created so the system can make use of the new configuration options. If you plan on using Home-Manager as a NixOS-module, run the commands below as root.
Unstable channel: $ nix-channel --add https://github.com/nix-community/home-manager/archive/master.tar.gz home-manager
Stable channel: nix-channel --add https://github.com/nix-community/home-manager/archive/release-<version.number>.tar.gz home-manager
And update the channels with: $ nix-channel --update
Standalone #
Installation is done by running the command: $ nix-shell '<home-manager>' -A install
You can nog edit the home.nix file in ~/.config/nixpkgs/home.nix
Module #
Implement the options below into your configuration.nix. You can afterwards nixos-rebuild switch
.
let
in
{
imports = [ <home-manager/nixos> ];
users.users.<name> = {
isNormalUser = true;
}
home-manager.users.<name> = { pkgs, …}: {
# declared packages. for example:
home.packages = [ pkgs.atool pkgs.httpie ];
};
}
Configuration #
Declaring packages
home.packages = with pkgs; [
firefox
];
services.dunst = {
enable = true;
};
To apply the changes to the standalone version: $ home-manager switch
Dotfiles
- Implement existing config inside the nix file
home.file = {
".config/alacritty/alacritty.yml".text = ''
{"font":{"bold":{"style":"Bold"}}}
'';
};
- Implement with files saved locally
home.file.".doom.d" = {
source = ./doom.d;
recursive = true;
onChange = builtins.readFile ./doom.sh;
};
home.file.".config/polybar/script/mic.sh"={
source = ./mic.sh;
executable = true;
};
- Declarativly with existing options,\
{
xsession = {
windowManager = {
bspwm = {
enable = true;
rules = {
"Emacs" = {
desktop = "3";
follow = true;
state = "tiled";
};
".blueman-manager-wrapped" ={
state = "floating";
sticky = true;
};
};
};
};
};
}
Flakes #
Flakes are a fully supported feature of the NixOS.
With flakes you can specify code dependencies declaratively. These will be stored inside a flake.lock
file.
An example of a code dependency is home-manager.
Flakes make rebuilding and updating the whole system a lot easier.
Arguably it makes it way easier to build your own config. You can save multiple configs inside one file and share them easily with others using something like GitHub.
It’s highly recommended that you have a look at the wiki article about flakes.
Setup #
Inside the configuration.nix add:
nix = {
package = pkgs.nixFlakes;
extraOptions = "experimental-features = nix-command flakes";
};
Now choose a location where you want to store the flake file and run:
$ nix flake init
Inside the flake.nix file you will find the optionsinputs
andoutputs
. The inputs are used to declare your dependencies. Of of these dependencies is, for example, the nix packages itself.
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
};
The outpus are the arguments that can be used that reference the imputs. This can be:
- Everything you imported
- Packages / configurations / modules / options / etc …
Configuration #
An example of a basic flake.nix file is:
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
#nixpkgs-unstable.url = "github:nixos/nixpkgs/nixpkgs-unstable";
};
outputs = { nixpkgs, home-manager, … }:
let
system = “x86_64-linux”;
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
};
lib = nixpkgs.lib;
in {
nixosConfigurations = {
<user> = lib.nixosSystem {
inherit system;
modules = [ ./configuration.nix ];
};
#<second user> = lib.nixosSystem {
#inherit system;
#modules = [ ./configuration.nix ];
#};
};
}
In this example the configuration.nix and hardware-configuration.nix file is move to the same directory as the flake.nix file.
To update the system using the flake.nix file run: $ sudo nixos-rebuild switch --flake .#
Home-Manager #
Once again, Home-Manager can be set up in multiple ways inside the flake.
Seperate
{
inputs = {
#other inputs
home-manager = {
url = github:nix-community/home-manager;
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, home-manager, ... }:
let
#variables
in {
#other outputs
hmConfig = {
<user> = home-manager.lib.homeManagerConfiguration {
inherit system pkgs;
username = “<user>”;
homeDirectory = “/home/<user>”;
#stateVersion = "22.05"; # If there is any complaining about differing stateVersions, specifically state here.
configuration = {
imports = [
/home/<user>/.config/home/home.nix
];
};
};
};
};
}
Inside nixosConfigurations
{
inputs = {
#other inputs
home-manager = {
url = github:nix-community/home-manager;
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, home-manager, ... }:
let
#variables
in {
nixosConfigurations = {
<user> = lib.nixosSystem {
inherit system;
modules = [
./configuration.nix
home-manager.nixosModules.home-manager {
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.<user> = {
imports = [ ./home.nix ];
};
}
];
};
};
};
}
Build
If you chose for the seperate options, you will have to rebuild your system once for home-manager and once for flake itself. You will first need to build your home-manager config and afterwards switch.
$ nix build .#hmConfig.<user>.activationPackage
$ ./result/activate
These command only need to be run on your initial installation. From now on you can switch to the new home-manager configuration using:home-manager switch --flake .#<host>
If you chose for the nixosConfigurations module just rebuild as usual: $ sudo nixos-rebuild switch --flake .#<host>
Update Since all dependencies are now declared, you will first need to update the flake.lock file before you can rebuild the system.
$ nix flake update
$ sudo nixos-rebuild switch --flake .#<host>
Conclusion #
By now, you should have a decent grasp of how NixOS works. If some notes are not clear, I highly recommend that you watch the 3 hour mini-course where I go into much more detail.