Incus host under NixOS

Abstract: topic created by Alain, 10/june/2024

I configured an incus host (named ALPHA) under NixOS operating system. This is my experience.
See this post as an example for inspiration :slight_smile:
Once your host is created and initialized via its configuration.nix, be inspired by ScootiByte blog for using incus ! Thx Scott.
key words: nixos, incus

Context:

I think NixOS with incus is a good combo. Why?
Because configurations in NixOS are so easy: all is stuck in one configuration file (/etc/nixos/configuration.nix).
You don’t need to type commands (as in ubuntu server) and forget them later on.
You can do the same with lxc container choosing NixOS ones, so you clone them and change some parameters in configuration.nix inside the container (hostname, Ip adress, etc.).
NixOS is systemd oriented, as incus is.

Preparation:

You need a computer as a NixOS host (here named “ALPHA”) and one empty not formatted partition for default storing your containers.
For getting the things done use these commands to prepare this partition (/dev/sdb1 for me):

$ lsblk
$ sudo blkid
$ sudo fdisk /dev/sdb (for me, be carefull)

The choice of using a dedicated partition for default incus storage is simple: don’t use slow loop-backed pools (an actual file mimicking a fs).
See documentation: https://linuxcontainers.org/incus/docs/main/howto/initialize/

See my configuration.nix (btw I use flakes):

/etc/nixos/configuration.nix

# Edit this configuration file to define what should be installed on
# your system.  Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running ‘nixos-help’).

{ config, pkgs, lib, ... }:

