Podklady z mého Nix & NixOS workshopu na InstallFestu 2024.

Základy práce s Nixem

Instalace

Základní příkazy

  • Starý nix: nix-build, nix-shell, nix-env
    • (-) částečně nekonzistentní přepínače u různých příkazů
    • (+) stabilní, dostupná široká funkcionalita
  • Experimentální příkaz: nix
    • nix build, nix develop, nix profile
    • (-) je potřeba explicitně povolit:
      • nix --experimental-features 'nix-command flake'
      • konfigurační soubor: ~/.config/nix/nix.conf
    • (-) nestabilní, na stabilizaci se pracuje
    • (+) konzistentnější přepínače
    • (+) TAB-doplňování názvů attributů

Nixpkgs

Jak použít libovolnou verzi SW?

  • Použít starší větev nixpkgs:

    nix shell nixpkgs/nixos-23.05#htop
    htop --version
    exit
    
  • Použít libovolný commit z nixpkgs:
    • https://lazamar.co.uk/nix-versions/ – zkusíme verzi 3.0.5:

      nix-shell -p htop -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/f8f124009497b3f9908f395d2533a990feee1de8.tar.gz
      nix shell nixpkgs/f8f124009497b3f9908f395d2533a990feee1de8#htop
      htop --version
      exit
      
  • Použít aktuální nixpkgs, ale zkompilovat proti tomu starší verzi htopu
    • Musí se upravit atribut src v “definici” balíku

”Override” atributů v definici balíku

  • Je lepší si na to vytvořit soubor shell.nix:

    with import <nixpkgs> {};
    mkShell {
      packages = [
        bashInteractive
        (htop.overrideAttrs (old: {
          src = fetchFromGitHub {
            owner = "htop-dev";
            repo = old.pname;
            rev = "3.1.0";
            hash = "";              # smazat hash!!!
          };
        }))
      ];
    }
    
  • Spustit nix-shell a doplnit hash podle chybové hlášky
  • Otestujeme:

    nix-shell
    htop --version
    which htop
    
  • Aby i název cesty v /nix/store obsahoval správnou verzi, je potřeba změnit i atribut version

Změna více atributů

  • Přidání version (nezapomenout na rec)

    with import <nixpkgs> {};
    mkShell {
      packages = [
        bashInteractive
        (htop.overrideAttrs (old: rec {
          version = "3.1.0";
          src = fetchFromGitHub {
            owner = "htop-dev";
            repo = old.pname;
            rev = version;
            hash = "sha256-/48Ca7JPzhPS4eYsPbwbSVcx9aS1f0LHcqsbNVWL+9k=";
          };
        }))
      ];
    }
    
  • Občas je potřeba změnit i jiné atributy (závislosti, configure flags, …)

Jak vytvořit balíček?

  • Použít nástroj nix-init pro vytvoření kostry balíku.
    • Výstup předpokládá, že tvoříme balík pro nixpkgs, což je funkce, která bude zavolána v rámci callPackage z nixpkgs.
    • Pokud chceme balík mimo nixpkgs, můžeme nahradit parametr funkce ({...}:) za with import <nixpkgs> {};
  • Zkusíme balík přeložit (pravděpodobně to nepůjde bez úprav):

    nix-build
    
  • Výsledek uvidíme za symlinkem result:

    ls -l ./result/
    
  • Typické úpravy default.nix:
    • Přidání závislostí (buildInputs, nativeBuildInputs, propagatedBuildInputs, …)
    • Nastavení parametrů pro ./configure, make, cmake, meson apod.
    • Instalace do $out
    • Patchování nekompatibilit s Nixem
  • Neocenitelný pomocník: nix-shell (bez -p) nebo nix develop:

Ladění pomocí nix-shell/nix develop

nix-shell
# Všechny atributy derivation dostupné jako proměnné prostředí
echo $version
echo $src
# Vlastní sestavení balíku je (v nixpkgs) realizováno funkcí genericBuild
type genericBuild
type buildPhase
genericBuild
# interaktivní řešení problémů

