{ config, lib, pkgs, ... }: let # qemu 9.1.2 no longer supports strings being passed instead of some # integer parameters. incus already has the fix, but it hasn't made it # into a release yet. # see: https://github.com/lxc/incus/issues/1522 incusPatch = pkgs.fetchpatch { name = "1531.patch"; url = "https://github.com/lxc/incus/pull/1531.patch"; sha256 = "sha256-tM/+JRH0OwR3bM8gk3yNo9SSAEMqpS2HP+OzooV3DJY="; }; incus = pkgs.incus.overrideAttrs (attrs: { patches = (attrs.patches or [ ]) ++ [ incusPatch ]; }); toYAML = lib.generators.toYAML { }; cloudInitConfiguration = { users = [ { name = "admin"; groups = "users"; sudo = "ALL=(ALL) NOPASSWD:ALL"; plain_text_passwd = "example"; lock_passwd = false; } ]; # ssh configuration allow_public_ssh_keys = true; disable_root = true; packages = [ "openssh-server" ]; runcmd = [ [ "systemctl" "enable" "ssh.service" ] ]; }; in { networking.firewall = { # needed so that the nixos firewall does not block # DHCP+DNS requests from incus, and to prevent conflicts # between the two firewalls. trustedInterfaces = [ "incusbr0" ]; allowedTCPPorts = [ 23 ]; }; # needed so inscus instances can connect to the proxy. boot.kernelModules = [ "br_netfilter" ]; virtualisation.incus = { enable = true; package = incus; preseed = { networks = [ { config = { "ipv4.address" = "10.0.100.1/24"; "ipv4.nat" = "true"; }; name = "incusbr0"; type = "bridge"; } ]; profiles = [ # this default profile gets applied to all # new instances without an explicitly set profile. { name = "default"; config = { }; devices = { eth0 = { name = "eth0"; network = "incusbr0"; type = "nic"; }; root = { path = "/"; pool = "default"; size = "35GiB"; type = "disk"; }; }; } # this profile is the one we want to apply to an ubuntu example vm. # it is provisioned with a static ipv4 (for nat-ted proxy) # and cloud-init configuration { name = "vm-1"; # config applied to new instances, # this is how we can best control # vm provisioning semi-declaratively. # for options, see: https://linuxcontainers.org/incus/docs/main/reference/instance_options/ config = { # `vendor` is usually for defaults, but it doesn't actually matter here. # NOTE: cloud-init requires either the incus-agent to be running, # or that the image is a special cloud image. i.e. `images:ubuntu/22.04/cloud`. "cloud-init.vendor-data" = '' #cloud-config ${toYAML cloudInitConfiguration} ''; }; devices = { eth0 = { name = "eth0"; network = "incusbr0"; type = "nic"; # this is necessary for our nat proxy configuration. # see: https://linuxcontainers.org/incus/docs/main/reference/devices_proxy/#nat-mode "ipv4.address" = "10.0.100.123"; }; proxy = { type = "proxy"; listen = "tcp:127.0.0.1:2222"; connect = "tcp:0.0.0.0:22"; nat = true; }; root = { path = "/"; pool = "default"; size = "35GiB"; type = "disk"; }; }; } ]; storage_pools = [ { config = { source = "/var/lib/incus/storage-pools/default"; }; driver = "dir"; name = "default"; } ]; }; }; # `incus-admin` essentially gives you root access anyway, # let users in `wheel` use it freely. users.groups."incus-admin".members = config.users.groups."wheel".members; }