9 cfg = config.virtualisation.incus;
10 preseedFormat = pkgs.formats.yaml { };
12 serverBinPath = ''/run/wrappers/bin:${pkgs.qemu_kvm}/libexec:${
52 thin-provisioning-tools
58 ++ lib.optionals (lib.versionAtLeast cfg.package.version "6.3.0") [
62 ++ lib.optionals config.security.apparmor.enable [
65 (writeShellScriptBin "apparmor_parser" ''
66 exec '${apparmor-parser}/bin/apparmor_parser' -I '${apparmor-profiles}/etc/apparmor.d' "$@"
69 ++ lib.optionals config.services.ceph.client.enable [ ceph-client ]
70 ++ lib.optionals config.virtualisation.vswitch.enable [ config.virtualisation.vswitch.package ]
71 ++ lib.optionals config.boot.zfs.enabled [
72 config.boot.zfs.package
73 "${config.boot.zfs.package}/lib/udev"
78 # https://github.com/lxc/incus/blob/cff35a29ee3d7a2af1f937cbb6cf23776941854b/internal/server/instance/drivers/driver_qemu.go#L123
79 OVMF2MB = pkgs.OVMF.override {
83 ovmf-prefix = if pkgs.stdenv.hostPlatform.isAarch64 then "AAVMF" else "OVMF";
84 ovmf = pkgs.linkFarm "incus-ovmf" [
85 # 2MB must remain the default or existing VMs will fail to boot. New VMs will prefer 4MB
87 name = "OVMF_CODE.fd";
88 path = "${OVMF2MB.fd}/FV/${ovmf-prefix}_CODE.fd";
91 name = "OVMF_VARS.fd";
92 path = "${OVMF2MB.fd}/FV/${ovmf-prefix}_VARS.fd";
95 name = "OVMF_VARS.ms.fd";
96 path = "${OVMF2MB.fd}/FV/${ovmf-prefix}_VARS.fd";
100 name = "OVMF_CODE.4MB.fd";
101 path = "${pkgs.OVMFFull.fd}/FV/${ovmf-prefix}_CODE.fd";
104 name = "OVMF_VARS.4MB.fd";
105 path = "${pkgs.OVMFFull.fd}/FV/${ovmf-prefix}_VARS.fd";
108 name = "OVMF_VARS.4MB.ms.fd";
109 path = "${pkgs.OVMFFull.fd}/FV/${ovmf-prefix}_VARS.fd";
113 environment = lib.mkMerge [
115 INCUS_EDK2_PATH = ovmf;
116 INCUS_LXC_TEMPLATE_CONFIG = "${pkgs.lxcfs}/share/lxc/config";
117 INCUS_USBIDS_PATH = "${pkgs.hwdata}/share/hwdata/usb.ids";
118 PATH = lib.mkForce serverBinPath;
120 (lib.mkIf (cfg.ui.enable) { "INCUS_UI" = cfg.ui.package; })
123 incus-startup = pkgs.writeShellScript "incus-startup" ''
126 systemctl is-active incus.service -q && exit 0
127 exec incusd activateifneeded
131 systemctl is-active incus.service -q || exit 0
136 echo "unknown argument \`$1'" >&2
146 maintainers = lib.teams.lxc.members;
150 virtualisation.incus = {
151 enable = lib.mkEnableOption ''
152 incusd, a daemon that manages containers and virtual machines.
154 Users in the "incus-admin" group can interact with
155 the daemon (e.g. to start or stop containers) using the
156 {command}`incus` command line tool, among others.
157 Users in the "incus" group can also interact with
158 the daemon, but with lower permissions
159 (i.e. administrative operations are forbidden).
162 package = lib.mkPackageOption pkgs "incus-lts" { };
164 lxcPackage = lib.mkOption {
165 type = lib.types.package;
166 default = config.virtualisation.lxc.package;
167 defaultText = lib.literalExpression "config.virtualisation.lxc.package";
168 description = "The lxc package to use.";
171 clientPackage = lib.mkOption {
172 type = lib.types.package;
173 default = cfg.package.client;
174 defaultText = lib.literalExpression "config.virtualisation.incus.package.client";
175 description = "The incus client package to use. This package is added to PATH.";
178 softDaemonRestart = lib.mkOption {
179 type = lib.types.bool;
182 Allow for incus.service to be stopped without affecting running instances.
186 preseed = lib.mkOption {
187 type = lib.types.nullOr (lib.types.submodule { freeformType = preseedFormat.type; });
192 Configuration for Incus preseed, see
193 <https://linuxcontainers.org/incus/docs/main/howto/initialize/#non-interactive-configuration>
194 for supported values.
196 Changes to this will be re-applied to Incus which will overwrite existing entities or create missing ones,
197 but entities will *not* be removed by preseed.
206 "ipv4.address" = "10.0.100.1/24";
217 network = "incusbr0";
234 source = "/var/lib/incus/storage-pools/default";
241 socketActivation = lib.mkEnableOption (''
242 socket-activation for starting incus.service. Enabling this option
243 will stop incus.service from starting automatically on boot.
246 startTimeout = lib.mkOption {
247 type = lib.types.ints.unsigned;
251 Time to wait (in seconds) for incusd to become ready to process requests.
252 If incusd does not reply within the configured time, `incus.service` will be
253 considered failed and systemd will attempt to restart it.
258 enable = lib.mkEnableOption "(experimental) Incus UI";
260 package = lib.mkPackageOption pkgs [
268 config = lib.mkIf cfg.enable {
273 config.networking.firewall.enable
274 && !config.networking.nftables.enable
275 && config.virtualisation.incus.enable
277 message = "Incus on NixOS is unsupported using iptables. Set `networking.nftables.enable = true;`";
281 # https://github.com/lxc/incus/blob/f145309929f849b9951658ad2ba3b8f10cbe69d1/doc/reference/server_settings.md
282 boot.kernel.sysctl = {
283 "fs.aio-max-nr" = lib.mkDefault 524288;
284 "fs.inotify.max_queued_events" = lib.mkDefault 1048576;
285 "fs.inotify.max_user_instances" = lib.mkOverride 1050 1048576; # override in case conflict nixos/modules/services/x11/xserver.nix
286 "fs.inotify.max_user_watches" = lib.mkOverride 1050 1048576; # override in case conflict nixos/modules/services/x11/xserver.nix
287 "kernel.dmesg_restrict" = lib.mkDefault 1;
288 "kernel.keys.maxbytes" = lib.mkDefault 2000000;
289 "kernel.keys.maxkeys" = lib.mkDefault 2000;
290 "net.core.bpf_jit_limit" = lib.mkDefault 1000000000;
291 "net.ipv4.neigh.default.gc_thresh3" = lib.mkDefault 8192;
292 "net.ipv6.neigh.default.gc_thresh3" = lib.mkDefault 8192;
293 # vm.max_map_count is set higher in nixos/modules/config/sysctl.nix
296 boot.kernelModules = [
302 ] ++ lib.optionals (!config.networking.nftables.enable) [ "iptable_mangle" ];
304 environment.systemPackages = [
307 # gui console support
311 # Note: the following options are also declared in virtualisation.lxc, but
312 # the latter can't be simply enabled to reuse the formers, because it
313 # does a bunch of unrelated things.
314 systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ];
316 security.apparmor = {
317 packages = [ cfg.lxcPackage ];
319 "bin.lxc-start".profile = ''
320 include ${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start
322 "lxc-containers".profile = ''
323 include ${cfg.lxcPackage}/etc/apparmor.d/lxc-containers
328 systemd.services.incus = {
329 description = "Incus Container and Virtual Machine Management Daemon";
333 wantedBy = lib.mkIf (!cfg.socketActivation) [ "multi-user.target" ];
335 "network-online.target"
338 ] ++ lib.optionals config.virtualisation.vswitch.enable [ "ovs-vswitchd.service" ];
343 ] ++ lib.optionals config.virtualisation.vswitch.enable [ "ovs-vswitchd.service" ];
345 wants = [ "network-online.target" ];
348 ExecStart = "${cfg.package}/bin/incusd --group incus-admin";
349 ExecStartPost = "${cfg.package}/bin/incusd waitready --timeout=${cfg.startTimeout}";
350 ExecStop = lib.optionalString (!cfg.softDaemonRestart) "${cfg.package}/bin/incus admin shutdown";
352 KillMode = "process"; # when stopping, leave the containers alone
354 LimitMEMLOCK = "infinity";
355 LimitNOFILE = "1048576";
356 LimitNPROC = "infinity";
357 TasksMax = "infinity";
359 Restart = "on-failure";
360 TimeoutStartSec = "${cfg.startTimeout}s";
361 TimeoutStopSec = "30s";
365 systemd.services.incus-user = {
366 description = "Incus Container and Virtual Machine Management User Daemon";
380 ExecStart = "${cfg.package}/bin/incus-user --group incus";
382 Restart = "on-failure";
386 systemd.services.incus-startup = lib.mkIf cfg.softDaemonRestart {
387 description = "Incus Instances Startup/Shutdown";
395 requires = [ "incus.socket" ];
398 ExecStart = "${incus-startup} start";
399 ExecStop = "${incus-startup} stop";
400 RemainAfterExit = true;
401 TimeoutStartSec = "600s";
402 TimeoutStopSec = "600s";
407 systemd.sockets.incus = {
408 description = "Incus UNIX socket";
409 wantedBy = [ "sockets.target" ];
412 ListenStream = "/var/lib/incus/unix.socket";
414 SocketGroup = "incus-admin";
418 systemd.sockets.incus-user = {
419 description = "Incus user UNIX socket";
420 wantedBy = [ "sockets.target" ];
423 ListenStream = "/var/lib/incus/unix.socket.user";
425 SocketGroup = "incus";
429 systemd.services.incus-preseed = lib.mkIf (cfg.preseed != null) {
430 description = "Incus initialization with preseed file";
432 wantedBy = [ "incus.service" ];
433 after = [ "incus.service" ];
434 bindsTo = [ "incus.service" ];
435 partOf = [ "incus.service" ];
438 ${cfg.package}/bin/incus admin init --preseed <${preseedFormat.generate "incus-preseed.yaml" cfg.preseed}
443 RemainAfterExit = true;
447 users.groups.incus = { };
448 users.groups.incus-admin = { };
451 # match documented default ranges https://linuxcontainers.org/incus/docs/main/userns-idmap/#allowed-ranges
466 virtualisation.lxc.lxcfs.enable = true;