at 23.11-pre 14 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 }; 132in 133{ 134 135 imports = [ 136 (mkRemovedOptionModule [ "virtualisation" "libvirtd" "enableKVM" ] 137 "Set the option `virtualisation.libvirtd.qemu.package' instead.") 138 (mkRenamedOptionModule 139 [ "virtualisation" "libvirtd" "qemuPackage" ] 140 [ "virtualisation" "libvirtd" "qemu" "package" ]) 141 (mkRenamedOptionModule 142 [ "virtualisation" "libvirtd" "qemuRunAsRoot" ] 143 [ "virtualisation" "libvirtd" "qemu" "runAsRoot" ]) 144 (mkRenamedOptionModule 145 [ "virtualisation" "libvirtd" "qemuVerbatimConfig" ] 146 [ "virtualisation" "libvirtd" "qemu" "verbatimConfig" ]) 147 (mkRenamedOptionModule 148 [ "virtualisation" "libvirtd" "qemuOvmf" ] 149 [ "virtualisation" "libvirtd" "qemu" "ovmf" "enable" ]) 150 (mkRemovedOptionModule 151 [ "virtualisation" "libvirtd" "qemuOvmfPackage" ] 152 "If this option was set to `foo`, set the option `virtualisation.libvirtd.qemu.ovmf.packages' to `[foo.fd]` instead.") 153 (mkRenamedOptionModule 154 [ "virtualisation" "libvirtd" "qemuSwtpm" ] 155 [ "virtualisation" "libvirtd" "qemu" "swtpm" "enable" ]) 156 ]; 157 158 ###### interface 159 160 options.virtualisation.libvirtd = { 161 162 enable = mkOption { 163 type = types.bool; 164 default = false; 165 description = lib.mdDoc '' 166 This option enables libvirtd, a daemon that manages 167 virtual machines. Users in the "libvirtd" group can interact with 168 the daemon (e.g. to start or stop VMs) using the 169 {command}`virsh` command line tool, among others. 170 ''; 171 }; 172 173 package = mkOption { 174 type = types.package; 175 default = pkgs.libvirt; 176 defaultText = literalExpression "pkgs.libvirt"; 177 description = lib.mdDoc '' 178 libvirt package to use. 179 ''; 180 }; 181 182 extraConfig = mkOption { 183 type = types.lines; 184 default = ""; 185 description = lib.mdDoc '' 186 Extra contents appended to the libvirtd configuration file, 187 libvirtd.conf. 188 ''; 189 }; 190 191 extraOptions = mkOption { 192 type = types.listOf types.str; 193 default = [ ]; 194 example = [ "--verbose" ]; 195 description = lib.mdDoc '' 196 Extra command line arguments passed to libvirtd on startup. 197 ''; 198 }; 199 200 onBoot = mkOption { 201 type = types.enum [ "start" "ignore" ]; 202 default = "start"; 203 description = lib.mdDoc '' 204 Specifies the action to be done to / on the guests when the host boots. 205 The "start" option starts all guests that were running prior to shutdown 206 regardless of their autostart settings. The "ignore" option will not 207 start the formerly running guest on boot. However, any guest marked as 208 autostart will still be automatically started by libvirtd. 209 ''; 210 }; 211 212 onShutdown = mkOption { 213 type = types.enum [ "shutdown" "suspend" ]; 214 default = "suspend"; 215 description = lib.mdDoc '' 216 When shutting down / restarting the host what method should 217 be used to gracefully halt the guests. Setting to "shutdown" 218 will cause an ACPI shutdown of each guest. "suspend" will 219 attempt to save the state of the guests ready to restore on boot. 220 ''; 221 }; 222 223 parallelShutdown = mkOption { 224 type = types.ints.unsigned; 225 default = 0; 226 description = lib.mdDoc '' 227 Number of guests that will be shutdown concurrently, taking effect when onShutdown 228 is set to "shutdown". If set to 0, guests will be shutdown one after another. 229 Number of guests on shutdown at any time will not exceed number set in this 230 variable. 231 ''; 232 }; 233 234 allowedBridges = mkOption { 235 type = types.listOf types.str; 236 default = [ "virbr0" ]; 237 description = lib.mdDoc '' 238 List of bridge devices that can be used by qemu:///session 239 ''; 240 }; 241 242 qemu = mkOption { 243 type = qemuModule; 244 default = { }; 245 description = lib.mdDoc '' 246 QEMU related options. 247 ''; 248 }; 249 }; 250 251 252 ###### implementation 253 254 config = mkIf cfg.enable { 255 256 assertions = [ 257 { 258 assertion = config.virtualisation.libvirtd.qemu.ovmf.package == null; 259 message = '' 260 The option virtualisation.libvirtd.qemu.ovmf.package is superseded by virtualisation.libvirtd.qemu.ovmf.packages. 261 If this option was set to `foo`, set the option `virtualisation.libvirtd.qemu.ovmf.packages' to `[foo.fd]` instead. 262 ''; 263 } 264 { 265 assertion = config.security.polkit.enable; 266 message = "The libvirtd module currently requires Polkit to be enabled ('security.polkit.enable = true')."; 267 } 268 ]; 269 270 environment = { 271 # this file is expected in /etc/qemu and not sysconfdir (/var/lib) 272 etc."qemu/bridge.conf".text = lib.concatMapStringsSep "\n" 273 (e: 274 "allow ${e}") 275 cfg.allowedBridges; 276 systemPackages = with pkgs; [ libressl.nc iptables cfg.package cfg.qemu.package ]; 277 etc.ethertypes.source = "${pkgs.iptables}/etc/ethertypes"; 278 }; 279 280 boot.kernelModules = [ "tun" ]; 281 282 users.groups.libvirtd.gid = config.ids.gids.libvirtd; 283 284 # libvirtd runs qemu as this user and group by default 285 users.extraGroups.qemu-libvirtd.gid = config.ids.gids.qemu-libvirtd; 286 users.extraUsers.qemu-libvirtd = { 287 uid = config.ids.uids.qemu-libvirtd; 288 isNormalUser = false; 289 group = "qemu-libvirtd"; 290 }; 291 292 security.wrappers.qemu-bridge-helper = { 293 setuid = true; 294 owner = "root"; 295 group = "root"; 296 source = "${cfg.qemu.package}/libexec/qemu-bridge-helper"; 297 }; 298 299 systemd.packages = [ cfg.package ]; 300 301 systemd.services.libvirtd-config = { 302 description = "Libvirt Virtual Machine Management Daemon - configuration"; 303 script = '' 304 # Copy default libvirt network config .xml files to /var/lib 305 # Files modified by the user will not be overwritten 306 for i in $(cd ${cfg.package}/var/lib && echo \ 307 libvirt/qemu/networks/*.xml \ 308 libvirt/nwfilter/*.xml ); 309 do 310 mkdir -p /var/lib/$(dirname $i) -m 755 311 cp -npd ${cfg.package}/var/lib/$i /var/lib/$i 312 done 313 314 # Copy generated qemu config to libvirt directory 315 cp -f ${qemuConfigFile} /var/lib/${dirName}/qemu.conf 316 317 # stable (not GC'able as in /nix/store) paths for using in <emulator> section of xml configs 318 for emulator in ${cfg.package}/libexec/libvirt_lxc ${cfg.qemu.package}/bin/qemu-kvm ${cfg.qemu.package}/bin/qemu-system-*; do 319 ln -s --force "$emulator" /run/${dirName}/nix-emulators/ 320 done 321 322 for helper in bin/qemu-pr-helper; do 323 ln -s --force ${cfg.qemu.package}/$helper /run/${dirName}/nix-helpers/ 324 done 325 326 ${optionalString cfg.qemu.ovmf.enable (let 327 ovmfpackage = pkgs.buildEnv { 328 name = "qemu-ovmf"; 329 paths = cfg.qemu.ovmf.packages; 330 }; 331 in 332 '' 333 ln -s --force ${ovmfpackage}/FV/AAVMF_CODE.fd /run/${dirName}/nix-ovmf/ 334 ln -s --force ${ovmfpackage}/FV/OVMF_CODE.fd /run/${dirName}/nix-ovmf/ 335 ln -s --force ${ovmfpackage}/FV/AAVMF_VARS.fd /run/${dirName}/nix-ovmf/ 336 ln -s --force ${ovmfpackage}/FV/OVMF_VARS.fd /run/${dirName}/nix-ovmf/ 337 '')} 338 ''; 339 340 serviceConfig = { 341 Type = "oneshot"; 342 RuntimeDirectoryPreserve = "yes"; 343 LogsDirectory = subDirs [ "qemu" ]; 344 RuntimeDirectory = subDirs [ "nix-emulators" "nix-helpers" "nix-ovmf" ]; 345 StateDirectory = subDirs [ "dnsmasq" ]; 346 }; 347 }; 348 349 systemd.services.libvirtd = { 350 wantedBy = [ "multi-user.target" ]; 351 requires = [ "libvirtd-config.service" ]; 352 after = [ "libvirtd-config.service" ] 353 ++ optional vswitch.enable "ovs-vswitchd.service"; 354 355 environment.LIBVIRTD_ARGS = escapeShellArgs ( 356 [ 357 "--config" 358 configFile 359 "--timeout" 360 "120" # from ${libvirt}/var/lib/sysconfig/libvirtd 361 ] ++ cfg.extraOptions 362 ); 363 364 path = [ cfg.qemu.package ] # libvirtd requires qemu-img to manage disk images 365 ++ optional vswitch.enable vswitch.package 366 ++ optional cfg.qemu.swtpm.enable cfg.qemu.swtpm.package; 367 368 serviceConfig = { 369 Type = "notify"; 370 KillMode = "process"; # when stopping, leave the VMs alone 371 Restart = "no"; 372 }; 373 restartIfChanged = false; 374 }; 375 376 systemd.services.virtchd = { 377 path = [ pkgs.cloud-hypervisor ]; 378 }; 379 380 systemd.services.libvirt-guests = { 381 wantedBy = [ "multi-user.target" ]; 382 path = with pkgs; [ coreutils gawk cfg.package ]; 383 restartIfChanged = false; 384 385 environment.ON_BOOT = "${cfg.onBoot}"; 386 environment.ON_SHUTDOWN = "${cfg.onShutdown}"; 387 environment.PARALLEL_SHUTDOWN = "${toString cfg.parallelShutdown}"; 388 }; 389 390 systemd.sockets.virtlogd = { 391 description = "Virtual machine log manager socket"; 392 wantedBy = [ "sockets.target" ]; 393 listenStreams = [ "/run/${dirName}/virtlogd-sock" ]; 394 }; 395 396 systemd.services.virtlogd = { 397 description = "Virtual machine log manager"; 398 serviceConfig.ExecStart = "@${cfg.package}/sbin/virtlogd virtlogd"; 399 restartIfChanged = false; 400 }; 401 402 systemd.sockets.virtlockd = { 403 description = "Virtual machine lock manager socket"; 404 wantedBy = [ "sockets.target" ]; 405 listenStreams = [ "/run/${dirName}/virtlockd-sock" ]; 406 }; 407 408 systemd.services.virtlockd = { 409 description = "Virtual machine lock manager"; 410 serviceConfig.ExecStart = "@${cfg.package}/sbin/virtlockd virtlockd"; 411 restartIfChanged = false; 412 }; 413 414 # https://libvirt.org/daemons.html#monolithic-systemd-integration 415 systemd.sockets.libvirtd.wantedBy = [ "sockets.target" ]; 416 417 security.polkit = { 418 enable = true; 419 extraConfig = '' 420 polkit.addRule(function(action, subject) { 421 if (action.id == "org.libvirt.unix.manage" && 422 subject.isInGroup("libvirtd")) { 423 return polkit.Result.YES; 424 } 425 }); 426 ''; 427 }; 428 }; 429}