Nix Mystery
Table of Contents
I’ve been wanting to use nix for a long time, and it seems that the time has finally come.
Nix always has been something familiar but unknown to me for a while. I know Nix lets you manage packages in a declarative fashion and it is reproducible.
But in all honesty, these are just buzz words which I really don’t understand just yet. So, I’ll just look into what actually Nix is.
Setup
search?q=nix
Aparently, Nix is a package manager that works on a functional programming language, called Nix, which is interesting as I’m interested in functional programming.
I’m obviously not going to opt into using nixOS or nix for my host system just right now. For now, the middleground for me seems to just use nix in my host system to build a nixOS system and run it on QEMU.
PS: QEMU is a great tool to emulate a PC environment.
pacman -Q nix
Something I realized off the boat was that for some reason, nix has majority of their “useful” utilities and tool under an experimental feature, which seems quite odd.
sudo pacman -S nix
I’ll have to make sure that locked features like nix-shell and flake, I’ll have to add to:
/etc/nix/nix.conf
experimental-features = nix-command flakes
nix –help
So aparently, nix uses flakes for packaging and reproducing builds, which seems analogous to cargo for rust. As, the nix package repository stores all the nix packages in form of nix expressions of sometimes a flake itself.
nix flake init
So, looking into how nix works, it seems to me that I’ve gone ahead and built a simple hierarchy of how my files are structured and how the flake is going to look, and named the configuration kanto.
nix flake init
├── flake.lock
├── flake.nix
├── hosts
│ └── kanto
│ ├── default.nix
│ ├── hardware.nix
│ ├── services.nix
│ └──users.nix
├── justfile
├── README.md
└── result -> /nix/store/jibberish-hash
Directories:
hosts/ - Holds configuration for multiple hosts
kanto/ - Holds configuration for kanto
Files:
default.nix - entrypoint
hardware.nix - fs and bootloader
users.nix - user config
services.nix - daemons
Configuration
{…} : {}
Since, the most interesting part of nix is about, how I can “declare” my OS. So, I ended up writing:
default.nix
{ config, pkgs, ... }:
{
imports = [ ./hardware.nix ./users.nix ./services.nix ];
networking.hostName = "kanto";
time.timeZone = "Asia/Kathmandu";
system.stateVersion = "24.11";
}
{config,pkgs, ...}: :
This essentially introduces my flake’s function which takes in a config, pkgs, and a variadic.
{ bunch of declarations } : This is the block, this returns an object with import, networking, time and system fields, which define our system.
nix build
This makes our configuration able to run on QEMU. Let’s try to build it:
nix run nixpkgs#nixos-rebuild -- build-vm --flake .#kanto
‘#’ here is the fragment specifier. But I liked to think of it as a package access specfier, like accessing the package from a flake.
It works, but I might’ve cheated, as I’ve already written some declarations in some of the files.
hardware.nix
{ config, ... }:
{
boot.loader = {
grub = {
enable = true;
efiSupport = true;
device = "nodev";
};
efi.canTouchEfiVariables = true;
};
fileSystems."/" = {
device = "/dev/disk/by-label/nixos";
fsType = "ext4";
options = [ "noatime" ];
};
fileSystems."/boot" = {
device = "/dev/disk/by-label/BOOT";
fsType = "vfat";
};
swapDevices = [
{ device = "/dev/disk/by-label/swap"; }
];
}
This defines my hardware, currently assumed that my system has simple dual partitions: and also have assumed a swap.
"/" - primary partition
"/boot" - boot partition
The fact that if you’ve understood the syntax of nix, this is very much readable.
Nix actually stores each of its rebuilds as generations and allows the user to boot into any of its builds, hence making it extremely tolerant to any faulty builds.
users.nix
{config,pkgs,...}:
{
users.users.irhs = {
isNormalUser = true;
description = "Admin";
extraGroups = [ "networkmanager" "wheel" ];
openssh.authorizedKeys.keys= [
"your-ssh-pub-key"
];
};
programs.zsh.enable = true;
}
Here, we’re just declaraing a simple user called “irhs” with hierarchy as:
users = {
users = {
irhs = {
# above configurations
}
}
}
This structure has a high resemblance to recursive structures defined in LISP.
'(( users . (( users . (( shri . (( ; above configuration )) )) )) ))
This essentially shows how the origin of nix was highly inspired from LISP and how it has vastly simplified it. In my mind, I’ve mostly been reiterating how nix seemed really familiar with my time configuring emacs.
Now for the final part, I have to ensure that I am able to remotely access the machine, so I have to setup a sshd,
for which I’ll have to populate services.nix.
services.nix
{ config, pkgs, ... }:
{
services = {
openssh = {
enable = true;
settings.PermitRootLogin = "no";
settings.PasswordAuthentication = true;
};
};
}
This is relatively straight forward, and now if I build the OS.
building the system configuration...
Done. The virtual machine can be started by running /nix/store/2b59ryh0v0fan4wr8q0b04b2zniz4srp-nixos-vm/bin/run-kanto-vm
run-kanto-vm
Actually, all the packages that I intend on adding to my system in the future, are actually stored in a central repository
/nix/storein my system. And it successfully boots into NixOS, but that is not our goal. We’ll have to try to ssh into it.

And well it works as well.

Finishing up
Nix isn’t just a package manager; it’s an abstraction layer over the messiness of Linux.
By treating my OS like my emacs configuration, with simpler syntax and large amount of packages. I can avoid imperative deficiencies like failing commands and expecting functional reliability of declaraing my operating system.
For anybody looking into this article, this is not something you should follow, I’ve only written this as a note to myself and to understand about Nix a little bit more.