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