at 24.05-pre 16 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 = lib.mdDoc '' 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 = 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 52 ''; 53 }; 54 }; 55 }; 56 57 swtpmModule = types.submodule { 58 options = { 59 enable = mkOption { 60 type = types.bool; 61 default = false; 62 description = lib.mdDoc '' 63 Allows libvirtd to use swtpm to create an emulated TPM. 64 ''; 65 }; 66 67 package = mkOption { 68 type = types.package; 69 default = pkgs.swtpm; 70 defaultText = literalExpression "pkgs.swtpm"; 71 description = lib.mdDoc '' 72 swtpm package to use. 73 ''; 74 }; 75 }; 76 }; 77 78 qemuModule = types.submodule { 79 options = { 80 package = mkOption { 81 type = types.package; 82 default = pkgs.qemu; 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. 88 ''; 89 }; 90 91 runAsRoot = mkOption { 92 type = types.bool; 93 default = true; 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. 100 ''; 101 }; 102 103 verbatimConfig = mkOption { 104 type = types.lines; 105 default = '' 106 namespaces = [] 107 ''; 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. 112 ''; 113 }; 114 115 ovmf = mkOption { 116 type = ovmfModule; 117 default = { }; 118 description = lib.mdDoc '' 119 QEMU's OVMF options. 120 ''; 121 }; 122 123 swtpm = mkOption { 124 type = swtpmModule; 125 default = { }; 126 description = lib.mdDoc '' 127 QEMU's swtpm options. 128 ''; 129 }; 130 }; 131 }; 132 133 hooksModule = types.submodule { 134 options = { 135 daemon = mkOption { 136 type = types.attrsOf types.path; 137 default = { }; 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. 142 ''; 143 }; 144 145 qemu = mkOption { 146 type = types.attrsOf types.path; 147 default = { }; 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. 152 ''; 153 }; 154 155 lxc = mkOption { 156 type = types.attrsOf types.path; 157 default = { }; 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. 162 ''; 163 }; 164 165 libxl = mkOption { 166 type = types.attrsOf types.path; 167 default = { }; 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. 172 ''; 173 }; 174 175 network = mkOption { 176 type = types.attrsOf types.path; 177 default = { }; 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. 182 ''; 183 }; 184 }; 185 }; 186in 187{ 188 189 imports = [ 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" ]) 210 ]; 211 212 ###### interface 213 214 options.virtualisation.libvirtd = { 215 216 enable = mkOption { 217 type = types.bool; 218 default = false; 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. 224 ''; 225 }; 226 227 package = mkOption { 228 type = types.package; 229 default = pkgs.libvirt; 230 defaultText = literalExpression "pkgs.libvirt"; 231 description = lib.mdDoc '' 232 libvirt package to use. 233 ''; 234 }; 235 236 extraConfig = mkOption { 237 type = types.lines; 238 default = ""; 239 description = lib.mdDoc '' 240 Extra contents appended to the libvirtd configuration file, 241 libvirtd.conf. 242 ''; 243 }; 244 245 extraOptions = mkOption { 246 type = types.listOf types.str; 247 default = [ ]; 248 example = [ "--verbose" ]; 249 description = lib.mdDoc '' 250 Extra command line arguments passed to libvirtd on startup. 251 ''; 252 }; 253 254 onBoot = mkOption { 255 type = types.enum [ "start" "ignore" ]; 256 default = "start"; 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. 263 ''; 264 }; 265 266 onShutdown = mkOption { 267 type = types.enum [ "shutdown" "suspend" ]; 268 default = "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. 274 ''; 275 }; 276 277 parallelShutdown = mkOption { 278 type = types.ints.unsigned; 279 default = 0; 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 284 variable. 285 ''; 286 }; 287 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 293 ''; 294 }; 295 296 qemu = mkOption { 297 type = qemuModule; 298 default = { }; 299 description = lib.mdDoc '' 300 QEMU related options. 301 ''; 302 }; 303 304 hooks = mkOption { 305 type = hooksModule; 306 default = { }; 307 description = lib.mdDoc '' 308 Hooks related options. 309 ''; 310 }; 311 }; 312 313 314 ###### implementation 315 316 config = mkIf cfg.enable { 317 318 assertions = [ 319 { 320 assertion = config.virtualisation.libvirtd.qemu.ovmf.package == null; 321 message = '' 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. 324 ''; 325 } 326 { 327 assertion = config.security.polkit.enable; 328 message = "The libvirtd module currently requires Polkit to be enabled ('security.polkit.enable = true')."; 329 } 330 ]; 331 332 environment = { 333 # this file is expected in /etc/qemu and not sysconfdir (/var/lib) 334 etc."qemu/bridge.conf".text = lib.concatMapStringsSep "\n" 335 (e: 336 "allow ${e}") 337 cfg.allowedBridges; 338 systemPackages = with pkgs; [ libressl.nc iptables cfg.package cfg.qemu.package ]; 339 etc.ethertypes.source = "${pkgs.iptables}/etc/ethertypes"; 340 }; 341 342 boot.kernelModules = [ "tun" ]; 343 344 users.groups.libvirtd.gid = config.ids.gids.libvirtd; 345 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"; 352 }; 353 354 security.wrappers.qemu-bridge-helper = { 355 setuid = true; 356 owner = "root"; 357 group = "root"; 358 source = "${cfg.qemu.package}/libexec/qemu-bridge-helper"; 359 }; 360 361 systemd.packages = [ cfg.package ]; 362 363 systemd.services.libvirtd-config = { 364 description = "Libvirt Virtual Machine Management Daemon - configuration"; 365 script = '' 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 ); 371 do 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 375 fi 376 done 377 378 # Copy generated qemu config to libvirt directory 379 cp -f ${qemuConfigFile} /var/lib/${dirName}/qemu.conf 380 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/ 384 done 385 386 for helper in bin/qemu-pr-helper; do 387 ln -s --force ${cfg.qemu.package}/$helper /run/${dirName}/nix-helpers/ 388 done 389 390 ${optionalString cfg.qemu.ovmf.enable (let 391 ovmfpackage = pkgs.buildEnv { 392 name = "qemu-ovmf"; 393 paths = cfg.qemu.ovmf.packages; 394 }; 395 in 396 '' 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/ 401 '')} 402 403 # Symlink hooks to /var/lib/libvirt 404 ${concatStringsSep "\n" (map (driver: 405 '' 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))} 411 ''; 412 413 serviceConfig = { 414 Type = "oneshot"; 415 RuntimeDirectoryPreserve = "yes"; 416 LogsDirectory = subDirs [ "qemu" ]; 417 RuntimeDirectory = subDirs [ "nix-emulators" "nix-helpers" "nix-ovmf" ]; 418 StateDirectory = subDirs [ "dnsmasq" ]; 419 }; 420 }; 421 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"; 427 428 environment.LIBVIRTD_ARGS = escapeShellArgs ( 429 [ 430 "--config" 431 configFile 432 "--timeout" 433 "120" # from ${libvirt}/var/lib/sysconfig/libvirtd 434 ] ++ cfg.extraOptions 435 ); 436 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; 440 441 serviceConfig = { 442 Type = "notify"; 443 KillMode = "process"; # when stopping, leave the VMs alone 444 Restart = "no"; 445 }; 446 restartIfChanged = false; 447 }; 448 449 systemd.services.virtchd = { 450 path = [ pkgs.cloud-hypervisor ]; 451 }; 452 453 systemd.services.libvirt-guests = { 454 wantedBy = [ "multi-user.target" ]; 455 path = with pkgs; [ coreutils gawk cfg.package ]; 456 restartIfChanged = false; 457 458 environment.ON_BOOT = "${cfg.onBoot}"; 459 environment.ON_SHUTDOWN = "${cfg.onShutdown}"; 460 environment.PARALLEL_SHUTDOWN = "${toString cfg.parallelShutdown}"; 461 }; 462 463 systemd.sockets.virtlogd = { 464 description = "Virtual machine log manager socket"; 465 wantedBy = [ "sockets.target" ]; 466 listenStreams = [ "/run/${dirName}/virtlogd-sock" ]; 467 }; 468 469 systemd.services.virtlogd = { 470 description = "Virtual machine log manager"; 471 serviceConfig.ExecStart = "@${cfg.package}/sbin/virtlogd virtlogd"; 472 restartIfChanged = false; 473 }; 474 475 systemd.sockets.virtlockd = { 476 description = "Virtual machine lock manager socket"; 477 wantedBy = [ "sockets.target" ]; 478 listenStreams = [ "/run/${dirName}/virtlockd-sock" ]; 479 }; 480 481 systemd.services.virtlockd = { 482 description = "Virtual machine lock manager"; 483 serviceConfig.ExecStart = "@${cfg.package}/sbin/virtlockd virtlockd"; 484 restartIfChanged = false; 485 }; 486 487 # https://libvirt.org/daemons.html#monolithic-systemd-integration 488 systemd.sockets.libvirtd.wantedBy = [ "sockets.target" ]; 489 490 security.polkit = { 491 enable = true; 492 extraConfig = '' 493 polkit.addRule(function(action, subject) { 494 if (action.id == "org.libvirt.unix.manage" && 495 subject.isInGroup("libvirtd")) { 496 return polkit.Result.YES; 497 } 498 }); 499 ''; 500 }; 501 }; 502}