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