Příklad

  • Vygenerujeme kostru pomocí nix-init

    $ nix-init --url https://pypi.org/project/pynostr/
    Enter tag or revision (defaults to 0.6.2)
    ❯ 0.6.2
    Enter version
    ❯ 0.6.2
    Enter pname
    ❯ pynostr
    How should this package be built?
    ❯ 1 - buildPythonPackage
    Enter output path (leave as empty for the current directory)
    ❯ .
    
  • Vytvoříme shell.nix, kam balík natáhneme voláním callPackage:

    with import <nixpkgs> {};
    mkShell {
      packages = [
        bashInteractive
        (python3Packages.callPackage ./default.nix {})
      ];
    }
    
  • Otestujeme (a zjistíme, že kostru v default.nix už není potřeba upravovat):

    $ nix-shell
    $ python
    from pynostr.key import PrivateKey
    private_key = PrivateKey()
    public_key = private_key.public_key
    print(f"Private key: {private_key.bech32()}")
    print(f"Public key: {public_key.bech32()}")
    

Vlastní distribuce založená na NixOS

  • Myšlenka NixOSu
    • Linuxová distribuce = balík, který závisí na jiných balících + konfigurační soubory, aby spolu balíky dobře fungovaly
    • Nix je dobrý v kompilaci balíků a řešení závislostí, abychom měli Linuxovou distribuci, potřebujeme vyřešit konfiguraci
    • NixOS = Nix kód pro automatické generování konfiguračních souborů

Vlastní distribuce

  • Vytvoříme soubor configuration.nix

    { config, pkgs, ... }:
    {
      boot.loader.systemd-boot.enable = true;
    
      networking.hostName = "InstallFest";
      time.timeZone = "Europe/Prague";
    
      services.openssh.enable = true;
    
      services.xserver.enable = true;
      services.xserver.displayManager.lightdm.enable = true;
      services.xserver.desktopManager.xfce.enable = true;
    
      environment.systemPackages = with pkgs; [ emacs wget ];
    
      users.users.alice = {
        isNormalUser = true;
        extraGroups = [ "wheel" ];
        # secret
        hashedPassword = "$6$UeUi3mis.Na253Zz$l3RlyTEn8ZI0eFUgyJAwjvxr.TMx8rZc1sP/2qbKke1p4fxbSLx2iQ1mV5Qot3p9mofxWcPLxSnH/ciOYo3Pu0";
        packages = with pkgs; [ firefox thunderbird ];
      };
      documentation.nixos.enable = false;
      system.stateVersion = "23.11";
    }
    
    nix-shell -p nixos-rebuild
    # Test the system in a VM
    nixos-rebuild build-vm --no-flake -I nixos-config=$PWD/configuration.nix
    ./result/bin/run-InstallFest-vm
    # Deploy to a remote NixOS computer
    nixos-rebuild switch --no-flake -I nixos-config=$PWD/configuration.nix --target-host example.com
    

Testování s nixos-shell

  • Nástroj nixos-shell (neplést s nix-shell) vytvoří VM dle NixOS konfigurace v souboru ./vm.nix (default), nabootuje jí v qmeu
  • vm.nix

    { config, pkgs, ... }:
    {
      networking.hostName = "InstallFest";
    
      environment.systemPackages = with pkgs; [
        htop
      ];
    
      documentation.nixos.enable = false;
      system.stateVersion = "23.11";
    }
    
    nix-shell -p nixos-shell
    nixos-shell
    

Kontejnery bez zbytečného balastu

  • Kontejnery se často generují z Alpine Linuxu – používá malou musl libc
  • S Nixem můžete snadno docílit toho samého:

    nix-build --expr 'with (import <nixpkgs> {}); ministat'
    nix-tree ./result/              # 30 MB
    nix-build --expr 'with (import <nixpkgs> {}).pkgsMusl; ministat'
    nix-tree ./result/              # 3 MB
    nix-build --expr 'with (import <nixpkgs> {}).pkgsStatic; ministat'
    nix-tree ./result/              # 80 kB
    

Tvorba Docker kontejneru z libovolných Nix balíků

  • Vytvoříme soubor default.nix s následujícím obsahem:

    { pkgs ? import <nixpkgs> {} }:
    pkgs.dockerTools.buildImage {
      name = "ministat";
      tag = "latest";
      config = {
        Cmd = [ "${pkgs.ministat}/bin/ministat" ];
      };
    }
    
  • ${pkgs.ministat} můžeme nahradit ${pkgs.pkgsStatic.ministat}

Spuštění v kontejneru

nix-build
docker load < result
docker run -it ministat

Layered image

  • Místo dockerTools.buildImage lze použít dockerTools.buildLayeredImage, který dá každý Nix balík do samostatné vrstvy (pokud jich není víc než 256).
  • Zlepší upload časy změněného obrazu

Binární kompatibilita s FHS distribucemi