grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / virtualisation / incus.nix
blob777e3b28f20072fd13c909f9701002dc87c3961c
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       '';
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.";
166       };
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.";
173       };
175       softDaemonRestart = lib.mkOption {
176         type = lib.types.bool;
177         default = true;
178         description = ''
179           Allow for incus.service to be stopped without affecting running instances.
180         '';
181       };
183       preseed = lib.mkOption {
184         type = lib.types.nullOr (lib.types.submodule { freeformType = preseedFormat.type; });
186         default = null;
188         description = ''
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.
195         '';
197         example = {
198           networks = [
199             {
200               name = "incusbr0";
201               type = "bridge";
202               config = {
203                 "ipv4.address" = "10.0.100.1/24";
204                 "ipv4.nat" = "true";
205               };
206             }
207           ];
208           profiles = [
209             {
210               name = "default";
211               devices = {
212                 eth0 = {
213                   name = "eth0";
214                   network = "incusbr0";
215                   type = "nic";
216                 };
217                 root = {
218                   path = "/";
219                   pool = "default";
220                   size = "35GiB";
221                   type = "disk";
222                 };
223               };
224             }
225           ];
226           storage_pools = [
227             {
228               name = "default";
229               driver = "dir";
230               config = {
231                 source = "/var/lib/incus/storage-pools/default";
232               };
233             }
234           ];
235         };
236       };
238       socketActivation = lib.mkEnableOption (''
239         socket-activation for starting incus.service. Enabling this option
240         will stop incus.service from starting automatically on boot.
241       '');
243       startTimeout = lib.mkOption {
244         type = lib.types.ints.unsigned;
245         default = 600;
246         apply = toString;
247         description = ''
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.
251         '';
252       };
254       ui = {
255         enable = lib.mkEnableOption "(experimental) Incus UI";
257         package = lib.mkPackageOption pkgs [
258           "incus"
259           "ui"
260         ] { };
261       };
262     };
263   };
265   config = lib.mkIf cfg.enable {
266     assertions = [
267       {
268         assertion =
269           !(
270             config.networking.firewall.enable
271             && !config.networking.nftables.enable
272             && config.virtualisation.incus.enable
273           );
274         message = "Incus on NixOS is unsupported using iptables. Set `networking.nftables.enable = true;`";
275       }
276     ];
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
291     };
293     boot.kernelModules = [
294       "veth"
295       "xt_comment"
296       "xt_CHECKSUM"
297       "xt_MASQUERADE"
298       "vhost_vsock"
299     ] ++ lib.optionals (!config.networking.nftables.enable) [ "iptable_mangle" ];
301     environment.systemPackages = [
302       cfg.clientPackage
304       # gui console support
305       pkgs.spice-gtk
306     ];
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 ];
315       policies = {
316         "bin.lxc-start".profile = ''
317           include ${cfg.lxcPackage}/etc/apparmor.d/usr.bin.lxc-start
318         '';
319         "lxc-containers".profile = ''
320           include ${cfg.lxcPackage}/etc/apparmor.d/lxc-containers
321         '';
322       };
323     };
325     systemd.services.incus = {
326       description = "Incus Container and Virtual Machine Management Daemon";
328       inherit environment;
330       wantedBy = lib.mkIf (!cfg.socketActivation) [ "multi-user.target" ];
331       after = [
332         "network-online.target"
333         "lxcfs.service"
334         "incus.socket"
335       ] ++ lib.optionals config.virtualisation.vswitch.enable [ "ovs-vswitchd.service" ];
337       requires = [
338         "lxcfs.service"
339         "incus.socket"
340       ] ++ lib.optionals config.virtualisation.vswitch.enable [ "ovs-vswitchd.service" ];
342       wants = [ "network-online.target" ];
344       serviceConfig = {
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
350         Delegate = "yes";
351         LimitMEMLOCK = "infinity";
352         LimitNOFILE = "1048576";
353         LimitNPROC = "infinity";
354         TasksMax = "infinity";
356         Restart = "on-failure";
357         TimeoutStartSec = "${cfg.startTimeout}s";
358         TimeoutStopSec = "30s";
359       };
360     };
362     systemd.services.incus-startup = lib.mkIf cfg.softDaemonRestart {
363       description = "Incus Instances Startup/Shutdown";
365       inherit environment;
367       after = [
368         "incus.service"
369         "incus.socket"
370       ];
371       requires = [ "incus.socket" ];
373       serviceConfig = {
374         ExecStart = "${incus-startup} start";
375         ExecStop = "${incus-startup} stop";
376         RemainAfterExit = true;
377         TimeoutStartSec = "600s";
378         TimeoutStopSec = "600s";
379         Type = "oneshot";
380       };
381     };
383     systemd.sockets.incus = {
384       description = "Incus UNIX socket";
385       wantedBy = [ "sockets.target" ];
387       socketConfig = {
388         ListenStream = "/var/lib/incus/unix.socket";
389         SocketMode = "0660";
390         SocketGroup = "incus-admin";
391       };
392     };
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" ];
402       script = ''
403         ${cfg.package}/bin/incus admin init --preseed <${preseedFormat.generate "incus-preseed.yaml" cfg.preseed}
404       '';
406       serviceConfig = {
407         Type = "oneshot";
408         RemainAfterExit = true;
409       };
410     };
412     users.groups.incus-admin = { };
414     users.users.root = {
415       # match documented default ranges https://linuxcontainers.org/incus/docs/main/userns-idmap/#allowed-ranges
416       subUidRanges = [
417         {
418           startUid = 1000000;
419           count = 1000000000;
420         }
421       ];
422       subGidRanges = [
423         {
424           startGid = 1000000;
425           count = 1000000000;
426         }
427       ];
428     };
430     virtualisation.lxc.lxcfs.enable = true;
431   };