typioca: 2.7.0 -> 2.8.0
[NixPkgs.git] / nixos / modules / virtualisation / libvirtd.nix
blob708c577ec1edf5cc69bd8772dd6ef322478f3d93
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
7   cfg = config.virtualisation.libvirtd;
8   vswitch = config.virtualisation.vswitch;
9   configFile = pkgs.writeText "libvirtd.conf" ''
10     auth_unix_ro = "polkit"
11     auth_unix_rw = "polkit"
12     ${cfg.extraConfig}
13   '';
14   qemuConfigFile = pkgs.writeText "qemu.conf" ''
15     ${optionalString cfg.qemu.ovmf.enable ''
16       nvram = [ "/run/libvirt/nix-ovmf/AAVMF_CODE.fd:/run/libvirt/nix-ovmf/AAVMF_VARS.fd", "/run/libvirt/nix-ovmf/OVMF_CODE.fd:/run/libvirt/nix-ovmf/OVMF_VARS.fd" ]
17     ''}
18     ${optionalString (!cfg.qemu.runAsRoot) ''
19       user = "qemu-libvirtd"
20       group = "qemu-libvirtd"
21     ''}
22     ${cfg.qemu.verbatimConfig}
23   '';
24   dirName = "libvirt";
25   subDirs = list: [ dirName ] ++ map (e: "${dirName}/${e}") list;
27   ovmfModule = types.submodule {
28     options = {
29       enable = mkOption {
30         type = types.bool;
31         default = true;
32         description = lib.mdDoc ''
33           Allows libvirtd to take advantage of OVMF when creating new
34           QEMU VMs with UEFI boot.
35         '';
36       };
38       # mkRemovedOptionModule does not work in submodules, do it manually
39       package = mkOption {
40         type = types.nullOr types.package;
41         default = null;
42         internal = true;
43       };
45       packages = mkOption {
46         type = types.listOf types.package;
47         default = [ pkgs.OVMF.fd ];
48         defaultText = literalExpression "[ pkgs.OVMF.fd ]";
49         example = literalExpression "[ pkgs.OVMFFull.fd pkgs.pkgsCross.aarch64-multiplatform.OVMF.fd ]";
50         description = lib.mdDoc ''
51           List of OVMF packages to use. Each listed package must contain files names FV/OVMF_CODE.fd and FV/OVMF_VARS.fd or FV/AAVMF_CODE.fd and FV/AAVMF_VARS.fd
52         '';
53       };
54     };
55   };
57   swtpmModule = types.submodule {
58     options = {
59       enable = mkOption {
60         type = types.bool;
61         default = false;
62         description = lib.mdDoc ''
63           Allows libvirtd to use swtpm to create an emulated TPM.
64         '';
65       };
67       package = mkOption {
68         type = types.package;
69         default = pkgs.swtpm;
70         defaultText = literalExpression "pkgs.swtpm";
71         description = lib.mdDoc ''
72           swtpm package to use.
73         '';
74       };
75     };
76   };
78   qemuModule = types.submodule {
79     options = {
80       package = mkOption {
81         type = types.package;
82         default = pkgs.qemu;
83         defaultText = literalExpression "pkgs.qemu";
84         description = lib.mdDoc ''
85           Qemu package to use with libvirt.
86           `pkgs.qemu` can emulate alien architectures (e.g. aarch64 on x86)
87           `pkgs.qemu_kvm` saves disk space allowing to emulate only host architectures.
88         '';
89       };
91       runAsRoot = mkOption {
92         type = types.bool;
93         default = true;
94         description = lib.mdDoc ''
95           If true,  libvirtd runs qemu as root.
96           If false, libvirtd runs qemu as unprivileged user qemu-libvirtd.
97           Changing this option to false may cause file permission issues
98           for existing guests. To fix these, manually change ownership
99           of affected files in /var/lib/libvirt/qemu to qemu-libvirtd.
100         '';
101       };
103       verbatimConfig = mkOption {
104         type = types.lines;
105         default = ''
106           namespaces = []
107         '';
108         description = lib.mdDoc ''
109           Contents written to the qemu configuration file, qemu.conf.
110           Make sure to include a proper namespace configuration when
111           supplying custom configuration.
112         '';
113       };
115       ovmf = mkOption {
116         type = ovmfModule;
117         default = { };
118         description = lib.mdDoc ''
119           QEMU's OVMF options.
120         '';
121       };
123       swtpm = mkOption {
124         type = swtpmModule;
125         default = { };
126         description = lib.mdDoc ''
127           QEMU's swtpm options.
128         '';
129       };
130     };
131   };
133   hooksModule = types.submodule {
134     options = {
135       daemon = mkOption {
136         type = types.attrsOf types.path;
137         default = { };
138         description = lib.mdDoc ''
139           Hooks that will be placed under /var/lib/libvirt/hooks/daemon.d/
140           and called for daemon start/shutdown/SIGHUP events.
141           Please see https://libvirt.org/hooks.html for documentation.
142         '';
143       };
145       qemu = mkOption {
146         type = types.attrsOf types.path;
147         default = { };
148         description = lib.mdDoc ''
149           Hooks that will be placed under /var/lib/libvirt/hooks/qemu.d/
150           and called for qemu domains begin/end/migrate events.
151           Please see https://libvirt.org/hooks.html for documentation.
152         '';
153       };
155       lxc = mkOption {
156         type = types.attrsOf types.path;
157         default = { };
158         description = lib.mdDoc ''
159           Hooks that will be placed under /var/lib/libvirt/hooks/lxc.d/
160           and called for lxc domains begin/end events.
161           Please see https://libvirt.org/hooks.html for documentation.
162         '';
163       };
165       libxl = mkOption {
166         type = types.attrsOf types.path;
167         default = { };
168         description = lib.mdDoc ''
169           Hooks that will be placed under /var/lib/libvirt/hooks/libxl.d/
170           and called for libxl-handled xen domains begin/end events.
171           Please see https://libvirt.org/hooks.html for documentation.
172         '';
173       };
175       network = mkOption {
176         type = types.attrsOf types.path;
177         default = { };
178         description = lib.mdDoc ''
179           Hooks that will be placed under /var/lib/libvirt/hooks/lxc.d/
180           and called for networks begin/end events.
181           Please see https://libvirt.org/hooks.html for documentation.
182         '';
183       };
184     };
185   };
189   imports = [
190     (mkRemovedOptionModule [ "virtualisation" "libvirtd" "enableKVM" ]
191       "Set the option `virtualisation.libvirtd.qemu.package' instead.")
192     (mkRenamedOptionModule
193       [ "virtualisation" "libvirtd" "qemuPackage" ]
194       [ "virtualisation" "libvirtd" "qemu" "package" ])
195     (mkRenamedOptionModule
196       [ "virtualisation" "libvirtd" "qemuRunAsRoot" ]
197       [ "virtualisation" "libvirtd" "qemu" "runAsRoot" ])
198     (mkRenamedOptionModule
199       [ "virtualisation" "libvirtd" "qemuVerbatimConfig" ]
200       [ "virtualisation" "libvirtd" "qemu" "verbatimConfig" ])
201     (mkRenamedOptionModule
202       [ "virtualisation" "libvirtd" "qemuOvmf" ]
203       [ "virtualisation" "libvirtd" "qemu" "ovmf" "enable" ])
204     (mkRemovedOptionModule
205       [ "virtualisation" "libvirtd" "qemuOvmfPackage" ]
206       "If this option was set to `foo`, set the option `virtualisation.libvirtd.qemu.ovmf.packages' to `[foo.fd]` instead.")
207     (mkRenamedOptionModule
208       [ "virtualisation" "libvirtd" "qemuSwtpm" ]
209       [ "virtualisation" "libvirtd" "qemu" "swtpm" "enable" ])
210   ];
212   ###### interface
214   options.virtualisation.libvirtd = {
216     enable = mkOption {
217       type = types.bool;
218       default = false;
219       description = lib.mdDoc ''
220         This option enables libvirtd, a daemon that manages
221         virtual machines. Users in the "libvirtd" group can interact with
222         the daemon (e.g. to start or stop VMs) using the
223         {command}`virsh` command line tool, among others.
224       '';
225     };
227     package = mkOption {
228       type = types.package;
229       default = pkgs.libvirt;
230       defaultText = literalExpression "pkgs.libvirt";
231       description = lib.mdDoc ''
232         libvirt package to use.
233       '';
234     };
236     extraConfig = mkOption {
237       type = types.lines;
238       default = "";
239       description = lib.mdDoc ''
240         Extra contents appended to the libvirtd configuration file,
241         libvirtd.conf.
242       '';
243     };
245     extraOptions = mkOption {
246       type = types.listOf types.str;
247       default = [ ];
248       example = [ "--verbose" ];
249       description = lib.mdDoc ''
250         Extra command line arguments passed to libvirtd on startup.
251       '';
252     };
254     onBoot = mkOption {
255       type = types.enum [ "start" "ignore" ];
256       default = "start";
257       description = lib.mdDoc ''
258         Specifies the action to be done to / on the guests when the host boots.
259         The "start" option starts all guests that were running prior to shutdown
260         regardless of their autostart settings. The "ignore" option will not
261         start the formerly running guest on boot. However, any guest marked as
262         autostart will still be automatically started by libvirtd.
263       '';
264     };
266     onShutdown = mkOption {
267       type = types.enum [ "shutdown" "suspend" ];
268       default = "suspend";
269       description = lib.mdDoc ''
270         When shutting down / restarting the host what method should
271         be used to gracefully halt the guests. Setting to "shutdown"
272         will cause an ACPI shutdown of each guest. "suspend" will
273         attempt to save the state of the guests ready to restore on boot.
274       '';
275     };
277     parallelShutdown = mkOption {
278       type = types.ints.unsigned;
279       default = 0;
280       description = lib.mdDoc ''
281         Number of guests that will be shutdown concurrently, taking effect when onShutdown
282         is set to "shutdown". If set to 0, guests will be shutdown one after another.
283         Number of guests on shutdown at any time will not exceed number set in this
284         variable.
285       '';
286     };
288     allowedBridges = mkOption {
289       type = types.listOf types.str;
290       default = [ "virbr0" ];
291       description = lib.mdDoc ''
292         List of bridge devices that can be used by qemu:///session
293       '';
294     };
296     qemu = mkOption {
297       type = qemuModule;
298       default = { };
299       description = lib.mdDoc ''
300         QEMU related options.
301       '';
302     };
304     hooks = mkOption {
305       type = hooksModule;
306       default = { };
307       description = lib.mdDoc ''
308         Hooks related options.
309       '';
310     };
311   };
314   ###### implementation
316   config = mkIf cfg.enable {
318     assertions = [
319       {
320         assertion = config.virtualisation.libvirtd.qemu.ovmf.package == null;
321         message = ''
322         The option virtualisation.libvirtd.qemu.ovmf.package is superseded by virtualisation.libvirtd.qemu.ovmf.packages.
323         If this option was set to `foo`, set the option `virtualisation.libvirtd.qemu.ovmf.packages' to `[foo.fd]` instead.
324         '';
325       }
326       {
327         assertion = config.security.polkit.enable;
328         message = "The libvirtd module currently requires Polkit to be enabled ('security.polkit.enable = true').";
329       }
330     ];
332     environment = {
333       # this file is expected in /etc/qemu and not sysconfdir (/var/lib)
334       etc."qemu/bridge.conf".text = lib.concatMapStringsSep "\n"
335         (e:
336           "allow ${e}")
337         cfg.allowedBridges;
338       systemPackages = with pkgs; [ libressl.nc iptables cfg.package cfg.qemu.package ];
339       etc.ethertypes.source = "${pkgs.iptables}/etc/ethertypes";
340     };
342     boot.kernelModules = [ "tun" ];
344     users.groups.libvirtd.gid = config.ids.gids.libvirtd;
346     # libvirtd runs qemu as this user and group by default
347     users.extraGroups.qemu-libvirtd.gid = config.ids.gids.qemu-libvirtd;
348     users.extraUsers.qemu-libvirtd = {
349       uid = config.ids.uids.qemu-libvirtd;
350       isNormalUser = false;
351       group = "qemu-libvirtd";
352     };
354     security.wrappers.qemu-bridge-helper = {
355       setuid = true;
356       owner = "root";
357       group = "root";
358       source = "${cfg.qemu.package}/libexec/qemu-bridge-helper";
359     };
361     systemd.packages = [ cfg.package ];
363     systemd.services.libvirtd-config = {
364       description = "Libvirt Virtual Machine Management Daemon - configuration";
365       script = ''
366         # Copy default libvirt network config .xml files to /var/lib
367         # Files modified by the user will not be overwritten
368         for i in $(cd ${cfg.package}/var/lib && echo \
369             libvirt/qemu/networks/*.xml \
370             libvirt/nwfilter/*.xml );
371         do
372             mkdir -p /var/lib/$(dirname $i) -m 755
373             if [ ! -e /var/lib/$i ]; then
374               cp -pd ${cfg.package}/var/lib/$i /var/lib/$i
375             fi
376         done
378         # Copy generated qemu config to libvirt directory
379         cp -f ${qemuConfigFile} /var/lib/${dirName}/qemu.conf
381         # stable (not GC'able as in /nix/store) paths for using in <emulator> section of xml configs
382         for emulator in ${cfg.package}/libexec/libvirt_lxc ${cfg.qemu.package}/bin/qemu-kvm ${cfg.qemu.package}/bin/qemu-system-*; do
383           ln -s --force "$emulator" /run/${dirName}/nix-emulators/
384         done
386         for helper in bin/qemu-pr-helper; do
387           ln -s --force ${cfg.qemu.package}/$helper /run/${dirName}/nix-helpers/
388         done
390         ${optionalString cfg.qemu.ovmf.enable (let
391           ovmfpackage = pkgs.buildEnv {
392             name = "qemu-ovmf";
393             paths = cfg.qemu.ovmf.packages;
394           };
395         in
396           ''
397           ln -s --force ${ovmfpackage}/FV/AAVMF_CODE.fd /run/${dirName}/nix-ovmf/
398           ln -s --force ${ovmfpackage}/FV/OVMF_CODE.fd /run/${dirName}/nix-ovmf/
399           ln -s --force ${ovmfpackage}/FV/AAVMF_VARS.fd /run/${dirName}/nix-ovmf/
400           ln -s --force ${ovmfpackage}/FV/OVMF_VARS.fd /run/${dirName}/nix-ovmf/
401         '')}
403         # Symlink hooks to /var/lib/libvirt
404         ${concatStringsSep "\n" (map (driver:
405           ''
406           mkdir -p /var/lib/${dirName}/hooks/${driver}.d
407           rm -rf /var/lib/${dirName}/hooks/${driver}.d/*
408           ${concatStringsSep "\n" (mapAttrsToList (name: value:
409             "ln -s --force ${value} /var/lib/${dirName}/hooks/${driver}.d/${name}") cfg.hooks.${driver})}
410         '') (attrNames cfg.hooks))}
411       '';
413       serviceConfig = {
414         Type = "oneshot";
415         RuntimeDirectoryPreserve = "yes";
416         LogsDirectory = subDirs [ "qemu" ];
417         RuntimeDirectory = subDirs [ "nix-emulators" "nix-helpers" "nix-ovmf" ];
418         StateDirectory = subDirs [ "dnsmasq" ];
419       };
420     };
422     systemd.services.libvirtd = {
423       wantedBy = [ "multi-user.target" ];
424       requires = [ "libvirtd-config.service" ];
425       after = [ "libvirtd-config.service" ]
426         ++ optional vswitch.enable "ovs-vswitchd.service";
428       environment.LIBVIRTD_ARGS = escapeShellArgs (
429         [
430           "--config"
431           configFile
432           "--timeout"
433           "120" # from ${libvirt}/var/lib/sysconfig/libvirtd
434         ] ++ cfg.extraOptions
435       );
437       path = [ cfg.qemu.package ] # libvirtd requires qemu-img to manage disk images
438         ++ optional vswitch.enable vswitch.package
439         ++ optional cfg.qemu.swtpm.enable cfg.qemu.swtpm.package;
441       serviceConfig = {
442         Type = "notify";
443         KillMode = "process"; # when stopping, leave the VMs alone
444         Restart = "no";
445       };
446       restartIfChanged = false;
447     };
449     systemd.services.virtchd = {
450       path = [ pkgs.cloud-hypervisor ];
451     };
453     systemd.services.libvirt-guests = {
454       wantedBy = [ "multi-user.target" ];
455       path = with pkgs; [ coreutils gawk cfg.package ];
456       restartIfChanged = false;
458       environment.ON_BOOT = "${cfg.onBoot}";
459       environment.ON_SHUTDOWN = "${cfg.onShutdown}";
460       environment.PARALLEL_SHUTDOWN = "${toString cfg.parallelShutdown}";
461     };
463     systemd.sockets.virtlogd = {
464       description = "Virtual machine log manager socket";
465       wantedBy = [ "sockets.target" ];
466       listenStreams = [ "/run/${dirName}/virtlogd-sock" ];
467     };
469     systemd.services.virtlogd = {
470       description = "Virtual machine log manager";
471       serviceConfig.ExecStart = "@${cfg.package}/sbin/virtlogd virtlogd";
472       restartIfChanged = false;
473     };
475     systemd.sockets.virtlockd = {
476       description = "Virtual machine lock manager socket";
477       wantedBy = [ "sockets.target" ];
478       listenStreams = [ "/run/${dirName}/virtlockd-sock" ];
479     };
481     systemd.services.virtlockd = {
482       description = "Virtual machine lock manager";
483       serviceConfig.ExecStart = "@${cfg.package}/sbin/virtlockd virtlockd";
484       restartIfChanged = false;
485     };
487     # https://libvirt.org/daemons.html#monolithic-systemd-integration
488     systemd.sockets.libvirtd.wantedBy = [ "sockets.target" ];
490     security.polkit = {
491       enable = true;
492       extraConfig = ''
493         polkit.addRule(function(action, subject) {
494           if (action.id == "org.libvirt.unix.manage" &&
495             subject.isInGroup("libvirtd")) {
496             return polkit.Result.YES;
497           }
498         });
499       '';
500     };
501   };