at 22.05-pre 12 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 ovmfFilePrefix = if pkgs.stdenv.isAarch64 then "AAVMF" else "OVMF"; 15 qemuConfigFile = pkgs.writeText "qemu.conf" '' 16 ${optionalString cfg.qemu.ovmf.enable '' 17 nvram = [ "/run/libvirt/nix-ovmf/${ovmfFilePrefix}_CODE.fd:/run/libvirt/nix-ovmf/${ovmfFilePrefix}_VARS.fd" ] 18 ''} 19 ${optionalString (!cfg.qemu.runAsRoot) '' 20 user = "qemu-libvirtd" 21 group = "qemu-libvirtd" 22 ''} 23 ${cfg.qemu.verbatimConfig} 24 ''; 25 dirName = "libvirt"; 26 subDirs = list: [ dirName ] ++ map (e: "${dirName}/${e}") list; 27 28 ovmfModule = types.submodule { 29 options = { 30 enable = mkOption { 31 type = types.bool; 32 default = true; 33 description = '' 34 Allows libvirtd to take advantage of OVMF when creating new 35 QEMU VMs with UEFI boot. 36 ''; 37 }; 38 39 package = mkOption { 40 type = types.package; 41 default = pkgs.OVMF; 42 defaultText = literalExpression "pkgs.OVMF"; 43 example = literalExpression "pkgs.OVMFFull"; 44 description = '' 45 OVMF package to use. 46 ''; 47 }; 48 }; 49 }; 50 51 swtpmModule = types.submodule { 52 options = { 53 enable = mkOption { 54 type = types.bool; 55 default = false; 56 description = '' 57 Allows libvirtd to use swtpm to create an emulated TPM. 58 ''; 59 }; 60 61 package = mkOption { 62 type = types.package; 63 default = pkgs.swtpm; 64 defaultText = literalExpression "pkgs.swtpm"; 65 description = '' 66 swtpm package to use. 67 ''; 68 }; 69 }; 70 }; 71 72 qemuModule = types.submodule { 73 options = { 74 package = mkOption { 75 type = types.package; 76 default = pkgs.qemu; 77 defaultText = literalExpression "pkgs.qemu"; 78 description = '' 79 Qemu package to use with libvirt. 80 `pkgs.qemu` can emulate alien architectures (e.g. aarch64 on x86) 81 `pkgs.qemu_kvm` saves disk space allowing to emulate only host architectures. 82 ''; 83 }; 84 85 runAsRoot = mkOption { 86 type = types.bool; 87 default = true; 88 description = '' 89 If true, libvirtd runs qemu as root. 90 If false, libvirtd runs qemu as unprivileged user qemu-libvirtd. 91 Changing this option to false may cause file permission issues 92 for existing guests. To fix these, manually change ownership 93 of affected files in /var/lib/libvirt/qemu to qemu-libvirtd. 94 ''; 95 }; 96 97 verbatimConfig = mkOption { 98 type = types.lines; 99 default = '' 100 namespaces = [] 101 ''; 102 description = '' 103 Contents written to the qemu configuration file, qemu.conf. 104 Make sure to include a proper namespace configuration when 105 supplying custom configuration. 106 ''; 107 }; 108 109 ovmf = mkOption { 110 type = ovmfModule; 111 default = { }; 112 description = '' 113 QEMU's OVMF options. 114 ''; 115 }; 116 117 swtpm = mkOption { 118 type = swtpmModule; 119 default = { }; 120 description = '' 121 QEMU's swtpm options. 122 ''; 123 }; 124 }; 125 }; 126in 127{ 128 129 imports = [ 130 (mkRemovedOptionModule [ "virtualisation" "libvirtd" "enableKVM" ] 131 "Set the option `virtualisation.libvirtd.qemu.package' instead.") 132 (mkRenamedOptionModule 133 [ "virtualisation" "libvirtd" "qemuPackage" ] 134 [ "virtualisation" "libvirtd" "qemu" "package" ]) 135 (mkRenamedOptionModule 136 [ "virtualisation" "libvirtd" "qemuRunAsRoot" ] 137 [ "virtualisation" "libvirtd" "qemu" "runAsRoot" ]) 138 (mkRenamedOptionModule 139 [ "virtualisation" "libvirtd" "qemuVerbatimConfig" ] 140 [ "virtualisation" "libvirtd" "qemu" "verbatimConfig" ]) 141 (mkRenamedOptionModule 142 [ "virtualisation" "libvirtd" "qemuOvmf" ] 143 [ "virtualisation" "libvirtd" "qemu" "ovmf" "enable" ]) 144 (mkRenamedOptionModule 145 [ "virtualisation" "libvirtd" "qemuOvmfPackage" ] 146 [ "virtualisation" "libvirtd" "qemu" "ovmf" "package" ]) 147 (mkRenamedOptionModule 148 [ "virtualisation" "libvirtd" "qemuSwtpm" ] 149 [ "virtualisation" "libvirtd" "qemu" "swtpm" "enable" ]) 150 ]; 151 152 ###### interface 153 154 options.virtualisation.libvirtd = { 155 156 enable = mkOption { 157 type = types.bool; 158 default = false; 159 description = '' 160 This option enables libvirtd, a daemon that manages 161 virtual machines. Users in the "libvirtd" group can interact with 162 the daemon (e.g. to start or stop VMs) using the 163 <command>virsh</command> command line tool, among others. 164 ''; 165 }; 166 167 package = mkOption { 168 type = types.package; 169 default = pkgs.libvirt; 170 defaultText = literalExpression "pkgs.libvirt"; 171 description = '' 172 libvirt package to use. 173 ''; 174 }; 175 176 extraConfig = mkOption { 177 type = types.lines; 178 default = ""; 179 description = '' 180 Extra contents appended to the libvirtd configuration file, 181 libvirtd.conf. 182 ''; 183 }; 184 185 extraOptions = mkOption { 186 type = types.listOf types.str; 187 default = [ ]; 188 example = [ "--verbose" ]; 189 description = '' 190 Extra command line arguments passed to libvirtd on startup. 191 ''; 192 }; 193 194 onBoot = mkOption { 195 type = types.enum [ "start" "ignore" ]; 196 default = "start"; 197 description = '' 198 Specifies the action to be done to / on the guests when the host boots. 199 The "start" option starts all guests that were running prior to shutdown 200 regardless of their autostart settings. The "ignore" option will not 201 start the formerly running guest on boot. However, any guest marked as 202 autostart will still be automatically started by libvirtd. 203 ''; 204 }; 205 206 onShutdown = mkOption { 207 type = types.enum [ "shutdown" "suspend" ]; 208 default = "suspend"; 209 description = '' 210 When shutting down / restarting the host what method should 211 be used to gracefully halt the guests. Setting to "shutdown" 212 will cause an ACPI shutdown of each guest. "suspend" will 213 attempt to save the state of the guests ready to restore on boot. 214 ''; 215 }; 216 217 allowedBridges = mkOption { 218 type = types.listOf types.str; 219 default = [ "virbr0" ]; 220 description = '' 221 List of bridge devices that can be used by qemu:///session 222 ''; 223 }; 224 225 qemu = mkOption { 226 type = qemuModule; 227 default = { }; 228 description = '' 229 QEMU related options. 230 ''; 231 }; 232 }; 233 234 235 ###### implementation 236 237 config = mkIf cfg.enable { 238 239 assertions = [ 240 { 241 assertion = config.security.polkit.enable; 242 message = "The libvirtd module currently requires Polkit to be enabled ('security.polkit.enable = true')."; 243 } 244 { 245 assertion = builtins.elem "fd" cfg.qemu.ovmf.package.outputs; 246 message = "The option 'virtualisation.libvirtd.qemuOvmfPackage' needs a package that has an 'fd' output."; 247 } 248 ]; 249 250 environment = { 251 # this file is expected in /etc/qemu and not sysconfdir (/var/lib) 252 etc."qemu/bridge.conf".text = lib.concatMapStringsSep "\n" 253 (e: 254 "allow ${e}") 255 cfg.allowedBridges; 256 systemPackages = with pkgs; [ libressl.nc iptables cfg.package cfg.qemu.package ]; 257 etc.ethertypes.source = "${pkgs.iptables}/etc/ethertypes"; 258 }; 259 260 boot.kernelModules = [ "tun" ]; 261 262 users.groups.libvirtd.gid = config.ids.gids.libvirtd; 263 264 # libvirtd runs qemu as this user and group by default 265 users.extraGroups.qemu-libvirtd.gid = config.ids.gids.qemu-libvirtd; 266 users.extraUsers.qemu-libvirtd = { 267 uid = config.ids.uids.qemu-libvirtd; 268 isNormalUser = false; 269 group = "qemu-libvirtd"; 270 }; 271 272 security.wrappers.qemu-bridge-helper = { 273 setuid = true; 274 owner = "root"; 275 group = "root"; 276 source = "/run/${dirName}/nix-helpers/qemu-bridge-helper"; 277 }; 278 279 systemd.packages = [ cfg.package ]; 280 281 systemd.services.libvirtd-config = { 282 description = "Libvirt Virtual Machine Management Daemon - configuration"; 283 script = '' 284 # Copy default libvirt network config .xml files to /var/lib 285 # Files modified by the user will not be overwritten 286 for i in $(cd ${cfg.package}/var/lib && echo \ 287 libvirt/qemu/networks/*.xml libvirt/qemu/networks/autostart/*.xml \ 288 libvirt/nwfilter/*.xml ); 289 do 290 mkdir -p /var/lib/$(dirname $i) -m 755 291 cp -npd ${cfg.package}/var/lib/$i /var/lib/$i 292 done 293 294 # Copy generated qemu config to libvirt directory 295 cp -f ${qemuConfigFile} /var/lib/${dirName}/qemu.conf 296 297 # stable (not GC'able as in /nix/store) paths for using in <emulator> section of xml configs 298 for emulator in ${cfg.package}/libexec/libvirt_lxc ${cfg.qemu.package}/bin/qemu-kvm ${cfg.qemu.package}/bin/qemu-system-*; do 299 ln -s --force "$emulator" /run/${dirName}/nix-emulators/ 300 done 301 302 for helper in libexec/qemu-bridge-helper bin/qemu-pr-helper; do 303 ln -s --force ${cfg.qemu.package}/$helper /run/${dirName}/nix-helpers/ 304 done 305 306 ${optionalString cfg.qemu.ovmf.enable '' 307 ln -s --force ${cfg.qemu.ovmf.package.fd}/FV/${ovmfFilePrefix}_CODE.fd /run/${dirName}/nix-ovmf/ 308 ln -s --force ${cfg.qemu.ovmf.package.fd}/FV/${ovmfFilePrefix}_VARS.fd /run/${dirName}/nix-ovmf/ 309 ''} 310 ''; 311 312 serviceConfig = { 313 Type = "oneshot"; 314 RuntimeDirectoryPreserve = "yes"; 315 LogsDirectory = subDirs [ "qemu" ]; 316 RuntimeDirectory = subDirs [ "nix-emulators" "nix-helpers" "nix-ovmf" ]; 317 StateDirectory = subDirs [ "dnsmasq" ]; 318 }; 319 }; 320 321 systemd.services.libvirtd = { 322 requires = [ "libvirtd-config.service" ]; 323 after = [ "libvirtd-config.service" ] 324 ++ optional vswitch.enable "ovs-vswitchd.service"; 325 326 environment.LIBVIRTD_ARGS = escapeShellArgs ( 327 [ 328 "--config" 329 configFile 330 "--timeout" 331 "120" # from ${libvirt}/var/lib/sysconfig/libvirtd 332 ] ++ cfg.extraOptions 333 ); 334 335 path = [ cfg.qemu.package ] # libvirtd requires qemu-img to manage disk images 336 ++ optional vswitch.enable vswitch.package 337 ++ optional cfg.qemu.swtpm.enable cfg.qemu.swtpm.package; 338 339 serviceConfig = { 340 Type = "notify"; 341 KillMode = "process"; # when stopping, leave the VMs alone 342 Restart = "no"; 343 }; 344 restartIfChanged = false; 345 }; 346 347 systemd.services.libvirt-guests = { 348 wantedBy = [ "multi-user.target" ]; 349 path = with pkgs; [ coreutils gawk cfg.package ]; 350 restartIfChanged = false; 351 352 environment.ON_BOOT = "${cfg.onBoot}"; 353 environment.ON_SHUTDOWN = "${cfg.onShutdown}"; 354 }; 355 356 systemd.sockets.virtlogd = { 357 description = "Virtual machine log manager socket"; 358 wantedBy = [ "sockets.target" ]; 359 listenStreams = [ "/run/${dirName}/virtlogd-sock" ]; 360 }; 361 362 systemd.services.virtlogd = { 363 description = "Virtual machine log manager"; 364 serviceConfig.ExecStart = "@${cfg.package}/sbin/virtlogd virtlogd"; 365 restartIfChanged = false; 366 }; 367 368 systemd.sockets.virtlockd = { 369 description = "Virtual machine lock manager socket"; 370 wantedBy = [ "sockets.target" ]; 371 listenStreams = [ "/run/${dirName}/virtlockd-sock" ]; 372 }; 373 374 systemd.services.virtlockd = { 375 description = "Virtual machine lock manager"; 376 serviceConfig.ExecStart = "@${cfg.package}/sbin/virtlockd virtlockd"; 377 restartIfChanged = false; 378 }; 379 380 # https://libvirt.org/daemons.html#monolithic-systemd-integration 381 systemd.sockets.libvirtd.wantedBy = [ "sockets.target" ]; 382 383 security.polkit.extraConfig = '' 384 polkit.addRule(function(action, subject) { 385 if (action.id == "org.libvirt.unix.manage" && 386 subject.isInGroup("libvirtd")) { 387 return polkit.Result.YES; 388 } 389 }); 390 ''; 391 }; 392}