Chapter 1 - Intro to NixOS
Welcome! This chapter will start you off on your journey with Nix/NixOS.
In this chapter we will…
- Install Nix on your current distro
- Go over using the basic parts of the Nix CLI
- Set up a basic NixOS VM with Nix
- Go over the basics of configuring a NixOS system
Installing Nix (Lix)
Before we even start with NixOS, we’re going to use Nix
to build a VM we can test with. This VM will eventually be the system you’ll install.
We’ll actually be using a fork of Nix called Lix. This fork fixes some common bugs and turns on some features we’ll be using by default.
To install Lix, follow the Lix installation guide.
After installing, check that you can run Nix.
Basic Nix CLI
Nix is a package manager that can run on many systems. One of the nice things it can do is run a package without needing to install it!
We can run Python!
And we can run a shell with Python in scope!
Here we’re asking for python3
from nixpkgs
(the primary package repository). In the first command we simply run the package’s default binary. In the second we bring the package into scope, meaning we can execute any commands it provides.
We’ll get into more later, just keep in mind we can run any package in nixpkgs like this!
Creating a NixOS VM with Nix
Now let’s get into creating a NixOS VM. This VM will eventually become the system you install on your actual device.
To start out, make a new folder to contain this project. In this new folder initialize a new Nix flake.
This should create a new file, flake.nix
.
- flake.nix
A flake is a Nix “project”, it’s written as a nix expression (specifically an attr set).
Opening the flake.nix
we can see the expression. Let’s walk through what each part means.
- We have an attribute named
description
that’s a string. - We have another attribute called
inputs
that’s an attr set. This attr set contains an attribute namednixpkgs
, which is another attr set. Within that we seturl
togithub:nixos/nixpkgs?ref=nixos-unstable
. - We have one more attribute named
outputs
that’s a function. This function takesself
, which is a reference to this flake, andnixpkgs
, which is the input we defined ininputs
. - Within this function we return two packages (placed in the
packages.x86_64-linux
attr set). We sethello
equal to an example package innixpkgs
and we setdefault
to a recursive reference to our ownhello
package.
This is the basic structure of a flake. We have inputs
, which define dependencies for our flake. And we have outputs
, which is a function that will be passed those inputs to output something.
This “something” could be packages (As it is now), NixOS configurations (as we’ll do in a bit), and much more. Flakes are a general purpose way to share Nix code between projects.
Setting Up Flake.nix
We’re going to make a few changes to flake.nix
to prepare for a NixOS config.
-
Remove the template lines that output the
hello
anddefault
packages. -
Create a
let
statement before our return inoutputs
. -
Add
system
to the let block, we want this to bex86_64-linux
as that’s the system we’ll make a VM for (and install on). -
Add
pkgs
to the let block, we’llimport nixpkgs
here, specifying the system we wantnixpkgs
to target.A few new pieces of syntax here.
With these changes we’ve:
- Defined a new variable,
system
that we’ll use in places we need to specify which system to use (like 2 lines later). - Instantiated
nixpkgs
, told it to target oursystem
, and stored it in a variable calledpkgs
.
Writing Our NixOS Config
Now we’ll define a NixOS Config. A NixOS config is a nix expression that instructs on how to build a system. This configures everything from the bootloader and kernel to the DE the system will run.
We’ll make our flake export this NixOS config, allowing us to use it with the Nix CLI (like how nixpkgs
exports python3
).
These steps will use the string YOURNAME
to represent your system’s name, you can set this to whatever you want, I’d recommend a kebab-case
string.
-
First we’ll add the output. Note that the start of the flake file isn’t included here for brevity.
Here we’re making a function call to
nixpkgs.lib.nixosSystem
, we’re passing an attr set as an argument. -
In this attr set, add a basic system configuration. We’ll go over what it means in a sec.
Here we’re doing 2 things
- Inheritting
system
andpkgs
, telling NixOS what system we’re building for and what packages to use. - Declaring an empty NixOS module. We’ll fill this out next.
- Inheritting
-
Configure our NixOS system. In the attr set we’re passing as the only item to the
modules
array, we’ll set some basic options. In this code block,YOURUSER
can be replaced with the username you wish to use, I’d recommend an all lowercase string.Big config here, let’s break it down.
system.stateVersion
sets the major version of NixOS to use.users.users.YOURUSER
configures your account to be a normal user, sets your password toasdf
, and makes you a sudoer.services.qemuGuest.enable
enables some features that’ll make the VM nicer to use.
We now have a very basic config set up, let’s boot it!
Running The VM
We have now configured a basic NixOS VM! With that small Nix expression we’ve represented a minimal NixOS system with one user.
On an actual NixOS system we’d try to switch to this config, but we’ll utilize a different NixOS feature to run a VM instead.
NixOS exports a package under every system config called build.vm
that builds and runs a VM image for us!
Replace YOURNAME
in the following command with the name you configured for your system.
This is similar to what we did with nixpkgs#python3
above. Instead of nixpkgs
we’re grabbing from the flake in the current directory (.
). And the package we want is nixosConfigurations.YOURNAME.config.system.build.vm
.
Run the command and wait for the VM to start. Use the username and password you configured above to login.
We’ve now successfully started a NixOS VM!
Separating Out Our Config
Right now we’re configuring everything within flake.nix
. While this works, it can get a bit messy to have everything in one file as you’ll likely set many options.
To fix this, instead of defining our module inline we can separate it out into another nix file.
- Create a new file called
config.nix
.- flake.nix
- flake.lock
- …
- config.nix
- In this file we’ll put another Nix expression. But unlike
flake.nix
(where the top-level expression is an attr set), we’ll make this top-level expression a function. Here we’re defining a function that takes one argument, an attr set. NixOS will pass many attributes to us through this set, but for now we only wantpkgs
. - Now, we can move the code we previously had inline…
- And move it over to
config.nix
. - Finally, we need to tell NixOS about
config.nix
, we do this by putting it inmodules
.
Run the VM again, make sure the system still builds and works.
Customization
Now for the fun stuff! Let’s try adding a DE to the system.
I’ll use XFCE here for simplicity, but feel free to change this later!
Adding a DE is as simple as this! Opposed to imperatively installing a package like with other distros, we declaratively enable the ones that we want.
We’ll get into how we can find more options and packages later. For now, try running the VM and check out XFCE.
Configuring Nix with Nix
Before we do anything else we’re going to configure Nix within our system. This way when we install, Nix will be setup how we expect it.
Big block here, let’s go over what this all does.
- We disable channels, they’re a different system usually used in place of flakes, but we like flakes so we’ll disable them.
- We set the underlying Nix implementation to be
lix
, just like on our host system. - Next in
settings
we enable a fewnix
options.- We enable a few experimental features, flakes and the new CLI (the CLI you’re using right now).
- We enable nix store opimization, makes stuff more space efficient.
- We configure garbage collection to run automatically once a week, I’ll explain what garbage collection is later.
Installing Programs
When installing programs or enabling features, there are two methods you can use.
Method 1: NixOS Options
Many popular programs or features have NixOS options built-in, this is how we can simply do services.xserver.desktopManager.xfce.enable = true;
for example.
In order to find the options that are available to us, we can use the NixOS Option Search. This will show you all the options that NixOS provides.
Example: Adding Firefox
Let’s say I want to use Firefox on my system. To do this I first look up “firefox” on the option search.
I can see that there’s an option, programs.firefox.enable. Like the name implies this option will enable firefox on my system.
Because NixOS provides an option for this, all I have to do is add it to my config.
When building my system, Nix will fetch the firefox
package from nixpkgs
for me and configure any other OS setting that might make using Firefox better (like setting it as a default browser).
Method 2: Using Nixpkgs Directly
If we can’t find our program through NixOS options search, that means there’s no NixOS options that can install and configure the application for us.
However, we can still use Nix packages search to see if the package we want is still available.
If it is available, you can add the package (gotten trough the pkgs
attr set we receive as an arg) to environment.systemPackages
to install it.
This will install the package and add its commands to your PATH
(as well as add other things, like man pages). However it may not configure other parts of the system to work well with it, you may need to do this manually.
Example: Installing Neofetch
Let’s say I want to install neofetch
on my system, however after checking for a NixOS option for it, it doesn’t exist!
But, after searching in Nixpkgs, I can see it’s listed!
To add neofetch
to my system without having an option for it, I can use environment.systemPackages
.
We now have neofetch
installed!
Experiment
From here I’d recommend trying out programs, options, etc. You now know the basics of configuring NixOS!
However, there’s much you must learn before you can install and daily drive. The following guides will go through some more advanced concepts.