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