1 { config, lib, pkgs, ... }:
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"
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" ]
18 ${optionalString (!cfg.qemu.runAsRoot) ''
19 user = "qemu-libvirtd"
20 group = "qemu-libvirtd"
22 ${cfg.qemu.verbatimConfig}
25 subDirs = list: [ dirName ] ++ map (e: "${dirName}/${e}") list;
27 ovmfModule = types.submodule {
32 description = lib.mdDoc ''
33 Allows libvirtd to take advantage of OVMF when creating new
34 QEMU VMs with UEFI boot.
38 # mkRemovedOptionModule does not work in submodules, do it manually
40 type = types.nullOr types.package;
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
57 swtpmModule = types.submodule {
62 description = lib.mdDoc ''
63 Allows libvirtd to use swtpm to create an emulated TPM.
70 defaultText = literalExpression "pkgs.swtpm";
71 description = lib.mdDoc ''
78 qemuModule = types.submodule {
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.
91 runAsRoot = mkOption {
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.
103 verbatimConfig = mkOption {
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.
118 description = lib.mdDoc ''
126 description = lib.mdDoc ''
127 QEMU's swtpm options.
133 hooksModule = types.submodule {
136 type = types.attrsOf types.path;
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.
146 type = types.attrsOf types.path;
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.
156 type = types.attrsOf types.path;
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.
166 type = types.attrsOf types.path;
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.
176 type = types.attrsOf types.path;
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.
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" ])
214 options.virtualisation.libvirtd = {
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.
228 type = types.package;
229 default = pkgs.libvirt;
230 defaultText = literalExpression "pkgs.libvirt";
231 description = lib.mdDoc ''
232 libvirt package to use.
236 extraConfig = mkOption {
239 description = lib.mdDoc ''
240 Extra contents appended to the libvirtd configuration file,
245 extraOptions = mkOption {
246 type = types.listOf types.str;
248 example = [ "--verbose" ];
249 description = lib.mdDoc ''
250 Extra command line arguments passed to libvirtd on startup.
255 type = types.enum [ "start" "ignore" ];
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.
266 onShutdown = mkOption {
267 type = types.enum [ "shutdown" "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.
277 parallelShutdown = mkOption {
278 type = types.ints.unsigned;
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
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
299 description = lib.mdDoc ''
300 QEMU related options.
307 description = lib.mdDoc ''
308 Hooks related options.
314 ###### implementation
316 config = mkIf cfg.enable {
320 assertion = config.virtualisation.libvirtd.qemu.ovmf.package == null;
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.
327 assertion = config.security.polkit.enable;
328 message = "The libvirtd module currently requires Polkit to be enabled ('security.polkit.enable = true').";
333 # this file is expected in /etc/qemu and not sysconfdir (/var/lib)
334 etc."qemu/bridge.conf".text = lib.concatMapStringsSep "\n"
338 systemPackages = with pkgs; [ libressl.nc iptables cfg.package cfg.qemu.package ];
339 etc.ethertypes.source = "${pkgs.iptables}/etc/ethertypes";
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";
354 security.wrappers.qemu-bridge-helper = {
358 source = "${cfg.qemu.package}/libexec/qemu-bridge-helper";
361 systemd.packages = [ cfg.package ];
363 systemd.services.libvirtd-config = {
364 description = "Libvirt Virtual Machine Management Daemon - configuration";
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 );
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
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/
386 for helper in bin/qemu-pr-helper; do
387 ln -s --force ${cfg.qemu.package}/$helper /run/${dirName}/nix-helpers/
390 ${optionalString cfg.qemu.ovmf.enable (let
391 ovmfpackage = pkgs.buildEnv {
393 paths = cfg.qemu.ovmf.packages;
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/
403 # Symlink hooks to /var/lib/libvirt
404 ${concatStringsSep "\n" (map (driver:
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))}
415 RuntimeDirectoryPreserve = "yes";
416 LogsDirectory = subDirs [ "qemu" ];
417 RuntimeDirectory = subDirs [ "nix-emulators" "nix-helpers" "nix-ovmf" ];
418 StateDirectory = subDirs [ "dnsmasq" ];
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 (
433 "120" # from ${libvirt}/var/lib/sysconfig/libvirtd
434 ] ++ cfg.extraOptions
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;
443 KillMode = "process"; # when stopping, leave the VMs alone
446 restartIfChanged = false;
449 systemd.services.virtchd = {
450 path = [ pkgs.cloud-hypervisor ];
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}";
463 systemd.sockets.virtlogd = {
464 description = "Virtual machine log manager socket";
465 wantedBy = [ "sockets.target" ];
466 listenStreams = [ "/run/${dirName}/virtlogd-sock" ];
469 systemd.services.virtlogd = {
470 description = "Virtual machine log manager";
471 serviceConfig.ExecStart = "@${cfg.package}/sbin/virtlogd virtlogd";
472 restartIfChanged = false;
475 systemd.sockets.virtlockd = {
476 description = "Virtual machine lock manager socket";
477 wantedBy = [ "sockets.target" ];
478 listenStreams = [ "/run/${dirName}/virtlockd-sock" ];
481 systemd.services.virtlockd = {
482 description = "Virtual machine lock manager";
483 serviceConfig.ExecStart = "@${cfg.package}/sbin/virtlockd virtlockd";
484 restartIfChanged = false;
487 # https://libvirt.org/daemons.html#monolithic-systemd-integration
488 systemd.sockets.libvirtd.wantedBy = [ "sockets.target" ];
493 polkit.addRule(function(action, subject) {
494 if (action.id == "org.libvirt.unix.manage" &&
495 subject.isInGroup("libvirtd")) {
496 return polkit.Result.YES;