Nix is a purely functional package manager. This means that it treats packages like values in purely functional programming languages such as Haskell — they are built by functions that don’t have side-effects, and they never change after they have been built. Nix stores packages in the Nix store, usually the directory /nix/store, where each package has its own unique subdirectory such as
where b6gvzjyb2pg0… is a unique identifier for the package that captures all its dependencies (it’s a cryptographic hash of the package’s build dependency graph). This enables many powerful features.
You can have multiple versions or variants of a package installed at the same time. This is especially important when different applications have dependencies on different versions of the same package — it prevents the “DLL hell”. Because of the hashing scheme, different versions of a package end up in different paths in the Nix store, so they don’t interfere with each other.
An important consequence is that operations like upgrading or uninstalling an application cannot break other applications, since these operations never “destructively” update or delete files that are used by other packages.
Nix helps you make sure that package dependency specifications are complete. In general, when you’re making a package for a package management system like RPM, you have to specify for each package what its dependencies are, but there are no guarantees that this specification is complete. If you forget a dependency, then the package will build and work correctly on your machine if you have the dependency installed, but not on the end user’s machine if it’s not there.
Since Nix on the other hand doesn’t install packages in “global” locations like /usr/bin but in package-specific directories, the risk of incomplete dependencies is greatly reduced. This is because tools such as compilers don’t search in per-packages directories such as /nix/store/5lbfaxb722zp…-openssl-0.9.8d/include, so if a package builds correctly on your system, this is because you specified the dependency explicitly. This takes care of the build-time dependencies.
Once a package is built, runtime dependencies are found by scanning binaries for the hash parts of Nix store paths (such as r8vvq9kq…). This sounds risky, but it works extremely well.
Nix has multi-user support. This means that non-privileged users can securely install software. Each user can have a different profile, a set of packages in the Nix store that appear in the user’s PATH. If a user installs a package that another user has already installed previously, the package won’t be built or downloaded a second time. At the same time, it is not possible for one user to inject a Trojan horse into a package that might be used by another user.
Since package management operations never overwrite packages in the Nix store but just add new versions in different paths, they are atomic. So during a package upgrade, there is no time window in which the package has some files from the old version and some files from the new version — which would be bad because a program might well crash if it’s started during that period.
And since packages aren’t overwritten, the old versions are still there after an upgrade. This means that you can roll back to the old version:
the package isn’t deleted from the system right away (after all, you might want to do a rollback, or it might be in the profiles of other users). Instead, unused packages can be deleted safely by running the garbage collector:
$ nix-collect-garbage
This deletes all packages that aren’t in use by any user profile or by a currently running program.
Packages are built from Nix expressions, which is a simple functional language. A Nix expression describes everything that goes into a package build task (a “derivation”): other packages, sources, the build script, environment variables for the build script, etc. Nix tries very hard to ensure that Nix expressions are deterministic: building a Nix expression twice should yield the same result.
Because it’s a functional language, it’s easy to support building variants of a package: turn the Nix expression into a function and call it any number of times with the appropriate arguments. Due to the hashing scheme, variants don’t conflict with each other in the Nix store.
Nix expressions generally describe how to build a package from source, so an installation action like
$ nix-env --install --attr nixpkgs.firefox
could cause quite a bit of build activity, as not only Firefox but also all its dependencies (all the way up to the C library and the compiler) would have to be built, at least if they are not already in the Nix store. This is a source deployment model. For most users, building from source is not very pleasant as it takes far too long. However, Nix can automatically skip building from source and instead use a binary cache, a web server that provides pre-built binaries. For instance, when asked to build /nix/store/b6gvzjyb2pg0…-firefox-33.1 from source, Nix would first check if the file https://cache.nixos.org/b6gvzjyb2pg0….narinfo exists, and if so, fetch the pre-built binary referenced from there; otherwise, it would fall back to building from source.
Nix is extremely useful for developers as it makes it easy to automatically set up the build environment for a package. Given a Nix expression that describes the dependencies of your package, the command nix-shell will build or download those dependencies if they’re not already in your Nix store, and then start a Bash shell in which all necessary environment variables (such as compiler search paths) are set.
For example, the following command gets all dependencies of the Pan newsreader, as described by its Nix expression:
$ nix-shell '<nixpkgs>' --attr pan
You’re then dropped into a shell where you can edit, build and test the package:
[nix-shell]$ unpackPhase [nix-shell]$ cd pan-*[nix-shell]$ configurePhase [nix-shell]$ buildPhase [nix-shell]$ ./pan/gui/pan
NixOS is a modern, open-source operating system built on the Nix package manager. It is designed to provide a declarative and purely functional approach to system configuration and package management. NixOS stands out from other Linux distributions due to its unique features and philosophies.
Key Features
1. Declarative System Configuration
NixOS employs a declarative configuration model. System configuration is described in a configuration file, typically /etc/nixos/configuration.nix. This approach allows users to define the desired state of the system explicitly, making it easy to understand and reproduce configurations.
2. Immutable Packages and Rollbacks
Nix, the package manager used by NixOS, manages packages in an immutable fashion. This immutability ensures that packages do not change after installation, promoting consistency and stability. Additionally, NixOS supports easy rollbacks, enabling users to revert to a previous system configuration if needed.
3. Atomic Upgrades
NixOS supports atomic upgrades, meaning system updates are performed as a single, atomic transaction. This ensures that either all updates are applied successfully, or none of them are, preventing potential system inconsistencies.
4. Functional Package Management
Packages in NixOS are built using a functional approach. Each package is isolated and built in its own environment, with all dependencies specified explicitly. This eliminates dependency conflicts and allows for reproducible builds.
5. Nix Expression Language
The Nix package manager and NixOS use a unique functional programming language for configuration called the Nix Expression Language (Nix). Nix provides a powerful and flexible way to describe packages and system configurations.
Use Cases
Development Environments: NixOS is well-suited for creating consistent and isolated development environments, making it easier for developers to manage dependencies.
Server Environments: Its declarative configuration and atomic upgrades make NixOS a strong choice for server setups where reliability and maintainability are crucial.
Reproducible Systems: NixOS is ideal for creating reproducible systems, ensuring that a system’s state can be replicated across different machines.
Point of use
Linux distribution based on Nix Package manager
Supports declarative reproducible system configuration
“Unbreakable”
Boot to specific configuration generations. (as mentioned above → reproducible)
nix-store: no /lib & /usr/lib. almost non-existant /bin & /usr/bin → /nix/store
nix-env: install packages at user level without having to change system state
Conclusion
NixOS provides a unique and powerful approach to operating system configuration and package management. Its declarative model, immutable packages, and functional design make it a compelling choice for those seeking reliability, consistency, and ease of management in their computing environments. Whether for development or production use, NixOS offers a fresh perspective on how we interact with and manage operating systems.
Warning: be extremely careful with disk labels when following this tutorial. Devices and partitions may appear under different labels on your system. Using commands like dd or cryptsetup luksFormat will cause permanent data loss! Use lsblk in order to check disk labels if at all unsure.
All commands must be run as the root user. This may be done using sudo, or by opening a root shell. In order to open a root shell, simply run:
sudo su root
Prepare Disks
This step is entirely optional. We will prepare the disk devices by erasing them. We will first zeroise/dev/sdb, in order to completely wipe it.
dd if=/dev/zero of=/dev/sdb status=progress
Next, we will wipe /dev/sda by filling it with random data.
dd if=/dev/urandom of=/dev/sda status=progress
The reason we wipe the primary device by filling it with random data, instead of simply zero-ising it, is to make the size of the encrypted content undeterminable.
Now /dev/sda and /dev/sdb are fully prepared. We are ready to create partition tables, and partitions within them.
Create Partitions
We will have the following partition scheme. The specific method (or scenario) that we will implement for our Full Disk Encryption (FDE) is to mount an LVM (Logical Volume Manager) on top of our LUKS2 Container. LVM volumes are then created to reflect the different partitions on our root filesystem. This method is referred to as LVM on LUKS and is a common method of implementing FDE on Linux devices.
/dev/sdaPrimary device
/dev/sda1 LUKS2 Container crypted
LVM Volume Group vg
LVM Logical Volume vg-swap
LVM Logical Volume vg-nixos
and any other partitions…
/dev/sdbPortable device
/dev/sdb1boot Partition
/dev/sdb2 LUKS2 Detached Header
The underlying root \, \home, and swap partitions will be created as LVM virtual volumes within the LUKS2 container.
We will first proceed to make the partition table and partition for the primary /dev/sda device, then for the portable /dev/sdb device. We will be using parted as the tool to create partitions.
Create a gpt partition table for /dev/sda. Other partition table formats like msdos are not necessary.
parted /dev/sda -- mklabel gpt
Create primary partition for /dev/sda. This will take up all of the space on the primary /dev/sda device, since it will hold a LUKS2 encryption container. Other partitions (such as swap, or /home) will be created within the encryption container.
parted /dev/sda -- mkpart primary 0% 100%
The partition setup for the primary /dev/sda device is now complete. We will now proceed to partition the /dev/sdb portable device (i.e. the USB, MicroSD card).
Make boot partition on portable device /dev/sdb
Create a gpt partition table for /dev/sdb.
parted /dev/sdb -- mklabel gpt
Create boot partition for /dev/sdb. This is the unencrypted partition that will contain the bootloader for the operating system. It will reside on the portable device, which should be stored securely when not in use.
parted /dev/sdb -- mkpart ESP fat32 0% 50%
We must set some flags to indicate that this partition contains the bootloader.
parted /dev/sdb -- set 1 boot on
Now we will use the remaining space on the portable device to create a partition for the LUKS2 detached header. This partition will not actually contain a filesystem, since the LUKS2 detached header will be read as a raw header.
parted /dev/sdb -- mkpart primary 50% 100%
I choose to devote the remaining 50% of the device’s space, simply because the MicroSD card will not be used for any other purpose. However, in practice a smaller partition can be allocated for the LUKS2 detached header. Unlike LUKS1, the detached header does not have a static, fixed size. However in practice a partition of 16MB should be more than enough.
Make filesystem for boot partition
We will make a FAT32 filesystem for the boot partition. We are using FAT32 instead of ext4 for greater compatibility with bootloaders.
mkfs.fat -F 32 -n boot /dev/sdb1
It is not necessary to make a filesystem for the LUKS2 header partition /dev/sdb2. This is because the LUKS2 header will reside as a raw header without any underlying file system. This avoids the complications of the bootloader having to mount a filesystem before reading the header file.
Make LUKS2 Encryption Container with detached headers
We will now use the cryptsetup command to create the LUKS2 Encryption container. We will invoke the luksFormat action on /dev/sda1 in order to do so. The command comes with a set of sensible and sane cryptographic defaults, however you may choose to explore further configuration options if you desire.
The location of the LUKS2 wrapper is on the primary hard drive /dev/sda1.
The LUKS2 header which contains the metadata is on raw partition /dev/sdb2
This command will prompt you to enter a password. This password will be used to unlock the primary device when the computer boots.
A LUKS2 container has been created on /dev/sda1, with it’s corresponding header file located at /dev/sdb2.
In order to use this container, we must unlock it using the following command. You will be asked to input the decryption password from the previous step.
Now the container will be available as crypted, at /dev/mapper/crypted.
Create a LVM Volume within the LUKS2 Container
We will now create a set of LVM volumes within the LUKS2 container. This way we may have other multiple logical partitions within the container itself.
First, we will initialise the physical volume crypted. This step is necessary in order to create logical volumes within it.
pvcreate /dev/mapper/crypted
Next, we will create a volume group upon the newly-initialised physical volume. This volume group will be called vg.
vgcreate vg /dev/mapper/crypted
Now that the volume group is made, we can make arbitrary logical volumes. These logical volumes correspond to the unencrypted partitions of a traditional Linux installation.
Although it is possible to make multiple partitions corresponding to different mount points (such as /home, /etc, var, etc), in practice this is not necessary. This is because the main benefit of having a separate /home partition is easier recovery, where the /home partition can be mounted separately in a rescue process.
Because all partitions reside within the encrypted LUKS2 container, which must be unlocked for access, there is no benefit to having separate partitions. Hence we will only create a root and swap partition.
Create a swap partition within the LVM volume
Create a swap partition with the label swap. Note that we use the option -L which denotes a numerical size. In the following command, a 16 GB swap partition is created.
lvcreate -L 16G -n swap vg
There are differing opinions on the appropriate size for a swap partition on modern Linux. In particular, if you wish to to enable hibernation you must have the same amount of swap as your RAM.
Create a root partition within the LVM volume
Create a root partition with the label nixos using the remaining free space. Note that we use the option -l which denotes a percentage.
lvcreate -l '100%FREE' -n nixos vg
Now the LVM volumes are created, and ready to be used.
Create Filesystem and Swap
After creating the volumes, we must create filesystems that will reside on them. For this guide, we will use a relatively ordinary ext4 filesystem. You may substitute more exotic filesystems such as ZFS or btfs if desired.
After creating the filesystems, we will mount them (and activate swap).
Mount filesystems
Mount the root partition
mount /dev/disk/by-label/nixos /mnt
Mount the boot partition
mkdir -p /mnt/bootmount /dev/sdb1 /mnt/boot
Activate swap
swapon /dev/vg/swap
Now our filesystems are mounted. The future NixOS installation’s root will be accessible at /mnt, and it’s boot partition at /mnt/boot.
Configure NixOS
We can now instruct NixOS to generate a set of configuration files for our installation. Make sure to pass the --root /mnt flag, in order to indicate where the root filesystem resides.
nixos-generate-config --root /mnt
Now the configuration files will be available at /mnt/etc/nixos. We must modify this file in order to add the appropriate settings.
Configure configurations.nix
We can now specify the configuration of the NixOS installation using configurations.nix. The included example configuration file has various options that can be explored. In particular, you should check out the following:
Once generic configurations are complete, we must add the specific boot.initrd.luks.devices settings which will allow the system to boot.
The following section must be added. We are specifying for NixOS that there is a dictionary of boot.initrd.luks.devices, where there exists a device crypted with configuration options enclosed in another dictionary.
# Configuration options for LUKS Deviceboot.initrd.luks.devices = { crypted = { device = "/dev/disk/by-partuuid/<PARTUUID of /dev/sda1>"; header = "/dev/disk/by-partuuid/<PARTUUID of /dev/sdb2>"; allowDiscards = true; # Used if primary device is a SSD preLVM = true; };};
We must specify the device directive which is a path that points to the encrypted primary partition /dev/sda1, as well as header which is a path that points to the LUKS2 Header located on /dev/sdb2.
These paths can be specified in more than one way. You can see the various ways that this path is specified by listing the subdirectories in /dev/disk.
I recommend specifying the paths using /dev/disk/by-partuuid instead of /dev/disk/by-uuid, because /dev/sda1 does not have a uuid assigned to it, since it doesn’t have a filesystem.
Hence in order to find the UUIDs of /dev/sda1 and /deb/sdb2, run:
blkid /dev/sda1 -s PARTUUID
Or you may simply run ls -l /dev/disk/by-partuuid
Installation
After setting the configuration options, it is time to install the device. You will need to have an internet connection, as NixOS will be downloading and compiling sources. Either connect to an ethernet cable, or a WiFi network.
Now run nixos-install. We will specify the option --cores 0 to let NixOS use all CPU cores when compiling binaries.
nixos-install --root /mnt --cores 0
This command will take anywhere from 5 to 25 minutes, depending on the configuration of the system and the speed of the internet connection.
Once the installation is nearly complete, it will prompt you to set a root password for the newly installed system. After setting the password, the installation is complete.
reboot
Post-Installation
When the installation is complete, perform a reboot. Now you must enter the BIOS/UEFI interface of your computer’s firmware, and change it’s boot settings to use the bootloader located on the portable device /dev/sdb1 by default.
Now your NixOS installation is complete
Initial configuration
Generate
Generate default configuration:
nixos-generate-config --root /mnt
Location:
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 setupboot.loader.grub.enable = true;boot.loader.grub.version = 2;boot.loader.grub.device = "/dev/vda": # if virtual machine# Dual booting made easy (Optional)boot.loader.grub.userOSProber = true;# Dual booting made a bit harder (Extra Optional)boot.loader.grub.extraEntries = '' menuentry "Windows 10" { chainloader (hd0,1)+1 }'';
OR
boot.loader.grub = { enable = true; version = 2;};
UEFI
Used for larger boot drives and dual booting with windows
# Default UEFI setupboot.loader.systemd-boot.enable = true;boot.loader.efi.canTouchEfiVariables = true;# Dual booting using grubboot.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
{ pkgs, ... }:{ boot = { kernelPackages = pkgs.linuxPackages_latest; # Get latest kernel initrd.kernelModules = ["amdgpu"]; # More on this later on (setting it for xserver) loader = { #efi = { #canTouchEfiVariables = true; #efiSysMountPoint = "/boot/efi"; #}; grub = { #enable = true; #devices = ["nodev"]; #efiSupport = true; #useOSProber = true; configurationLimit = 5; #Limit stored system "backup configuration". }; #Also exists for systemd-boot timeout = 5; #Work for grub and efi boot, time before auto-boot }; };}
Networking
Uncomment: networking.hostName=“nixos”;
networking.hostName=“nixos”;
networking.wireless.enable = true; or networking.networkmanager.enable = true; // for laptop or wireless chose one
Network card details
//could.be.moved.to.hardware-configuration.nix
Deprecated but keep: networking.useDHCP = false;
Just internet via ethernet: networking.interfaces.<networkcard-id%>.useDHCP = true;
NixOS is a modern, open-source operating system that provides a declarative and purely functional approach to system configuration and package management. This guide will walk you through setting up a NixOS server.
Getting Started
Before you begin with the installation and configuration, make sure you have the necessary hardware and a basic understanding of Linux concepts and server management.
![[OS/linux/distros/nixos/NixOS#NixOS#Initial configuration]]]
Initial Configuration
Ensure that you have a clear understanding of the system requirements and the specific configuration you want to achieve for your NixOS server. Decide on the necessary hardware specifications, network settings, and other relevant parameters.
FROM nixos/nix
RUN nix-channel --update
RUN nix-build -A pythonFull '<nixpkgs>'
Docker Pull Command
docker pull nixos/nix
Limitations
By default sandboxing is turned off inside the container, even though it is enabled in new installations of nix. This can lead to differences between derivations built inside a docker container versus those built without any containerization, especially if a derivation relies on sandboxing to block sideloading of dependencies.
To enable sandboxing the container has to be started with the --privileged flag and sandbox = true set in /etc/nix/nix.conf.
Config
Extras
Explore additional features and optimizations for your NixOS server:
Setting Up Services
Configure and manage services on your NixOS server, such as web servers, databases, and more.
Customizing Packages
Learn how to customize packages and manage dependencies using the Nix package manager.
Security Measures
Implement security best practices to protect your NixOS server from potential threats.
Monitoring and Logging Set up monitoring tools and log management to keep track of server performance and activities.
Home-Manager
Introduction
It’s like configuration.nix, but for the user environment.
Plenty more option to declare packages
Also a better way to manage dotfiles
“sudo” nix but home-manager “its not a sudo so only for the user and more option”
If not declared and for exeple alacrity gets a big update and the way they set up their yaml file is different or they use another way of configuring this will break
Stored files (also with no link to NixOS)
home.file.".domm.d" = { source ./doom.d; # Where is it recurcive = true; # Link any original file or link onChange = buildins.readFile ./doom.sh; # if change };home.file.".config/polybar/script/mic.sh" = { source = ./mic.sh; executable = true;};