linuxPackages_latest.broadcom_sta: add patch to compile on Kernel 6.12 (#359484)
[NixPkgs.git] / nixos / modules / virtualisation / incus.nix
blobaa2102f49f0d71ac21967e9804bb45e7c2e4c962
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
8 let
9   cfg = config.virtualisation.incus;
10   preseedFormat = pkgs.formats.yaml { };
12   serverBinPath = ''/run/wrappers/bin:${pkgs.qemu_kvm}/libexec:${
13     lib.makeBinPath (
14       with pkgs;
15       [
16         cfg.package
18         acl
19         attr
20         bash
21         btrfs-progs
22         cdrkit
23         coreutils
24         criu
25         dnsmasq
26         e2fsprogs
27         findutils
28         getent
29         gnugrep
30         gnused
31         gnutar
32         gptfdisk
33         gzip
34         iproute2
35         iptables
36         iw
37         kmod
38         libnvidia-container
39         libxfs
40         lvm2
41         minio
42         minio-client
43         nftables
44         qemu-utils
45         qemu_kvm
46         rsync
47         squashfs-tools-ng
48         squashfsTools
49         sshfs
50         swtpm
51         systemd
52         thin-provisioning-tools
53         util-linux
54         virtiofsd
55         xdelta
56         xz
57       ]
58       ++ lib.optionals (lib.versionAtLeast cfg.package.version "6.3.0") [
59         skopeo
60         umoci
61       ]
62       ++ lib.optionals config.security.apparmor.enable [
63         apparmor-bin-utils
65         (writeShellScriptBin "apparmor_parser" ''
66           exec '${apparmor-parser}/bin/apparmor_parser' -I '${apparmor-profiles}/etc/apparmor.d' "$@"
67         '')
68       ]
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"
74       ]
75     )
76   }'';
78   # https://github.com/lxc/incus/blob/cff35a29ee3d7a2af1f937cbb6cf23776941854b/internal/server/instance/drivers/driver_qemu.go#L123
79   OVMF2MB = pkgs.OVMF.override {
80     secureBoot = true;
81     fdSize2MB = true;
82   };
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
86     {
87       name = "OVMF_CODE.fd";
88       path = "${OVMF2MB.fd}/FV/${ovmf-prefix}_CODE.fd";
89     }
90     {
91       name = "OVMF_VARS.fd";
92       path = "${OVMF2MB.fd}/FV/${ovmf-prefix}_VARS.fd";
93     }
94     {
95       name = "OVMF_VARS.ms.fd";
96       path = "${OVMF2MB.fd}/FV/${ovmf-prefix}_VARS.fd";
97     }
99     {
100       name = "OVMF_CODE.4MB.fd";
101       path = "${pkgs.OVMFFull.fd}/FV/${ovmf-prefix}_CODE.fd";
102     }
103     {
104       name = "OVMF_VARS.4MB.fd";
105       path = "${pkgs.OVMFFull.fd}/FV/${ovmf-prefix}_VARS.fd";
106     }
107     {
108       name = "OVMF_VARS.4MB.ms.fd";
109       path = "${pkgs.OVMFFull.fd}/FV/${ovmf-prefix}_VARS.fd";
110     }
111   ];
113   environment = lib.mkMerge [
114     {
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;
119     }
120     (lib.mkIf (cfg.ui.enable) { "INCUS_UI" = cfg.ui.package; })
121   ];
123   incus-startup = pkgs.writeShellScript "incus-startup" ''
124     case "$1" in
125         start)
126           systemctl is-active incus.service -q && exit 0
127           exec incusd activateifneeded
128         ;;
130         stop)
131           systemctl is-active incus.service -q || exit 0
132           exec incusd shutdown
133         ;;
135         *)
136           echo "unknown argument \`$1'" >&2
137           exit 1
138         ;;
139     esac
141     exit 0
142   '';
145   meta = {
146     maintainers = lib.teams.lxc.members;
147   };
149   options = {
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).
160       '';
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.";
169       };
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.";
176       };
178       softDaemonRestart = lib.mkOption {
179         type = lib.types.bool;
180         default = true;
181         description = ''
182           Allow for incus.service to be stopped without affecting running instances.
183         '';
184       };
186       preseed = lib.mkOption {
187         type = lib.types.nullOr (lib.types.submodule { freeformType = preseedFormat.type; });
189         default = null;
191         description = ''
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.
198         '';
200         example = {
201           networks = [
202             {
203               name = "incusbr0";
204               type = "bridge";
205               config = {
206                 "ipv4.address" = "10.0.100.1/24";
207                 "ipv4.nat" = "true";
208               };
209             }
210           ];
211           profiles = [
212             {
213               name = "default";
214               devices = {
215                 eth0 = {
216                   name = "eth0";
217                   network = "incusbr0";
218                   type = "nic";
219                 };
220                 root = {
221                   path = "/";
222                   pool = "default";
223                   size = "35GiB";
224                   type = "disk";
225                 };
226               };
227             }
228           ];
229           storage_pools = [
230             {
231               name = "default";
232               driver = "dir";
233               config = {
234                 source = "/var/lib/incus/storage-pools/default";
235               };
236             }
237           ];
238         };
239       };
241       socketActivation = lib.mkEnableOption (''
242         socket-activation for starting incus.service. Enabling this option
243         will stop incus.service from starting automatically on boot.
244       '');
246       startTimeout = lib.mkOption {
247         type = lib.types.ints.unsigned;
248         default = 600;
249         apply = toString;
250         description = ''
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.
254         '';
255       };
257       ui = {
258         enable = lib.mkEnableOption "(experimental) Incus UI";
260         package = lib.mkPackageOption pkgs [
261           "incus"
262           "ui"
263         ] { };
264       };
265     };
266   };
268   config = lib.mkIf cfg.enable {
269     assertions = [
270       {
271         assertion =
272           !(
273             config.networking.firewall.enable
274             && !config.networking.nftables.enable
275             && config.virtualisation.incus.enable
276           );
277         message = "Incus on NixOS is unsupported using iptables. Set `networking.nftables.enable = true;`";
278       }
279     ];
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
294     };
296     boot.kernelModules = [
297       "veth"
298       "xt_comment"
299       "xt_CHECKSUM"
300       "xt_MASQUERADE"
301       "vhost_vsock"
302     ] ++ lib.optionals (!config.networking.nftables.enable) [ "iptable_mangle" ];
304     environment.systemPackages = [
305       cfg.clientPackage
307       # gui console support
308       pkgs.spice-gtk
309     ];
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 ];
318       policies = {
319         "bin.lxc-start".profile = ''
320           include ${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start
321         '';
322         "lxc-containers".profile = ''
323           include ${cfg.lxcPackage}/etc/apparmor.d/lxc-containers
324         '';
325       };
326     };
328     systemd.services.incus = {
329       description = "Incus Container and Virtual Machine Management Daemon";
331       inherit environment;
333       wantedBy = lib.mkIf (!cfg.socketActivation) [ "multi-user.target" ];
334       after = [
335         "network-online.target"
336         "lxcfs.service"
337         "incus.socket"
338       ] ++ lib.optionals config.virtualisation.vswitch.enable [ "ovs-vswitchd.service" ];
340       requires = [
341         "lxcfs.service"
342         "incus.socket"
343       ] ++ lib.optionals config.virtualisation.vswitch.enable [ "ovs-vswitchd.service" ];
345       wants = [ "network-online.target" ];
347       serviceConfig = {
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
353         Delegate = "yes";
354         LimitMEMLOCK = "infinity";
355         LimitNOFILE = "1048576";
356         LimitNPROC = "infinity";
357         TasksMax = "infinity";
359         Restart = "on-failure";
360         TimeoutStartSec = "${cfg.startTimeout}s";
361         TimeoutStopSec = "30s";
362       };
363     };
365     systemd.services.incus-user = {
366       description = "Incus Container and Virtual Machine Management User Daemon";
368       inherit environment;
370       after = [
371         "incus.service"
372         "incus-user.socket"
373       ];
375       requires = [
376         "incus-user.socket"
377       ];
379       serviceConfig = {
380         ExecStart = "${cfg.package}/bin/incus-user --group incus";
382         Restart = "on-failure";
383       };
384     };
386     systemd.services.incus-startup = lib.mkIf cfg.softDaemonRestart {
387       description = "Incus Instances Startup/Shutdown";
389       inherit environment;
391       after = [
392         "incus.service"
393         "incus.socket"
394       ];
395       requires = [ "incus.socket" ];
397       serviceConfig = {
398         ExecStart = "${incus-startup} start";
399         ExecStop = "${incus-startup} stop";
400         RemainAfterExit = true;
401         TimeoutStartSec = "600s";
402         TimeoutStopSec = "600s";
403         Type = "oneshot";
404       };
405     };
407     systemd.sockets.incus = {
408       description = "Incus UNIX socket";
409       wantedBy = [ "sockets.target" ];
411       socketConfig = {
412         ListenStream = "/var/lib/incus/unix.socket";
413         SocketMode = "0660";
414         SocketGroup = "incus-admin";
415       };
416     };
418     systemd.sockets.incus-user = {
419       description = "Incus user UNIX socket";
420       wantedBy = [ "sockets.target" ];
422       socketConfig = {
423         ListenStream = "/var/lib/incus/unix.socket.user";
424         SocketMode = "0660";
425         SocketGroup = "incus";
426       };
427     };
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" ];
437       script = ''
438         ${cfg.package}/bin/incus admin init --preseed <${preseedFormat.generate "incus-preseed.yaml" cfg.preseed}
439       '';
441       serviceConfig = {
442         Type = "oneshot";
443         RemainAfterExit = true;
444       };
445     };
447     users.groups.incus = { };
448     users.groups.incus-admin = { };
450     users.users.root = {
451       # match documented default ranges https://linuxcontainers.org/incus/docs/main/userns-idmap/#allowed-ranges
452       subUidRanges = [
453         {
454           startUid = 1000000;
455           count = 1000000000;
456         }
457       ];
458       subGidRanges = [
459         {
460           startGid = 1000000;
461           count = 1000000000;
462         }
463       ];
464     };
466     virtualisation.lxc.lxcfs.enable = true;
467   };