{

  nix.settings.experimental-features = [ "nix-command" "flakes" ];
  
  imports =
    [ # Include the results of the hardware scan.
      ./hardware-configuration.nix
    ];

  # Bootloader.
  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;

  # kernel de mon choix + zfs config
  # choosing "old" kernel because some bugs in new kernel with incus
  # see:https://discuss.linuxcontainers.org/t/incus-list-not-showing-ipv4-address-anymore-in-incus-6-2/20147
  boot.kernelPackages = pkgs.linuxPackages_6_8;
  # https://openzfs.github.io/openzfs-docs/Getting%20Started/NixOS/ 
  boot.supportedFilesystems = [ "zfs" ];
  boot.zfs.forceImportRoot = false;
  # you should calculate this for your own system see above
  networking.hostId = "f5154xxx";

  # Enable networking
  networking.networkmanager.enable = false;

  # 1 bridge
  networking.bridges = {
    "bridge0" = {
      interfaces = [ "enp0sxxxx" ];
    };
  };
  
  networking = {
    hostName = "ALPHA";
    dhcpcd.enable = false;
    interfaces.bridge0.ipv4.addresses = [
        {
          # choose your local fixed address 'xxx' :
          address = "192.168.3.xxx";
          prefixLength = 24;
        }
      ];
    defaultGateway = "192.168.3.254";
    nameservers = [ "8.8.8.8" "1.1.1.1" ];
    extraHosts = ''
      192.168.3.yyy ADRIEN
    '';
    firewall = {
  	  enable = true;
  	  allowedTCPPorts = [
  		  8443
  		  80
  		  22
  	  ];
  	  # this is important when you use incus managed networks here "br_net"
  	  # btw my advice is to use NixOS firewall and disable incus firewall
  	  trustedInterfaces = [
  	  	"br_net"
  	  ];
    };
  };

  # this mounted directory will be shared among the lxc containers
  # ADRIEN serves a nfs directory  
  fileSystems."/mnt/pris-incus" = {
    device = "ADRIEN:/pris-incus";
    fsType = "nfs";
    options = [ "nfsvers=4.2" "x-systemd.automount" "noauto" "x-systemd.after=network-online.target" "x-systemd.mount-timeout=90"];
  };

  
  # Set your time zone.
  time.timeZone = "Europe/Paris";

  # Select internationalisation properties.
  i18n.defaultLocale = "fr_FR.UTF-8";

  i18n.extraLocaleSettings = {
    LC_ADDRESS = "fr_FR.UTF-8";
    LC_IDENTIFICATION = "fr_FR.UTF-8";
    LC_MEASUREMENT = "fr_FR.UTF-8";
    LC_MONETARY = "fr_FR.UTF-8";
    LC_NAME = "fr_FR.UTF-8";
    LC_NUMERIC = "fr_FR.UTF-8";
    LC_PAPER = "fr_FR.UTF-8";
    LC_TELEPHONE = "fr_FR.UTF-8";
    LC_TIME = "fr_FR.UTF-8";
  };

  # Configure keymap in X11
  services.xserver.xkb = {
    layout = "fr";
    variant = "";
  };

  # Configure console keymap
  console.keyMap = "fr";

  # Define a user account. Don't forget to set a password with ‘passwd’.
  users.users.alain = {
    isNormalUser = true;
    description = "alain";
    extraGroups = [ "networkmanager" "wheel" "incus-admin"];
    packages = with pkgs; [];
  };

  # mapping Gids Uids so you can read/write in the containers in /mnt/pris-incus with alain user
  users.users.root.subGidRanges = lib.mkForce [
    { count = 1; startGid = 100; }
    { count = 1000000000; startGid = 1000000; }
  ];
  users.users.root.subUidRanges = lib.mkForce [
    { count = 1; startUid = 1000; }
    { count = 1000000000; startUid = 1000000; }
  ];
  
  programs.bash.shellAliases = {
    rr = "sudo nixos-rebuild switch";
    l = "ls -alh";
    fu = "cd /etc/nixos && sudo nix flake update";
    qq = "sudo micro /etc/nixos/configuration.nix";
  };

  environment = {
      shells = [ pkgs.bash ];
      variables = {
        EDITOR = "micro";
        SYSTEMD_EDITOR = "micro";
        VISUAL = "micro";
      };
  };

  # Allow unfree packages
  nixpkgs.config.allowUnfree = true;
  # ainsi que les casses pour zfs rrr
  nixpkgs.config.allowBroken = true;
  
  # List packages installed in system profile. To search, run:
  # $ nix search wget
  environment.systemPackages = with pkgs; [
  #  vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
    micro
    nfs-utils
    nettools
    zfs
    iperf
    ethtool
    sshfs
    openvswitch
  ];

  # List services that you want to enable:

  # Enable the OpenSSH daemon.
  services.openssh.enable = true;

  # incus !
  
  networking.nftables.enable = true; # mandatory for incus in NixOS 
  virtualisation.incus = {
    enable = true;
    package = pkgs.incus;
    # preseed est un service systemd: systemctl status incus-preseed.service
    preseed = {
     # this is configuration of the incusd server side: 
      config = {
      "core.https_address" = ":8443";
      "images.auto_update_interval" = 9;
      };
      
      networks = [
       {
         config = {
           "ipv4.address" = "10.32.241.1/24";
           "ipv4.dhcp" = "true";
           "ipv4.dhcp.ranges" = "10.32.241.50-10.32.241.150";
           "ipv4.nat" = "true";
           "ipv4.firewall" = "false";
           "ipv6.address" = "fd42:c3ac:167a:93e9::1/64";
           "ipv6.nat" = "true";
           "ipv6.firewall" = "false";
         };
         name = "br_net";
         type = "bridge";
         description = "prem";
       }
      ];
     
      profiles = [
        {
           devices = {
            root = {
    	      path = "/";
    	      pool = "default";
    	      type = "disk";
    	    };
          };
    	  name = "default";
        }
        {
          devices = {
            eth0 = {
              name = "eth0";
    	      parent = "bridge0";
    	      type = "nic";
    	      nictype = "bridged";
    	    };
          };
    	  name = "bridgeprofile";
    	  description = "merci Scott";
        }
      ];
      # to be uncommented at first boot and commented once the zfs storage is created
      # once it is commented all preseed info above will be take in account at each rebuild
      # and when you restart preseed service:
      # systemctl restart incus-preseed.service
            
      #storage_pools = [
      #  {
      #    config = {
      #      source = "/dev/sdb1";
      #    };
      #    driver = "zfs";
      #    name = "default";
      #  }
      #];
        	
    };
  };

  # This value determines the NixOS release from which the default
  # settings for stateful data, like file locations and database versions
  # on your system were taken. It‘s perfectly fine and recommended to leave
  # this value at the release version of the first install of this system.
  # Before changing this value read the documentation for this option
  # (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
  system.stateVersion = "23.11"; # Did you read the comment?

}

So rebuild your system with:

