nixos/ssh: use correct executable for grep in ssh-askpass-wrapper (#373746)
[NixPkgs.git] / nixos / modules / virtualisation / libvirtd.nix
blob03691d12e63a2ad4b1a6f4b7777d2c9853ceaadb
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 = [
17         "/run/libvirt/nix-ovmf/AAVMF_CODE.fd:/run/libvirt/nix-ovmf/AAVMF_VARS.fd",
18         "/run/libvirt/nix-ovmf/AAVMF_CODE.ms.fd:/run/libvirt/nix-ovmf/AAVMF_VARS.ms.fd",
19         "/run/libvirt/nix-ovmf/OVMF_CODE.fd:/run/libvirt/nix-ovmf/OVMF_VARS.fd",
20         "/run/libvirt/nix-ovmf/OVMF_CODE.ms.fd:/run/libvirt/nix-ovmf/OVMF_VARS.ms.fd"
21       ]
22     ''}
23     ${optionalString (!cfg.qemu.runAsRoot) ''
24       user = "qemu-libvirtd"
25       group = "qemu-libvirtd"
26     ''}
27     ${cfg.qemu.verbatimConfig}
28   '';
29   dirName = "libvirt";
30   subDirs = list: [ dirName ] ++ map (e: "${dirName}/${e}") list;
32   ovmfModule = types.submodule {
33     options = {
34       enable = mkOption {
35         type = types.bool;
36         default = true;
37         description = ''
38           Allows libvirtd to take advantage of OVMF when creating new
39           QEMU VMs with UEFI boot.
40         '';
41       };
43       # mkRemovedOptionModule does not work in submodules, do it manually
44       package = mkOption {
45         type = types.nullOr types.package;
46         default = null;
47         internal = true;
48       };
50       packages = mkOption {
51         type = types.listOf types.package;
52         default = [ pkgs.OVMF.fd ];
53         defaultText = literalExpression "[ pkgs.OVMF.fd ]";
54         example = literalExpression "[ pkgs.OVMFFull.fd pkgs.pkgsCross.aarch64-multiplatform.OVMF.fd ]";
55         description = ''
56           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
57         '';
58       };
59     };
60   };
62   swtpmModule = types.submodule {
63     options = {
64       enable = mkOption {
65         type = types.bool;
66         default = false;
67         description = ''
68           Allows libvirtd to use swtpm to create an emulated TPM.
69         '';
70       };
72       package = mkPackageOption pkgs "swtpm" { };
73     };
74   };
76   qemuModule = types.submodule {
77     options = {
78       package = mkPackageOption pkgs "qemu" {
79         extraDescription = ''
80           `pkgs.qemu` can emulate alien architectures (e.g. aarch64 on x86)
81           `pkgs.qemu_kvm` saves disk space allowing to emulate only host architectures.
82         '';
83       };
85       runAsRoot = mkOption {
86         type = types.bool;
87         default = true;
88         description = ''
89           If true,  libvirtd runs qemu as root.
90           If false, libvirtd runs qemu as unprivileged user qemu-libvirtd.
91           Changing this option to false may cause file permission issues
92           for existing guests. To fix these, manually change ownership
93           of affected files in /var/lib/libvirt/qemu to qemu-libvirtd.
94         '';
95       };
97       verbatimConfig = mkOption {
98         type = types.lines;
99         default = ''
100           namespaces = []
101         '';
102         description = ''
103           Contents written to the qemu configuration file, qemu.conf.
104           Make sure to include a proper namespace configuration when
105           supplying custom configuration.
106         '';
107       };
109       ovmf = mkOption {
110         type = ovmfModule;
111         default = { };
112         description = ''
113           QEMU's OVMF options.
114         '';
115       };
117       swtpm = mkOption {
118         type = swtpmModule;
119         default = { };
120         description = ''
121           QEMU's swtpm options.
122         '';
123       };
125       vhostUserPackages = mkOption {
126         type = types.listOf types.package;
127         default = [ ];
128         example = lib.literalExpression "[ pkgs.virtiofsd ]";
129         description = ''
130           Packages containing out-of-tree vhost-user drivers.
131         '';
132       };
133     };
134   };
136   hooksModule = types.submodule {
137     options = {
138       daemon = mkOption {
139         type = types.attrsOf types.path;
140         default = { };
141         description = ''
142           Hooks that will be placed under /var/lib/libvirt/hooks/daemon.d/
143           and called for daemon start/shutdown/SIGHUP events.
144           Please see https://libvirt.org/hooks.html for documentation.
145         '';
146       };
148       qemu = mkOption {
149         type = types.attrsOf types.path;
150         default = { };
151         description = ''
152           Hooks that will be placed under /var/lib/libvirt/hooks/qemu.d/
153           and called for qemu domains begin/end/migrate events.
154           Please see https://libvirt.org/hooks.html for documentation.
155         '';
156       };
158       lxc = mkOption {
159         type = types.attrsOf types.path;
160         default = { };
161         description = ''
162           Hooks that will be placed under /var/lib/libvirt/hooks/lxc.d/
163           and called for lxc domains begin/end events.
164           Please see https://libvirt.org/hooks.html for documentation.
165         '';
166       };
168       libxl = mkOption {
169         type = types.attrsOf types.path;
170         default = { };
171         description = ''
172           Hooks that will be placed under /var/lib/libvirt/hooks/libxl.d/
173           and called for libxl-handled xen domains begin/end events.
174           Please see https://libvirt.org/hooks.html for documentation.
175         '';
176       };
178       network = mkOption {
179         type = types.attrsOf types.path;
180         default = { };
181         description = ''
182           Hooks that will be placed under /var/lib/libvirt/hooks/lxc.d/
183           and called for networks begin/end events.
184           Please see https://libvirt.org/hooks.html for documentation.
185         '';
186       };
187     };
188   };
190   nssModule = types.submodule {
191     options = {
192       enable = mkOption {
193         type = types.bool;
194         default = false;
195         description = ''
196           This option enables the older libvirt NSS module. This method uses
197           DHCP server records, therefore is dependent on the hostname provided
198           by the guest.
199           Please see https://libvirt.org/nss.html for more information.
200         '';
201       };
203       enableGuest = mkOption {
204         type = types.bool;
205         default = false;
206         description = ''
207           This option enables the newer libvirt_guest NSS module. This module
208           uses the libvirt guest name instead of the hostname of the guest.
209           Please see https://libvirt.org/nss.html for more information.
210         '';
211       };
212     };
213   };
217   imports = [
218     (mkRemovedOptionModule [ "virtualisation" "libvirtd" "enableKVM" ]
219       "Set the option `virtualisation.libvirtd.qemu.package' instead.")
220     (mkRenamedOptionModule
221       [ "virtualisation" "libvirtd" "qemuPackage" ]
222       [ "virtualisation" "libvirtd" "qemu" "package" ])
223     (mkRenamedOptionModule
224       [ "virtualisation" "libvirtd" "qemuRunAsRoot" ]
225       [ "virtualisation" "libvirtd" "qemu" "runAsRoot" ])
226     (mkRenamedOptionModule
227       [ "virtualisation" "libvirtd" "qemuVerbatimConfig" ]
228       [ "virtualisation" "libvirtd" "qemu" "verbatimConfig" ])
229     (mkRenamedOptionModule
230       [ "virtualisation" "libvirtd" "qemuOvmf" ]
231       [ "virtualisation" "libvirtd" "qemu" "ovmf" "enable" ])
232     (mkRemovedOptionModule
233       [ "virtualisation" "libvirtd" "qemuOvmfPackage" ]
234       "If this option was set to `foo`, set the option `virtualisation.libvirtd.qemu.ovmf.packages' to `[foo.fd]` instead.")
235     (mkRenamedOptionModule
236       [ "virtualisation" "libvirtd" "qemuSwtpm" ]
237       [ "virtualisation" "libvirtd" "qemu" "swtpm" "enable" ])
238   ];
240   ###### interface
242   options.virtualisation.libvirtd = {
244     enable = mkOption {
245       type = types.bool;
246       default = false;
247       description = ''
248         This option enables libvirtd, a daemon that manages
249         virtual machines. Users in the "libvirtd" group can interact with
250         the daemon (e.g. to start or stop VMs) using the
251         {command}`virsh` command line tool, among others.
252       '';
253     };
255     package = mkPackageOption pkgs "libvirt" { };
257     extraConfig = mkOption {
258       type = types.lines;
259       default = "";
260       description = ''
261         Extra contents appended to the libvirtd configuration file,
262         libvirtd.conf.
263       '';
264     };
266     extraOptions = mkOption {
267       type = types.listOf types.str;
268       default = [ ];
269       example = [ "--verbose" ];
270       description = ''
271         Extra command line arguments passed to libvirtd on startup.
272       '';
273     };
275     onBoot = mkOption {
276       type = types.enum [ "start" "ignore" ];
277       default = "start";
278       description = ''
279         Specifies the action to be done to / on the guests when the host boots.
280         The "start" option starts all guests that were running prior to shutdown
281         regardless of their autostart settings. The "ignore" option will not
282         start the formerly running guest on boot. However, any guest marked as
283         autostart will still be automatically started by libvirtd.
284       '';
285     };
287     onShutdown = mkOption {
288       type = types.enum [ "shutdown" "suspend" ];
289       default = "suspend";
290       description = ''
291         When shutting down / restarting the host what method should
292         be used to gracefully halt the guests. Setting to "shutdown"
293         will cause an ACPI shutdown of each guest. "suspend" will
294         attempt to save the state of the guests ready to restore on boot.
295       '';
296     };
298     parallelShutdown = mkOption {
299       type = types.ints.unsigned;
300       default = 0;
301       description = ''
302         Number of guests that will be shutdown concurrently, taking effect when onShutdown
303         is set to "shutdown". If set to 0, guests will be shutdown one after another.
304         Number of guests on shutdown at any time will not exceed number set in this
305         variable.
306       '';
307     };
309     shutdownTimeout = mkOption {
310       type = types.ints.unsigned;
311       default = 300;
312       description = ''
313         Number of seconds we're willing to wait for a guest to shut down.
314         If parallel shutdown is enabled, this timeout applies as a timeout
315         for shutting down all guests on a single URI defined in the variable URIS.
316         If this is 0, then there is no time out (use with caution, as guests might not
317         respond to a shutdown request).
318       '';
319     };
321     startDelay = mkOption {
322       type = types.ints.unsigned;
323       default = 0;
324       description = ''
325         Number of seconds to wait between each guest start.
326         If set to 0, all guests will start up in parallel.
327       '';
328     };
330     allowedBridges = mkOption {
331       type = types.listOf types.str;
332       default = [ "virbr0" ];
333       description = ''
334         List of bridge devices that can be used by qemu:///session
335       '';
336     };
338     qemu = mkOption {
339       type = qemuModule;
340       default = { };
341       description = ''
342         QEMU related options.
343       '';
344     };
346     hooks = mkOption {
347       type = hooksModule;
348       default = { };
349       description = ''
350         Hooks related options.
351       '';
352     };
354     nss = mkOption {
355       type = nssModule;
356       default = { };
357       description = ''
358         libvirt NSS module options.
359       '';
360     };
362     sshProxy = mkOption {
363       type = types.bool;
364       default = true;
365       description = ''
366         Weither to configure OpenSSH to use the [SSH Proxy](https://libvirt.org/ssh-proxy.html).
367       '';
368     };
369   };
372   ###### implementation
374   config = mkIf cfg.enable {
376     assertions = [
377       {
378         assertion = config.virtualisation.libvirtd.qemu.ovmf.package == null;
379         message = ''
380         The option virtualisation.libvirtd.qemu.ovmf.package is superseded by virtualisation.libvirtd.qemu.ovmf.packages.
381         If this option was set to `foo`, set the option `virtualisation.libvirtd.qemu.ovmf.packages' to `[foo.fd]` instead.
382         '';
383       }
384       {
385         assertion = config.security.polkit.enable;
386         message = "The libvirtd module currently requires Polkit to be enabled ('security.polkit.enable = true').";
387       }
388     ];
390     environment = {
391       # this file is expected in /etc/qemu and not sysconfdir (/var/lib)
392       etc."qemu/bridge.conf".text = lib.concatMapStringsSep "\n"
393         (e:
394           "allow ${e}")
395         cfg.allowedBridges;
396       systemPackages = with pkgs; [ libressl.nc iptables cfg.package cfg.qemu.package ];
397       etc.ethertypes.source = "${pkgs.iptables}/etc/ethertypes";
398     };
400     boot.kernelModules = [ "tun" ];
402     users.groups.libvirtd.gid = config.ids.gids.libvirtd;
404     # libvirtd runs qemu as this user and group by default
405     users.extraGroups.qemu-libvirtd.gid = config.ids.gids.qemu-libvirtd;
406     users.extraUsers.qemu-libvirtd = {
407       uid = config.ids.uids.qemu-libvirtd;
408       isNormalUser = false;
409       group = "qemu-libvirtd";
410     };
412     security.wrappers.qemu-bridge-helper = {
413       setuid = true;
414       owner = "root";
415       group = "root";
416       source = "${cfg.qemu.package}/libexec/qemu-bridge-helper";
417     };
419     programs.ssh.extraConfig = mkIf cfg.sshProxy ''
420       Include ${cfg.package}/etc/ssh/ssh_config.d/30-libvirt-ssh-proxy.conf
421     '';
423     systemd.packages = [ cfg.package ];
425     systemd.services.libvirtd-config = {
426       description = "Libvirt Virtual Machine Management Daemon - configuration";
427       script = ''
428         # Copy default libvirt network config .xml files to /var/lib
429         # Files modified by the user will not be overwritten
430         for i in $(cd ${cfg.package}/var/lib && echo \
431             libvirt/qemu/networks/*.xml \
432             libvirt/nwfilter/*.xml );
433         do
434             # Intended behavior
435             # shellcheck disable=SC2174
436             mkdir -p "/var/lib/$(dirname "$i")" -m 755
437             if [ ! -e "/var/lib/$i" ]; then
438               cp -pd "${cfg.package}/var/lib/$i" "/var/lib/$i"
439             fi
440         done
442         # Copy generated qemu config to libvirt directory
443         cp -f ${qemuConfigFile} /var/lib/${dirName}/qemu.conf
445         # stable (not GC'able as in /nix/store) paths for using in <emulator> section of xml configs
446         for emulator in ${cfg.package}/libexec/libvirt_lxc ${cfg.qemu.package}/bin/qemu-kvm ${cfg.qemu.package}/bin/qemu-system-*; do
447           ln -s --force "$emulator" /run/${dirName}/nix-emulators/
448         done
450         ln -s --force ${cfg.qemu.package}/bin/qemu-pr-helper /run/${dirName}/nix-helpers/
452         ${optionalString cfg.qemu.ovmf.enable (let
453           ovmfpackage = pkgs.buildEnv {
454             name = "qemu-ovmf";
455             paths = cfg.qemu.ovmf.packages;
456           };
457         in
458           ''
459           ln -s --force ${ovmfpackage}/FV/AAVMF_CODE{,.ms}.fd /run/${dirName}/nix-ovmf/
460           ln -s --force ${ovmfpackage}/FV/OVMF_CODE{,.ms}.fd /run/${dirName}/nix-ovmf/
461           ln -s --force ${ovmfpackage}/FV/AAVMF_VARS{,.ms}.fd /run/${dirName}/nix-ovmf/
462           ln -s --force ${ovmfpackage}/FV/OVMF_VARS{,.ms}.fd /run/${dirName}/nix-ovmf/
463         '')}
465         # Symlink hooks to /var/lib/libvirt
466         ${concatStringsSep "\n" (map (driver:
467           ''
468           mkdir -p /var/lib/${dirName}/hooks/${driver}.d
469           rm -rf /var/lib/${dirName}/hooks/${driver}.d/*
470           ${concatStringsSep "\n" (mapAttrsToList (name: value:
471             "ln -s --force ${value} /var/lib/${dirName}/hooks/${driver}.d/${name}") cfg.hooks.${driver})}
472         '') (attrNames cfg.hooks))}
473       '';
475       serviceConfig = {
476         Type = "oneshot";
477         RuntimeDirectoryPreserve = "yes";
478         LogsDirectory = subDirs [ "qemu" ];
479         RuntimeDirectory = subDirs [ "nix-emulators" "nix-helpers" "nix-ovmf" ];
480         StateDirectory = subDirs [ "dnsmasq" ];
481       };
482     };
484     systemd.services.libvirtd = {
485       wantedBy = [ "multi-user.target" ];
486       requires = [ "libvirtd-config.service" ];
487       after = [ "libvirtd-config.service" ]
488         ++ optional vswitch.enable "ovs-vswitchd.service";
490       environment.LIBVIRTD_ARGS = escapeShellArgs (
491         [
492           "--config"
493           configFile
494           "--timeout"
495           "120" # from ${libvirt}/var/lib/sysconfig/libvirtd
496         ] ++ cfg.extraOptions
497       );
499       path = [ cfg.qemu.package pkgs.netcat ] # libvirtd requires qemu-img to manage disk images
500         ++ optional vswitch.enable vswitch.package
501         ++ optional cfg.qemu.swtpm.enable cfg.qemu.swtpm.package;
503       serviceConfig = {
504         Type = "notify";
505         KillMode = "process"; # when stopping, leave the VMs alone
506         Restart = "no";
507         OOMScoreAdjust = "-999";
508       };
509       restartIfChanged = false;
510     };
512     systemd.services.virtchd = {
513       path = [ pkgs.cloud-hypervisor ];
514     };
516     systemd.services.libvirt-guests = {
517       wantedBy = [ "multi-user.target" ];
518       path = with pkgs; [ coreutils gawk cfg.package ];
519       restartIfChanged = false;
521       environment.ON_BOOT = "${cfg.onBoot}";
522       environment.ON_SHUTDOWN = "${cfg.onShutdown}";
523       environment.PARALLEL_SHUTDOWN = "${toString cfg.parallelShutdown}";
524       environment.SHUTDOWN_TIMEOUT = "${toString cfg.shutdownTimeout}";
525       environment.START_DELAY = "${toString cfg.startDelay}";
526     };
528     systemd.sockets.virtlogd = {
529       description = "Virtual machine log manager socket";
530       wantedBy = [ "sockets.target" ];
531       listenStreams = [ "/run/${dirName}/virtlogd-sock" ];
532     };
534     systemd.services.virtlogd = {
535       description = "Virtual machine log manager";
536       serviceConfig.ExecStart = "@${cfg.package}/sbin/virtlogd virtlogd";
537       restartIfChanged = false;
538     };
540     systemd.sockets.virtlockd = {
541       description = "Virtual machine lock manager socket";
542       wantedBy = [ "sockets.target" ];
543       listenStreams = [ "/run/${dirName}/virtlockd-sock" ];
544     };
546     systemd.services.virtlockd = {
547       description = "Virtual machine lock manager";
548       serviceConfig.ExecStart = "@${cfg.package}/sbin/virtlockd virtlockd";
549       restartIfChanged = false;
550     };
552     # https://libvirt.org/daemons.html#monolithic-systemd-integration
553     systemd.sockets.libvirtd.wantedBy = [ "sockets.target" ];
555     systemd.tmpfiles.rules = let
556       vhostUserCollection = pkgs.buildEnv {
557         name = "vhost-user";
558         paths = cfg.qemu.vhostUserPackages;
559         pathsToLink = [ "/share/qemu/vhost-user" ];
560       };
561     in [
562       "L+ /var/lib/qemu/vhost-user - - - - ${vhostUserCollection}/share/qemu/vhost-user"
563       "L+ /var/lib/qemu/firmware - - - - ${cfg.qemu.package}/share/qemu/firmware"
564     ];
566     security.polkit = {
567       enable = true;
568       extraConfig = ''
569         polkit.addRule(function(action, subject) {
570           if (action.id == "org.libvirt.unix.manage" &&
571             subject.isInGroup("libvirtd")) {
572             return polkit.Result.YES;
573           }
574         });
575       '';
576     };
578     system.nssModules = optional (cfg.nss.enable or cfg.nss.enableGuest) cfg.package;
579     system.nssDatabases.hosts = mkMerge [
580       # ensure that the NSS modules come between mymachines (which is 400) and resolve (which is 501)
581       (mkIf cfg.nss.enable (mkOrder 430 [ "libvirt" ]))
582       (mkIf cfg.nss.enableGuest (mkOrder 432 [ "libvirt_guest" ]))
583     ];
584   };