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
159 package = lib.mkPackageOption pkgs "incus-lts" { };
161 lxcPackage = lib.mkOption {
162 type = lib.types.package;
163 default = config.virtualisation.lxc.package;
164 defaultText = lib.literalExpression "config.virtualisation.lxc.package";
165 description = "The lxc package to use.";
168 clientPackage = lib.mkOption {
169 type = lib.types.package;
170 default = cfg.package.client;
171 defaultText = lib.literalExpression "config.virtualisation.incus.package.client";
172 description = "The incus client package to use. This package is added to PATH.";
175 softDaemonRestart = lib.mkOption {
176 type = lib.types.bool;
179 Allow for incus.service to be stopped without affecting running instances.
183 preseed = lib.mkOption {
184 type = lib.types.nullOr (lib.types.submodule { freeformType = preseedFormat.type; });
189 Configuration for Incus preseed, see
190 <https://linuxcontainers.org/incus/docs/main/howto/initialize/#non-interactive-configuration>
191 for supported values.
193 Changes to this will be re-applied to Incus which will overwrite existing entities or create missing ones,
194 but entities will *not* be removed by preseed.
203 "ipv4.address" = "10.0.100.1/24";
214 network = "incusbr0";
231 source = "/var/lib/incus/storage-pools/default";
238 socketActivation = lib.mkEnableOption (''
239 socket-activation for starting incus.service. Enabling this option
240 will stop incus.service from starting automatically on boot.
243 startTimeout = lib.mkOption {
244 type = lib.types.ints.unsigned;
248 Time to wait (in seconds) for incusd to become ready to process requests.
249 If incusd does not reply within the configured time, `incus.service` will be
250 considered failed and systemd will attempt to restart it.
255 enable = lib.mkEnableOption "(experimental) Incus UI";
257 package = lib.mkPackageOption pkgs [
265 config = lib.mkIf cfg.enable {
270 config.networking.firewall.enable
271 && !config.networking.nftables.enable
272 && config.virtualisation.incus.enable
274 message = "Incus on NixOS is unsupported using iptables. Set `networking.nftables.enable = true;`";
278 # https://github.com/lxc/incus/blob/f145309929f849b9951658ad2ba3b8f10cbe69d1/doc/reference/server_settings.md
279 boot.kernel.sysctl = {
280 "fs.aio-max-nr" = lib.mkDefault 524288;
281 "fs.inotify.max_queued_events" = lib.mkDefault 1048576;
282 "fs.inotify.max_user_instances" = lib.mkOverride 1050 1048576; # override in case conflict nixos/modules/services/x11/xserver.nix
283 "fs.inotify.max_user_watches" = lib.mkOverride 1050 1048576; # override in case conflict nixos/modules/services/x11/xserver.nix
284 "kernel.dmesg_restrict" = lib.mkDefault 1;
285 "kernel.keys.maxbytes" = lib.mkDefault 2000000;
286 "kernel.keys.maxkeys" = lib.mkDefault 2000;
287 "net.core.bpf_jit_limit" = lib.mkDefault 1000000000;
288 "net.ipv4.neigh.default.gc_thresh3" = lib.mkDefault 8192;
289 "net.ipv6.neigh.default.gc_thresh3" = lib.mkDefault 8192;
290 # vm.max_map_count is set higher in nixos/modules/config/sysctl.nix
293 boot.kernelModules = [
299 ] ++ lib.optionals (!config.networking.nftables.enable) [ "iptable_mangle" ];
301 environment.systemPackages = [
304 # gui console support
308 # Note: the following options are also declared in virtualisation.lxc, but
309 # the latter can't be simply enabled to reuse the formers, because it
310 # does a bunch of unrelated things.
311 systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ];
313 security.apparmor = {
314 packages = [ cfg.lxcPackage ];
316 "bin.lxc-start".profile = ''
317 include ${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start
319 "lxc-containers".profile = ''
320 include ${cfg.lxcPackage}/etc/apparmor.d/lxc-containers
325 systemd.services.incus = {
326 description = "Incus Container and Virtual Machine Management Daemon";
330 wantedBy = lib.mkIf (!cfg.socketActivation) [ "multi-user.target" ];
332 "network-online.target"
335 ] ++ lib.optionals config.virtualisation.vswitch.enable [ "ovs-vswitchd.service" ];
340 ] ++ lib.optionals config.virtualisation.vswitch.enable [ "ovs-vswitchd.service" ];
342 wants = [ "network-online.target" ];
345 ExecStart = "${cfg.package}/bin/incusd --group incus-admin";
346 ExecStartPost = "${cfg.package}/bin/incusd waitready --timeout=${cfg.startTimeout}";
347 ExecStop = lib.optionalString (!cfg.softDaemonRestart) "${cfg.package}/bin/incus admin shutdown";
349 KillMode = "process"; # when stopping, leave the containers alone
351 LimitMEMLOCK = "infinity";
352 LimitNOFILE = "1048576";
353 LimitNPROC = "infinity";
354 TasksMax = "infinity";
356 Restart = "on-failure";
357 TimeoutStartSec = "${cfg.startTimeout}s";
358 TimeoutStopSec = "30s";
362 systemd.services.incus-startup = lib.mkIf cfg.softDaemonRestart {
363 description = "Incus Instances Startup/Shutdown";
371 requires = [ "incus.socket" ];
374 ExecStart = "${incus-startup} start";
375 ExecStop = "${incus-startup} stop";
376 RemainAfterExit = true;
377 TimeoutStartSec = "600s";
378 TimeoutStopSec = "600s";
383 systemd.sockets.incus = {
384 description = "Incus UNIX socket";
385 wantedBy = [ "sockets.target" ];
388 ListenStream = "/var/lib/incus/unix.socket";
390 SocketGroup = "incus-admin";
394 systemd.services.incus-preseed = lib.mkIf (cfg.preseed != null) {
395 description = "Incus initialization with preseed file";
397 wantedBy = [ "incus.service" ];
398 after = [ "incus.service" ];
399 bindsTo = [ "incus.service" ];
400 partOf = [ "incus.service" ];
403 ${cfg.package}/bin/incus admin init --preseed <${preseedFormat.generate "incus-preseed.yaml" cfg.preseed}
408 RemainAfterExit = true;
412 users.groups.incus-admin = { };
415 # match documented default ranges https://linuxcontainers.org/incus/docs/main/userns-idmap/#allowed-ranges
430 virtualisation.lxc.lxcfs.enable = true;