sudo nixos-rebuild switch

That’s it: incus host is created and initialized !

You can create later on some lxc NixOS containers.
For example:

incus launch images:nixos/unstable nixo --profile default --profile bridgeprofile -c security.nesting=true -c boot.autostart=true

etc.

This is operational see incus configurations files for ALPHA server (alpha*.yml):

[alain@CAMPELS:~/pilotage-incus]$ l
total 52K
drwxr-xr-x  2 alain users 4,0K  9 juin  21:05 .
drwx------ 53 alain users 4,0K  7 juin  15:15 ..
-rw-r--r--  1 alain users  252  4 juin  18:37 adrien-bridgeprofile.yml
-rw-r--r--  1 alain users   72  4 juin  18:37 adrien-config.yml
-rw-r--r--  1 alain users  231  4 juin  18:37 adrien-default-profile.yml
-rw-r--r--  1 alain users  280  4 juin  18:37 adrien-default-storage.yml
-rw-r--r--  1 alain users  217  9 juin  20:29 alpha-bridgeprofile.yml
-rw-r--r--  1 alain users  373  9 juin  20:29 alpha-br_net-network.yml
-rw-r--r--  1 alain users   72  9 juin  20:29 alpha-config.yml
-rw-r--r--  1 alain users  194  9 juin  20:29 alpha-default-profile.yml
-rw-r--r--  1 alain users  401  9 juin  20:29 alpha-default-storage.yml
-rwxr-xr-x  1 alain users  346  4 juin  18:37 refresh_adrien.sh
-rwxr-xr-x  1 alain users  397  9 juin  19:48 refresh_alpha.sh

[alain@CAMPELS:~/pilotage-incus]$ cat ./refresh_alpha.sh 
#!/run/current-system/sw/bin/bash
# refresh les YAML externes a partir de incus-database
incus remote list
incus config show alpha: > alpha-config.yml
incus profile show alpha:bridgeprofile > alpha-bridgeprofile.yml
incus profile show alpha:default > alpha-default-profile.yml
incus storage show alpha:default > alpha-default-storage.yml
incus network show alpha:br_net > alpha-br_net-network.yml

[alain@CAMPELS:~/pilotage-incus]$ cat alpha-config.yml
config:
  core.https_address: :8443
  images.auto_update_interval: "9"

[alain@CAMPELS:~/pilotage-incus]$ cat alpha-bridgeprofile.yml
config: {}
description: merci Scott !
devices:
  eth0:
    name: eth0
    nictype: bridged
    parent: bridge0
    type: nic
name: bridgeprofile
used_by:
- /1.0/instances/nixo
- /1.0/instances/nixoff
project: default
[alain@CAMPELS:~/pilotage-incus]$ cat alpha-default-profile.yml
config: {}
description: Default Incus profile
devices:
  root:
    path: /
    pool: default
    type: disk
name: default
used_by:
- /1.0/instances/nixo
- /1.0/instances/nixoff
project: default
[alain@CAMPELS:~/pilotage-incus]$ cat alpha-default-storage.yml
config:
  source: default
  volatile.initial_source: /dev/sdb1
  zfs.pool_name: default
description: ""
name: default
driver: zfs
used_by:
- /1.0/images/ded2b685a959d500b394ac09ecfb7dc5dd2f641f1c47c42f9ce4c3f7ba3075ed
- /1.0/images/f1cdb7e17b7ba3bb9a9f03dd4b9ca4799ead607b3c676310d36fea2ad83ceb5f
- /1.0/instances/nixo
- /1.0/instances/nixoff
- /1.0/profiles/default
status: Created
locations:
[alain@CAMPELS:~/pilotage-incus]$ cat alpha-br_net-network.yml
config:
  ipv4.address: 10.32.241.1/24
  ipv4.dhcp: "true"
  ipv4.dhcp.ranges: 10.32.241.50-10.32.241.150
  ipv4.firewall: "false"
  ipv4.nat: "true"
  ipv6.address: fd42:c3aa:167c:93e9::1/64
  ipv6.nat: "true"
description: ""
name: br_net
type: bridge
used_by:
- /1.0/instances/nixo
- /1.0/instances/nixoff
managed: true
status: Created
locations:
- none
project: default

to be continued and edited. Thx