Introduction

Warning This is an attempt to create workshops that can be done by complete beginners without having NixOS installed but only Nix.

The main idea is to provide a chain of workshops that do not require any Nix knowledge at the begining. Each step allows to talk about a little part of Nix & NixOS domain without the need of a full understanding. This learn by doing approach should break down the steep learning curve of Nix.

Each workshop should allow the "speaker" to drive the attendees with a live code session. Those workshops are not documented enough to be led by someone that does not have at least a small experience with Nix.

Warning The workshops have only been tested on NixOS and Ubuntu.

Nix installation

Requirements

  • Linux, MacOS, Windows (WSL2)

ToDo

Steps

For Linux users with install Nix:

sh <(curl -L https://nixos.org/nix/install) --daemon

Enable expirmental features but not so expiremental:

mkdir -p ~/.config/nix
echo "experimental-features = nix-command flakes" >> ~/.config/nix/nix.conf

Don't forget to restart nix-daemon.

systemctl restart nix-daemon

Let's play a litte bit with Nix:

  • Language
  • Channels
  • Nixpkgs
  • Repl

Minimal image

Requirements

ToDo

Steps

Let's initialize a flake:

nix flake init

Nix create a flake.nix file containing a minimal setup.

We need to lock our flake to define the main dependency nixpkgs:

nix flake lock

Repl exploration:

nix repl
:lf . #Load Nix flake
packages # hit tab for autocomple
packages.x86_64-linux # hit tab

packages.x86_64-linux.default # hit enter

:e packages.x86_64-linux.default # To open derivation

:b packages.x86_64-linux.default # To build derivation

:q # To exit

Show package content in store:

tree /nix/store/1pry7pnxqig0n2pkl4mnhl76qlmkk6vi-hello-2.12.1

Build packages:

nix build # to build the default package
nix build .#hello # to build the hello package
nix build nixpkgs#cowsay #to build a package from nixpkgs

Run packages:

nix run
nix run nixpkgs#cowsay hello

Look at the Flakes feature documentation: https://nixos.wiki/wiki/Flakes

Add nixpkgs channel to inputs:

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

Update flake lock file:

nix flake lock

To create a system configuration we need to use nixpkgs.lib.nixosSystem:

{
  outputs = { self, nixpkgs, ... }: {
    nixosConfigurations.default = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [];
    };
  };
}

Let's try to check if our configuration is valid with:

nix flake check

Output:

error:
       Failed assertions:
       - The ‘fileSystems’ option does not specify your root file system.
       - You must set the option ‘boot.loader.grub.devices’ or 'boot.loader.grub.mirroredBoots' to make the system bootable.
(use '--show-trace' to show detailed location information)

We need to define the fileSystems option and target a "device" (something like /dev/sda) to be able build a minimal nixosSystem.

To define "things" such as the fileSystems option in our configuration we need to put them in the modules argument.

Each module specified will receive arguments as described here: https://nixos.wiki/wiki/NixOS_modules#Function

We can either directly embded it or specify a file to import:

{
  system = "x86_64-linux";
  modules = [
    ({ config, pkgs, ... }: {
      # my config
    })
    ./machine.nix
  ];
}

To ease our workshop and not overload our brain with too much information we will import an installer module provided by the nixpkgs repository:

{
  modules = [
    ({ modulesPath, ... }: {
      imports = [
        "${modulesPath}/installer/cd-dvd/installation-cd-minimal.nix"
      ];
    })
  ]
}

This will help us to produce an image with predefined fileSytems and other neat configurations.

Don't hesitate to have a look to the file which is imported by installation-cd-minimal.nix: https://github.com/NixOS/nixpkgs/blob/nixos-22.11/nixos/modules/installer/cd-dvd/iso-image.nix

Do you remember how to build a something from nix repl? Let's do it again, according to the module we imported:

# This module creates a bootable ISO image containing the given NixOS
# configuration.  The derivation for the ISO image will be placed in
# config.system.build.isoImage.
:lf .
nixosConfigurations # hit tab
nixosConfigurations.default # again ...
nixosConfigurations.default.config.system.build.isoImage # it's a derivation so we can build it
:b nixosConfigurations.default.config.system.build.isoImage # build a derivation from repl is silent so you will have to wait until the build has finished

Let's add a package target:

{
  packages.x86_64-linux.isoImage = self.nixosConfigurations.default.config.system.build.isoImage;
}

Build it from nix build command:

nix build .#isoImage

Our image is now build and locate in the following folder:

ls result/iso

We now have a minimal image to use on a USB stick.

Custom image

Requirements

Steps

Maybe one of the first thing you might want to do when you booted your device from an image is to connect to it through SSH instead of relying on the main terminal so you can for example copy/paste commands directly from your computer.

To do this we need to install an OpenSSH server and add our public SSH key to the authorized keys of the main user.

We could install the package and configure it manually but instead we will be using a nixos module that will automatically do this for us.

This module can be searched from several places with services.openssh:

We can see that there is an option called services.openssh.enable but fortunately with the import "${modulesPath}/installer/cd-dvd/installation-cd-minimal.nix", OpenSSH server is already enabled here.

If we explore a little bit the code where is enabled the OpenSSH service, we can see that we need to add a public SSH key to the nixos user if we want to automatically be able to login.

To do that search for the authorizedKeys word, you should find some of these options:

  • services.openssh.authorizedKeysFiles
  • users.users.<name>.openssh.authorizedKeys.keys
  • users.users.<name>.openssh.authorizedKeys.keyFiles

The option users.users.<name>.openssh.authorizedKeys.keys is what we need, just replace <name> with nixos which is the default user configured by our import:

{
  modules = [
    ({ modulesPath, ... }: {
      imports = [
        "${modulesPath}/installer/cd-dvd/installation-cd-minimal.nix"
      ];

      users.users.nixos.openssh.authorizedKeys.keys = [
        "ssh-ed25519 AAAAC3N... "
      ];
    })
  ];
}

Now if we build our image, we will be able to SSH on a device that booted with our image.

Let's try on VM by running:

nix build .#nixosConfigurations.default.config.system.build.vm
export QEMU_NET_OPTS="hostfwd=tcp::2222-:22"
./result/bin/run-nixos-vm

In another terminal:

ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no nixos@localhost -p 2222
cat /etc/ssh/authorized_keys.d/nixos

We now have an image containing our public SSH key.

You can go further and pre-install packages like tmux, configure services or pre-configure WiFi setup.

Custom package

Requirements

ToDo

  • Create a Docker image from this book with Nginx

Package override

Requirements

ToDo

  • Override a package to apply a patch

Development environment

Requirements

ToDo

  • Create a developement environment for:
    • Python
    • Rust
    • PHP
    • Go
    • Node.js