nixpkgs mirror (for testing) github.com/NixOS/nixpkgs
nix
at fix-function-merge 1270 lines 48 kB view raw
1# This module creates a virtual machine from the NixOS configuration. 2# Building the `config.system.build.vm' attribute gives you a command 3# that starts a KVM/QEMU VM running the NixOS configuration defined in 4# `config'. By default, the Nix store is shared read-only with the 5# host, which makes (re)building VMs very efficient. 6 7{ config, lib, pkgs, options, ... }: 8 9with lib; 10 11let 12 13 qemu-common = import ../../lib/qemu-common.nix { inherit lib pkgs; }; 14 15 cfg = config.virtualisation; 16 17 opt = options.virtualisation; 18 19 qemu = cfg.qemu.package; 20 21 hostPkgs = cfg.host.pkgs; 22 23 consoles = lib.concatMapStringsSep " " (c: "console=${c}") cfg.qemu.consoles; 24 25 driveOpts = { ... }: { 26 27 options = { 28 29 file = mkOption { 30 type = types.str; 31 description = "The file image used for this drive."; 32 }; 33 34 driveExtraOpts = mkOption { 35 type = types.attrsOf types.str; 36 default = {}; 37 description = "Extra options passed to drive flag."; 38 }; 39 40 deviceExtraOpts = mkOption { 41 type = types.attrsOf types.str; 42 default = {}; 43 description = "Extra options passed to device flag."; 44 }; 45 46 name = mkOption { 47 type = types.nullOr types.str; 48 default = null; 49 description = "A name for the drive. Must be unique in the drives list. Not passed to qemu."; 50 }; 51 52 }; 53 54 }; 55 56 selectPartitionTableLayout = { useEFIBoot, useDefaultFilesystems }: 57 if useDefaultFilesystems then 58 if useEFIBoot then "efi" else "legacy" 59 else "none"; 60 61 driveCmdline = idx: { file, driveExtraOpts, deviceExtraOpts, ... }: 62 let 63 drvId = "drive${toString idx}"; 64 mkKeyValue = generators.mkKeyValueDefault {} "="; 65 mkOpts = opts: concatStringsSep "," (mapAttrsToList mkKeyValue opts); 66 driveOpts = mkOpts (driveExtraOpts // { 67 index = idx; 68 id = drvId; 69 "if" = "none"; 70 inherit file; 71 }); 72 deviceOpts = mkOpts (deviceExtraOpts // { 73 drive = drvId; 74 }); 75 device = 76 if cfg.qemu.diskInterface == "scsi" then 77 "-device lsi53c895a -device scsi-hd,${deviceOpts}" 78 else 79 "-device virtio-blk-pci,${deviceOpts}"; 80 in 81 "-drive ${driveOpts} ${device}"; 82 83 drivesCmdLine = drives: concatStringsSep "\\\n " (imap1 driveCmdline drives); 84 85 # Shell script to start the VM. 86 startVM = 87 '' 88 #! ${hostPkgs.runtimeShell} 89 90 export PATH=${makeBinPath [ hostPkgs.coreutils ]}''${PATH:+:}$PATH 91 92 set -e 93 94 # Create an empty ext4 filesystem image. A filesystem image does not 95 # contain a partition table but just a filesystem. 96 createEmptyFilesystemImage() { 97 local name=$1 98 local size=$2 99 local temp=$(mktemp) 100 ${qemu}/bin/qemu-img create -f raw "$temp" "$size" 101 ${hostPkgs.e2fsprogs}/bin/mkfs.ext4 -L ${rootFilesystemLabel} "$temp" 102 ${qemu}/bin/qemu-img convert -f raw -O qcow2 "$temp" "$name" 103 rm "$temp" 104 } 105 106 NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${toString config.virtualisation.diskImage}}") || test -z "$NIX_DISK_IMAGE" 107 108 if test -n "$NIX_DISK_IMAGE" && ! test -e "$NIX_DISK_IMAGE"; then 109 echo "Disk image do not exist, creating the virtualisation disk image..." 110 111 ${if (cfg.useBootLoader && cfg.useDefaultFilesystems) then '' 112 # Create a writable qcow2 image using the systemImage as a backing 113 # image. 114 115 # CoW prevent size to be attributed to an image. 116 # FIXME: raise this issue to upstream. 117 ${qemu}/bin/qemu-img create \ 118 -f qcow2 \ 119 -b ${systemImage}/nixos.qcow2 \ 120 -F qcow2 \ 121 "$NIX_DISK_IMAGE" 122 '' else if cfg.useDefaultFilesystems then '' 123 createEmptyFilesystemImage "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M" 124 '' else '' 125 # Create an empty disk image without a filesystem. 126 ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" "${toString cfg.diskSize}M" 127 '' 128 } 129 echo "Virtualisation disk image created." 130 fi 131 132 # Create a directory for storing temporary data of the running VM. 133 if [ -z "$TMPDIR" ] || [ -z "$USE_TMPDIR" ]; then 134 TMPDIR=$(mktemp -d nix-vm.XXXXXXXXXX --tmpdir) 135 fi 136 137 ${lib.optionalString (cfg.useNixStoreImage) '' 138 echo "Creating Nix store image..." 139 140 ${hostPkgs.gnutar}/bin/tar --create \ 141 --absolute-names \ 142 --verbatim-files-from \ 143 --transform 'flags=rSh;s|/nix/store/||' \ 144 --files-from ${hostPkgs.closureInfo { rootPaths = [ config.system.build.toplevel regInfo ]; }}/store-paths \ 145 | ${hostPkgs.erofs-utils}/bin/mkfs.erofs \ 146 --force-uid=0 \ 147 --force-gid=0 \ 148 -L ${nixStoreFilesystemLabel} \ 149 -U eb176051-bd15-49b7-9e6b-462e0b467019 \ 150 -T 0 \ 151 --tar=f \ 152 "$TMPDIR"/store.img 153 154 echo "Created Nix store image." 155 '' 156 } 157 158 # Create a directory for exchanging data with the VM. 159 mkdir -p "$TMPDIR/xchg" 160 161 ${lib.optionalString cfg.useHostCerts 162 '' 163 mkdir -p "$TMPDIR/certs" 164 if [ -e "$NIX_SSL_CERT_FILE" ]; then 165 cp -L "$NIX_SSL_CERT_FILE" "$TMPDIR"/certs/ca-certificates.crt 166 else 167 echo \$NIX_SSL_CERT_FILE should point to a valid file if virtualisation.useHostCerts is enabled. 168 fi 169 ''} 170 171 ${lib.optionalString cfg.useEFIBoot 172 '' 173 # Expose EFI variables, it's useful even when we are not using a bootloader (!). 174 # We might be interested in having EFI variable storage present even if we aren't booting via UEFI, hence 175 # no guard against `useBootLoader`. Examples: 176 # - testing PXE boot or other EFI applications 177 # - directbooting LinuxBoot, which `kexec()s` into a UEFI environment that can boot e.g. Windows 178 NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${config.system.name}-efi-vars.fd}") 179 # VM needs writable EFI vars 180 if ! test -e "$NIX_EFI_VARS"; then 181 ${if cfg.efi.keepVariables then 182 # We still need the EFI var from the make-disk-image derivation 183 # because our "switch-to-configuration" process might 184 # write into it and we want to keep this data. 185 ''cp ${systemImage}/efi-vars.fd "$NIX_EFI_VARS"'' 186 else 187 ''cp ${cfg.efi.variables} "$NIX_EFI_VARS"'' 188 } 189 chmod 0644 "$NIX_EFI_VARS" 190 fi 191 ''} 192 193 ${lib.optionalString cfg.tpm.enable '' 194 NIX_SWTPM_DIR=$(readlink -f "''${NIX_SWTPM_DIR:-${config.system.name}-swtpm}") 195 mkdir -p "$NIX_SWTPM_DIR" 196 ${lib.getExe cfg.tpm.package} \ 197 socket \ 198 --tpmstate dir="$NIX_SWTPM_DIR" \ 199 --ctrl type=unixio,path="$NIX_SWTPM_DIR"/socket,terminate \ 200 --pid file="$NIX_SWTPM_DIR"/pid --daemon \ 201 --tpm2 \ 202 --log file="$NIX_SWTPM_DIR"/stdout,level=6 203 204 # Enable `fdflags` builtin in Bash 205 # We will need it to perform surgical modification of the file descriptor 206 # passed in the coprocess to remove `FD_CLOEXEC`, i.e. close the file descriptor 207 # on exec. 208 # If let alone, it will trigger the coprocess to read EOF when QEMU is `exec` 209 # at the end of this script. To work around that, we will just clear 210 # the `FD_CLOEXEC` bits as a first step. 211 enable -f ${hostPkgs.bash}/lib/bash/fdflags fdflags 212 # leave a dangling subprocess because the swtpm ctrl socket has 213 # "terminate" when the last connection disconnects, it stops swtpm. 214 # When qemu stops, or if the main shell process ends, the coproc will 215 # get signaled by virtue of the pipe between main and coproc ending. 216 # Which in turns triggers a socat connect-disconnect to swtpm which 217 # will stop it. 218 coproc waitingswtpm { 219 read || : 220 echo "" | ${lib.getExe hostPkgs.socat} STDIO UNIX-CONNECT:"$NIX_SWTPM_DIR"/socket 221 } 222 # Clear `FD_CLOEXEC` on the coprocess' file descriptor stdin. 223 fdflags -s-cloexec ''${waitingswtpm[1]} 224 ''} 225 226 cd "$TMPDIR" 227 228 ${lib.optionalString (cfg.emptyDiskImages != []) "idx=0"} 229 ${flip concatMapStrings cfg.emptyDiskImages (size: '' 230 if ! test -e "empty$idx.qcow2"; then 231 ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M" 232 fi 233 idx=$((idx + 1)) 234 '')} 235 236 # Start QEMU. 237 exec ${qemu-common.qemuBinary qemu} \ 238 -name ${config.system.name} \ 239 -m ${toString config.virtualisation.memorySize} \ 240 -smp ${toString config.virtualisation.cores} \ 241 -device virtio-rng-pci \ 242 ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \ 243 ${concatStringsSep " \\\n " 244 (mapAttrsToList 245 (tag: share: "-virtfs local,path=${share.source},security_model=${share.securityModel},mount_tag=${tag}") 246 config.virtualisation.sharedDirectories)} \ 247 ${drivesCmdLine config.virtualisation.qemu.drives} \ 248 ${concatStringsSep " \\\n " config.virtualisation.qemu.options} \ 249 $QEMU_OPTS \ 250 "$@" 251 ''; 252 253 254 regInfo = hostPkgs.closureInfo { rootPaths = config.virtualisation.additionalPaths; }; 255 256 # Use well-defined and persistent filesystem labels to identify block devices. 257 rootFilesystemLabel = "nixos"; 258 espFilesystemLabel = "ESP"; # Hard-coded by make-disk-image.nix 259 nixStoreFilesystemLabel = "nix-store"; 260 261 # The root drive is a raw disk which does not necessarily contain a 262 # filesystem or partition table. It thus cannot be identified via the typical 263 # persistent naming schemes (e.g. /dev/disk/by-{label, uuid, partlabel, 264 # partuuid}. Instead, supply a well-defined and persistent serial attribute 265 # via QEMU. Inside the running system, the disk can then be identified via 266 # the /dev/disk/by-id scheme. 267 rootDriveSerialAttr = "root"; 268 269 # System image is akin to a complete NixOS install with 270 # a boot partition and root partition. 271 systemImage = import ../../lib/make-disk-image.nix { 272 inherit pkgs config lib; 273 additionalPaths = [ regInfo ]; 274 format = "qcow2"; 275 onlyNixStore = false; 276 label = rootFilesystemLabel; 277 partitionTableType = selectPartitionTableLayout { inherit (cfg) useDefaultFilesystems useEFIBoot; }; 278 # Bootloader should be installed on the system image only if we are booting through bootloaders. 279 # Though, if a user is not using our default filesystems, it is possible to not have any ESP 280 # or a strange partition table that's incompatible with GRUB configuration. 281 # As a consequence, this may lead to disk image creation failures. 282 # To avoid this, we prefer to let the user find out about how to install the bootloader on its ESP/disk. 283 # Usually, this can be through building your own disk image. 284 # TODO: If a user is interested into a more fine grained heuristic for `installBootLoader` 285 # by examining the actual contents of `cfg.fileSystems`, please send a PR. 286 installBootLoader = cfg.useBootLoader && cfg.useDefaultFilesystems; 287 touchEFIVars = cfg.useEFIBoot; 288 diskSize = "auto"; 289 additionalSpace = "0M"; 290 copyChannel = false; 291 OVMF = cfg.efi.OVMF; 292 }; 293 294in 295 296{ 297 imports = [ 298 ../profiles/qemu-guest.nix 299 (mkRenamedOptionModule [ "virtualisation" "pathsInNixDB" ] [ "virtualisation" "additionalPaths" ]) 300 (mkRemovedOptionModule [ "virtualisation" "bootDevice" ] "This option was renamed to `virtualisation.rootDevice`, as it was incorrectly named and misleading. Take the time to review what you want to do and look at the new options like `virtualisation.{bootLoaderDevice, bootPartition}`, open an issue in case of issues.") 301 (mkRemovedOptionModule [ "virtualisation" "efiVars" ] "This option was removed, it is possible to provide a template UEFI variable with `virtualisation.efi.variables` ; if this option is important to you, open an issue") 302 (mkRemovedOptionModule [ "virtualisation" "persistBootDevice" ] "Boot device is always persisted if you use a bootloader through the root disk image ; if this does not work for your usecase, please examine carefully what `virtualisation.{bootDevice, rootDevice, bootPartition}` options offer you and open an issue explaining your need.`") 303 ]; 304 305 options = { 306 307 virtualisation.fileSystems = options.fileSystems; 308 309 virtualisation.memorySize = 310 mkOption { 311 type = types.ints.positive; 312 default = 1024; 313 description = '' 314 The memory size in megabytes of the virtual machine. 315 ''; 316 }; 317 318 virtualisation.msize = 319 mkOption { 320 type = types.ints.positive; 321 default = 16384; 322 description = '' 323 The msize (maximum packet size) option passed to 9p file systems, in 324 bytes. Increasing this should increase performance significantly, 325 at the cost of higher RAM usage. 326 ''; 327 }; 328 329 virtualisation.diskSize = 330 mkOption { 331 type = types.nullOr types.ints.positive; 332 default = 1024; 333 description = '' 334 The disk size in megabytes of the virtual machine. 335 ''; 336 }; 337 338 virtualisation.diskImage = 339 mkOption { 340 type = types.nullOr types.str; 341 default = "./${config.system.name}.qcow2"; 342 defaultText = literalExpression ''"./''${config.system.name}.qcow2"''; 343 description = '' 344 Path to the disk image containing the root filesystem. 345 The image will be created on startup if it does not 346 exist. 347 348 If null, a tmpfs will be used as the root filesystem and 349 the VM's state will not be persistent. 350 ''; 351 }; 352 353 virtualisation.bootLoaderDevice = 354 mkOption { 355 type = types.path; 356 default = "/dev/disk/by-id/virtio-${rootDriveSerialAttr}"; 357 defaultText = literalExpression ''/dev/disk/by-id/virtio-${rootDriveSerialAttr}''; 358 example = "/dev/disk/by-id/virtio-boot-loader-device"; 359 description = '' 360 The path (inside th VM) to the device to boot from when legacy booting. 361 ''; 362 }; 363 364 virtualisation.bootPartition = 365 mkOption { 366 type = types.nullOr types.path; 367 default = if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null; 368 defaultText = literalExpression ''if cfg.useEFIBoot then "/dev/disk/by-label/${espFilesystemLabel}" else null''; 369 example = "/dev/disk/by-label/esp"; 370 description = '' 371 The path (inside the VM) to the device containing the EFI System Partition (ESP). 372 373 If you are *not* booting from a UEFI firmware, this value is, by 374 default, `null`. The ESP is mounted to `boot.loader.efi.efiSysMountpoint`. 375 ''; 376 }; 377 378 virtualisation.rootDevice = 379 mkOption { 380 type = types.nullOr types.path; 381 default = "/dev/disk/by-label/${rootFilesystemLabel}"; 382 defaultText = literalExpression ''/dev/disk/by-label/${rootFilesystemLabel}''; 383 example = "/dev/disk/by-label/nixos"; 384 description = '' 385 The path (inside the VM) to the device containing the root filesystem. 386 ''; 387 }; 388 389 virtualisation.emptyDiskImages = 390 mkOption { 391 type = types.listOf types.ints.positive; 392 default = []; 393 description = '' 394 Additional disk images to provide to the VM. The value is 395 a list of size in megabytes of each disk. These disks are 396 writeable by the VM. 397 ''; 398 }; 399 400 virtualisation.graphics = 401 mkOption { 402 type = types.bool; 403 default = true; 404 description = '' 405 Whether to run QEMU with a graphics window, or in nographic mode. 406 Serial console will be enabled on both settings, but this will 407 change the preferred console. 408 ''; 409 }; 410 411 virtualisation.resolution = 412 mkOption { 413 type = options.services.xserver.resolutions.type.nestedTypes.elemType; 414 default = { x = 1024; y = 768; }; 415 description = '' 416 The resolution of the virtual machine display. 417 ''; 418 }; 419 420 virtualisation.cores = 421 mkOption { 422 type = types.ints.positive; 423 default = 1; 424 description = '' 425 Specify the number of cores the guest is permitted to use. 426 The number can be higher than the available cores on the 427 host system. 428 ''; 429 }; 430 431 virtualisation.sharedDirectories = 432 mkOption { 433 type = types.attrsOf 434 (types.submodule { 435 options.source = mkOption { 436 type = types.str; 437 description = "The path of the directory to share, can be a shell variable"; 438 }; 439 options.target = mkOption { 440 type = types.path; 441 description = "The mount point of the directory inside the virtual machine"; 442 }; 443 options.securityModel = mkOption { 444 type = types.enum [ "passthrough" "mapped-xattr" "mapped-file" "none" ]; 445 default = "mapped-xattr"; 446 description = '' 447 The security model to use for this share: 448 449 - `passthrough`: files are stored using the same credentials as they are created on the guest (this requires QEMU to run as root) 450 - `mapped-xattr`: some of the file attributes like uid, gid, mode bits and link target are stored as file attributes 451 - `mapped-file`: the attributes are stored in the hidden .virtfs_metadata directory. Directories exported by this security model cannot interact with other unix tools 452 - `none`: same as "passthrough" except the sever won't report failures if it fails to set file attributes like ownership 453 ''; 454 }; 455 }); 456 default = { }; 457 example = { 458 my-share = { source = "/path/to/be/shared"; target = "/mnt/shared"; }; 459 }; 460 description = '' 461 An attributes set of directories that will be shared with the 462 virtual machine using VirtFS (9P filesystem over VirtIO). 463 The attribute name will be used as the 9P mount tag. 464 ''; 465 }; 466 467 virtualisation.additionalPaths = 468 mkOption { 469 type = types.listOf types.path; 470 default = []; 471 description = '' 472 A list of paths whose closure should be made available to 473 the VM. 474 475 When 9p is used, the closure is registered in the Nix 476 database in the VM. All other paths in the host Nix store 477 appear in the guest Nix store as well, but are considered 478 garbage (because they are not registered in the Nix 479 database of the guest). 480 481 When {option}`virtualisation.useNixStoreImage` is 482 set, the closure is copied to the Nix store image. 483 ''; 484 }; 485 486 virtualisation.forwardPorts = mkOption { 487 type = types.listOf 488 (types.submodule { 489 options.from = mkOption { 490 type = types.enum [ "host" "guest" ]; 491 default = "host"; 492 description = '' 493 Controls the direction in which the ports are mapped: 494 495 - `"host"` means traffic from the host ports 496 is forwarded to the given guest port. 497 - `"guest"` means traffic from the guest ports 498 is forwarded to the given host port. 499 ''; 500 }; 501 options.proto = mkOption { 502 type = types.enum [ "tcp" "udp" ]; 503 default = "tcp"; 504 description = "The protocol to forward."; 505 }; 506 options.host.address = mkOption { 507 type = types.str; 508 default = ""; 509 description = "The IPv4 address of the host."; 510 }; 511 options.host.port = mkOption { 512 type = types.port; 513 description = "The host port to be mapped."; 514 }; 515 options.guest.address = mkOption { 516 type = types.str; 517 default = ""; 518 description = "The IPv4 address on the guest VLAN."; 519 }; 520 options.guest.port = mkOption { 521 type = types.port; 522 description = "The guest port to be mapped."; 523 }; 524 }); 525 default = []; 526 example = lib.literalExpression 527 '' 528 [ # forward local port 2222 -> 22, to ssh into the VM 529 { from = "host"; host.port = 2222; guest.port = 22; } 530 531 # forward local port 80 -> 10.0.2.10:80 in the VLAN 532 { from = "guest"; 533 guest.address = "10.0.2.10"; guest.port = 80; 534 host.address = "127.0.0.1"; host.port = 80; 535 } 536 ] 537 ''; 538 description = '' 539 When using the SLiRP user networking (default), this option allows to 540 forward ports to/from the host/guest. 541 542 ::: {.warning} 543 If the NixOS firewall on the virtual machine is enabled, you also 544 have to open the guest ports to enable the traffic between host and 545 guest. 546 ::: 547 548 ::: {.note} 549 Currently QEMU supports only IPv4 forwarding. 550 ::: 551 ''; 552 }; 553 554 virtualisation.restrictNetwork = 555 mkOption { 556 type = types.bool; 557 default = false; 558 example = true; 559 description = '' 560 If this option is enabled, the guest will be isolated, i.e. it will 561 not be able to contact the host and no guest IP packets will be 562 routed over the host to the outside. This option does not affect 563 any explicitly set forwarding rules. 564 ''; 565 }; 566 567 virtualisation.vlans = 568 mkOption { 569 type = types.listOf types.ints.unsigned; 570 default = if config.virtualisation.interfaces == {} then [ 1 ] else [ ]; 571 defaultText = lib.literalExpression ''if config.virtualisation.interfaces == {} then [ 1 ] else [ ]''; 572 example = [ 1 2 ]; 573 description = '' 574 Virtual networks to which the VM is connected. Each 575 number «N» in this list causes 576 the VM to have a virtual Ethernet interface attached to a 577 separate virtual network on which it will be assigned IP 578 address 579 `192.168.«N».«M»`, 580 where «M» is the index of this VM 581 in the list of VMs. 582 ''; 583 }; 584 585 virtualisation.interfaces = mkOption { 586 default = {}; 587 example = { 588 enp1s0.vlan = 1; 589 }; 590 description = '' 591 Network interfaces to add to the VM. 592 ''; 593 type = with types; attrsOf (submodule { 594 options = { 595 vlan = mkOption { 596 type = types.ints.unsigned; 597 description = '' 598 VLAN to which the network interface is connected. 599 ''; 600 }; 601 602 assignIP = mkOption { 603 type = types.bool; 604 default = false; 605 description = '' 606 Automatically assign an IP address to the network interface using the same scheme as 607 virtualisation.vlans. 608 ''; 609 }; 610 }; 611 }); 612 }; 613 614 virtualisation.writableStore = 615 mkOption { 616 type = types.bool; 617 default = cfg.mountHostNixStore; 618 defaultText = literalExpression "cfg.mountHostNixStore"; 619 description = '' 620 If enabled, the Nix store in the VM is made writable by 621 layering an overlay filesystem on top of the host's Nix 622 store. 623 624 By default, this is enabled if you mount a host Nix store. 625 ''; 626 }; 627 628 virtualisation.writableStoreUseTmpfs = 629 mkOption { 630 type = types.bool; 631 default = true; 632 description = '' 633 Use a tmpfs for the writable store instead of writing to the VM's 634 own filesystem. 635 ''; 636 }; 637 638 networking.primaryIPAddress = 639 mkOption { 640 type = types.str; 641 default = ""; 642 internal = true; 643 description = "Primary IP address used in /etc/hosts."; 644 }; 645 646 networking.primaryIPv6Address = 647 mkOption { 648 type = types.str; 649 default = ""; 650 internal = true; 651 description = "Primary IPv6 address used in /etc/hosts."; 652 }; 653 654 virtualisation.host.pkgs = mkOption { 655 type = options.nixpkgs.pkgs.type; 656 default = pkgs; 657 defaultText = literalExpression "pkgs"; 658 example = literalExpression '' 659 import pkgs.path { system = "x86_64-darwin"; } 660 ''; 661 description = '' 662 Package set to use for the host-specific packages of the VM runner. 663 Changing this to e.g. a Darwin package set allows running NixOS VMs on Darwin. 664 ''; 665 }; 666 667 virtualisation.qemu = { 668 package = 669 mkOption { 670 type = types.package; 671 default = if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then hostPkgs.qemu_kvm else hostPkgs.qemu; 672 defaultText = literalExpression "if hostPkgs.stdenv.hostPlatform.qemuArch == pkgs.stdenv.hostPlatform.qemuArch then config.virtualisation.host.pkgs.qemu_kvm else config.virtualisation.host.pkgs.qemu"; 673 example = literalExpression "pkgs.qemu_test"; 674 description = "QEMU package to use."; 675 }; 676 677 options = 678 mkOption { 679 type = types.listOf types.str; 680 default = []; 681 example = [ "-vga std" ]; 682 description = '' 683 Options passed to QEMU. 684 See [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for a complete list. 685 ''; 686 }; 687 688 consoles = mkOption { 689 type = types.listOf types.str; 690 default = let 691 consoles = [ "${qemu-common.qemuSerialDevice},115200n8" "tty0" ]; 692 in if cfg.graphics then consoles else reverseList consoles; 693 example = [ "console=tty1" ]; 694 description = '' 695 The output console devices to pass to the kernel command line via the 696 `console` parameter, the primary console is the last 697 item of this list. 698 699 By default it enables both serial console and 700 `tty0`. The preferred console (last one) is based on 701 the value of {option}`virtualisation.graphics`. 702 ''; 703 }; 704 705 networkingOptions = 706 mkOption { 707 type = types.listOf types.str; 708 default = [ ]; 709 example = [ 710 "-net nic,netdev=user.0,model=virtio" 711 "-netdev user,id=user.0,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}" 712 ]; 713 description = '' 714 Networking-related command-line options that should be passed to qemu. 715 The default is to use userspace networking (SLiRP). 716 See the [QEMU Wiki on Networking](https://wiki.qemu.org/Documentation/Networking) for details. 717 718 If you override this option, be advised to keep 719 `''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}` (as seen in the example) 720 to keep the default runtime behaviour. 721 ''; 722 }; 723 724 drives = 725 mkOption { 726 type = types.listOf (types.submodule driveOpts); 727 description = "Drives passed to qemu."; 728 }; 729 730 diskInterface = 731 mkOption { 732 type = types.enum [ "virtio" "scsi" "ide" ]; 733 default = "virtio"; 734 example = "scsi"; 735 description = "The interface used for the virtual hard disks."; 736 }; 737 738 guestAgent.enable = 739 mkOption { 740 type = types.bool; 741 default = true; 742 description = '' 743 Enable the Qemu guest agent. 744 ''; 745 }; 746 747 virtioKeyboard = 748 mkOption { 749 type = types.bool; 750 default = true; 751 description = '' 752 Enable the virtio-keyboard device. 753 ''; 754 }; 755 }; 756 757 virtualisation.useNixStoreImage = 758 mkOption { 759 type = types.bool; 760 default = false; 761 description = '' 762 Build and use a disk image for the Nix store, instead of 763 accessing the host's one through 9p. 764 765 For applications which do a lot of reads from the store, 766 this can drastically improve performance, but at the cost of 767 disk space and image build time. 768 769 The Nix store image is built just-in-time right before the VM is 770 started. Because it does not produce another derivation, the image is 771 not cached between invocations and never lands in the store or binary 772 cache. 773 774 If you want a full disk image with a partition table and a root 775 filesystem instead of only a store image, enable 776 {option}`virtualisation.useBootLoader` instead. 777 ''; 778 }; 779 780 virtualisation.mountHostNixStore = 781 mkOption { 782 type = types.bool; 783 default = !cfg.useNixStoreImage && !cfg.useBootLoader; 784 defaultText = literalExpression "!cfg.useNixStoreImage && !cfg.useBootLoader"; 785 description = '' 786 Mount the host Nix store as a 9p mount. 787 ''; 788 }; 789 790 virtualisation.directBoot = { 791 enable = 792 mkOption { 793 type = types.bool; 794 default = !cfg.useBootLoader; 795 defaultText = "!cfg.useBootLoader"; 796 description = '' 797 If enabled, the virtual machine will boot directly into the kernel instead of through a bootloader. 798 Read more about this feature in the [QEMU documentation on Direct Linux Boot](https://qemu-project.gitlab.io/qemu/system/linuxboot.html) 799 800 This is enabled by default. 801 If you want to test netboot, consider disabling this option. 802 Enable a bootloader with {option}`virtualisation.useBootLoader` if you need. 803 804 Relevant parameters such as those set in `boot.initrd` and `boot.kernelParams` are also passed to QEMU. 805 Additional parameters can be supplied on invocation through the environment variable `$QEMU_KERNEL_PARAMS`. 806 They are added to the `-append` option, see [QEMU User Documentation](https://www.qemu.org/docs/master/system/qemu-manpage) for details 807 For example, to let QEMU use the parent terminal as the serial console, set `QEMU_KERNEL_PARAMS="console=ttyS0"`. 808 809 This will not (re-)boot correctly into a system that has switched to a different configuration on disk. 810 ''; 811 }; 812 initrd = 813 mkOption { 814 type = types.str; 815 default = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}"; 816 defaultText = "\${config.system.build.initialRamdisk}/\${config.system.boot.loader.initrdFile}"; 817 description = '' 818 In direct boot situations, you may want to influence the initrd to load 819 to use your own customized payload. 820 821 This is useful if you want to test the netboot image without 822 testing the firmware or the loading part. 823 ''; 824 }; 825 }; 826 827 virtualisation.useBootLoader = 828 mkOption { 829 type = types.bool; 830 default = false; 831 description = '' 832 Use a boot loader to boot the system. 833 This allows, among other things, testing the boot loader. 834 835 If disabled, the kernel and initrd are directly booted, 836 forgoing any bootloader. 837 838 Check the documentation on {option}`virtualisation.directBoot.enable` for details. 839 ''; 840 }; 841 842 virtualisation.useEFIBoot = 843 mkOption { 844 type = types.bool; 845 default = false; 846 description = '' 847 If enabled, the virtual machine will provide a EFI boot 848 manager. 849 useEFIBoot is ignored if useBootLoader == false. 850 ''; 851 }; 852 853 virtualisation.efi = { 854 OVMF = mkOption { 855 type = types.package; 856 default = (pkgs.OVMF.override { 857 secureBoot = cfg.useSecureBoot; 858 }).fd; 859 defaultText = ''(pkgs.OVMF.override { 860 secureBoot = cfg.useSecureBoot; 861 }).fd''; 862 description = "OVMF firmware package, defaults to OVMF configured with secure boot if needed."; 863 }; 864 865 firmware = mkOption { 866 type = types.path; 867 default = cfg.efi.OVMF.firmware; 868 defaultText = literalExpression "cfg.efi.OVMF.firmware"; 869 description = '' 870 Firmware binary for EFI implementation, defaults to OVMF. 871 ''; 872 }; 873 874 variables = mkOption { 875 type = types.path; 876 default = cfg.efi.OVMF.variables; 877 defaultText = literalExpression "cfg.efi.OVMF.variables"; 878 description = '' 879 Platform-specific flash binary for EFI variables, implementation-dependent to the EFI firmware. 880 Defaults to OVMF. 881 ''; 882 }; 883 884 keepVariables = mkOption { 885 type = types.bool; 886 default = cfg.useBootLoader; 887 defaultText = literalExpression "cfg.useBootLoader"; 888 description = "Whether to keep EFI variable values from the generated system image"; 889 }; 890 }; 891 892 virtualisation.tpm = { 893 enable = mkEnableOption "a TPM device in the virtual machine with a driver, using swtpm"; 894 895 package = mkPackageOption cfg.host.pkgs "swtpm" { }; 896 897 deviceModel = mkOption { 898 type = types.str; 899 default = ({ 900 "i686-linux" = "tpm-tis"; 901 "x86_64-linux" = "tpm-tis"; 902 "ppc64-linux" = "tpm-spapr"; 903 "armv7-linux" = "tpm-tis-device"; 904 "aarch64-linux" = "tpm-tis-device"; 905 }.${pkgs.stdenv.hostPlatform.system} or (throw "Unsupported system for TPM2 emulation in QEMU")); 906 defaultText = '' 907 Based on the guest platform Linux system: 908 909 - `tpm-tis` for (i686, x86_64) 910 - `tpm-spapr` for ppc64 911 - `tpm-tis-device` for (armv7, aarch64) 912 ''; 913 example = "tpm-tis-device"; 914 description = "QEMU device model for the TPM, uses the appropriate default based on th guest platform system and the package passed."; 915 }; 916 }; 917 918 virtualisation.useDefaultFilesystems = 919 mkOption { 920 type = types.bool; 921 default = true; 922 description = '' 923 If enabled, the boot disk of the virtual machine will be 924 formatted and mounted with the default filesystems for 925 testing. Swap devices and LUKS will be disabled. 926 927 If disabled, a root filesystem has to be specified and 928 formatted (for example in the initial ramdisk). 929 ''; 930 }; 931 932 virtualisation.useSecureBoot = 933 mkOption { 934 type = types.bool; 935 default = false; 936 description = '' 937 Enable Secure Boot support in the EFI firmware. 938 ''; 939 }; 940 941 virtualisation.bios = 942 mkOption { 943 type = types.nullOr types.package; 944 default = null; 945 description = '' 946 An alternate BIOS (such as `qboot`) with which to start the VM. 947 Should contain a file named `bios.bin`. 948 If `null`, QEMU's builtin SeaBIOS will be used. 949 ''; 950 }; 951 952 virtualisation.useHostCerts = 953 mkOption { 954 type = types.bool; 955 default = false; 956 description = '' 957 If enabled, when `NIX_SSL_CERT_FILE` is set on the host, 958 pass the CA certificates from the host to the VM. 959 ''; 960 }; 961 962 }; 963 964 config = { 965 966 assertions = 967 lib.concatLists (lib.flip lib.imap cfg.forwardPorts (i: rule: 968 [ 969 { assertion = rule.from == "guest" -> rule.proto == "tcp"; 970 message = 971 '' 972 Invalid virtualisation.forwardPorts.<entry ${toString i}>.proto: 973 Guest forwarding supports only TCP connections. 974 ''; 975 } 976 { assertion = rule.from == "guest" -> lib.hasPrefix "10.0.2." rule.guest.address; 977 message = 978 '' 979 Invalid virtualisation.forwardPorts.<entry ${toString i}>.guest.address: 980 The address must be in the default VLAN (10.0.2.0/24). 981 ''; 982 } 983 ])) ++ [ 984 { assertion = pkgs.stdenv.hostPlatform.is32bit -> cfg.memorySize < 2047; 985 message = '' 986 virtualisation.memorySize is above 2047, but qemu is only able to allocate 2047MB RAM on 32bit max. 987 ''; 988 } 989 { assertion = cfg.directBoot.enable || cfg.directBoot.initrd == options.virtualisation.directBoot.initrd.default; 990 message = 991 '' 992 You changed the default of `virtualisation.directBoot.initrd` but you are not 993 using QEMU direct boot. This initrd will not be used in your current 994 boot configuration. 995 996 Either do not mutate `virtualisation.directBoot.initrd` or enable direct boot. 997 998 If you have a more advanced usecase, please open an issue or a pull request. 999 ''; 1000 } 1001 ]; 1002 1003 warnings = 1004 optional (cfg.directBoot.enable && cfg.useBootLoader) 1005 '' 1006 You enabled direct boot and a bootloader, QEMU will not boot your bootloader, rendering 1007 `useBootLoader` useless. You might want to disable one of those options. 1008 ''; 1009 1010 # In UEFI boot, we use a EFI-only partition table layout, thus GRUB will fail when trying to install 1011 # legacy and UEFI. In order to avoid this, we have to put "nodev" to force UEFI-only installs. 1012 # Otherwise, we set the proper bootloader device for this. 1013 # FIXME: make a sense of this mess wrt to multiple ESP present in the system, probably use boot.efiSysMountpoint? 1014 boot.loader.grub.device = mkVMOverride (if cfg.useEFIBoot then "nodev" else cfg.bootLoaderDevice); 1015 boot.loader.grub.gfxmodeBios = with cfg.resolution; "${toString x}x${toString y}"; 1016 1017 boot.loader.supportsInitrdSecrets = mkIf (!cfg.useBootLoader) (mkVMOverride false); 1018 1019 # After booting, register the closure of the paths in 1020 # `virtualisation.additionalPaths' in the Nix database in the VM. This 1021 # allows Nix operations to work in the VM. The path to the 1022 # registration file is passed through the kernel command line to 1023 # allow `system.build.toplevel' to be included. (If we had a direct 1024 # reference to ${regInfo} here, then we would get a cyclic 1025 # dependency.) 1026 boot.postBootCommands = lib.mkIf config.nix.enable 1027 '' 1028 if [[ "$(cat /proc/cmdline)" =~ regInfo=([^ ]*) ]]; then 1029 ${config.nix.package.out}/bin/nix-store --load-db < ''${BASH_REMATCH[1]} 1030 fi 1031 ''; 1032 1033 boot.initrd.availableKernelModules = 1034 optional (cfg.qemu.diskInterface == "scsi") "sym53c8xx" 1035 ++ optional (cfg.tpm.enable) "tpm_tis"; 1036 1037 virtualisation.additionalPaths = [ config.system.build.toplevel ]; 1038 1039 virtualisation.sharedDirectories = { 1040 nix-store = mkIf cfg.mountHostNixStore { 1041 source = builtins.storeDir; 1042 # Always mount this to /nix/.ro-store because we never want to actually 1043 # write to the host Nix Store. 1044 target = "/nix/.ro-store"; 1045 securityModel = "none"; 1046 }; 1047 xchg = { 1048 source = ''"$TMPDIR"/xchg''; 1049 securityModel = "none"; 1050 target = "/tmp/xchg"; 1051 }; 1052 shared = { 1053 source = ''"''${SHARED_DIR:-$TMPDIR/xchg}"''; 1054 target = "/tmp/shared"; 1055 securityModel = "none"; 1056 }; 1057 certs = mkIf cfg.useHostCerts { 1058 source = ''"$TMPDIR"/certs''; 1059 target = "/etc/ssl/certs"; 1060 securityModel = "none"; 1061 }; 1062 }; 1063 1064 security.pki.installCACerts = mkIf cfg.useHostCerts false; 1065 1066 virtualisation.qemu.networkingOptions = 1067 let 1068 forwardingOptions = flip concatMapStrings cfg.forwardPorts 1069 ({ proto, from, host, guest }: 1070 if from == "host" 1071 then "hostfwd=${proto}:${host.address}:${toString host.port}-" + 1072 "${guest.address}:${toString guest.port}," 1073 else "'guestfwd=${proto}:${guest.address}:${toString guest.port}-" + 1074 "cmd:${pkgs.netcat}/bin/nc ${host.address} ${toString host.port}'," 1075 ); 1076 restrictNetworkOption = lib.optionalString cfg.restrictNetwork "restrict=on,"; 1077 in 1078 [ 1079 "-net nic,netdev=user.0,model=virtio" 1080 "-netdev user,id=user.0,${forwardingOptions}${restrictNetworkOption}\"$QEMU_NET_OPTS\"" 1081 ]; 1082 1083 virtualisation.qemu.options = mkMerge [ 1084 (mkIf cfg.qemu.virtioKeyboard [ 1085 "-device virtio-keyboard" 1086 ]) 1087 (mkIf pkgs.stdenv.hostPlatform.isx86 [ 1088 "-usb" "-device usb-tablet,bus=usb-bus.0" 1089 ]) 1090 (mkIf pkgs.stdenv.hostPlatform.isAarch [ 1091 "-device virtio-gpu-pci" "-device usb-ehci,id=usb0" "-device usb-kbd" "-device usb-tablet" 1092 ]) 1093 (let 1094 alphaNumericChars = lowerChars ++ upperChars ++ (map toString (range 0 9)); 1095 # Replace all non-alphanumeric characters with underscores 1096 sanitizeShellIdent = s: concatMapStrings (c: if builtins.elem c alphaNumericChars then c else "_") (stringToCharacters s); 1097 in mkIf cfg.directBoot.enable [ 1098 "-kernel \${NIXPKGS_QEMU_KERNEL_${sanitizeShellIdent config.system.name}:-${config.system.build.toplevel}/kernel}" 1099 "-initrd ${cfg.directBoot.initrd}" 1100 ''-append "$(cat ${config.system.build.toplevel}/kernel-params) init=${config.system.build.toplevel}/init regInfo=${regInfo}/registration ${consoles} $QEMU_KERNEL_PARAMS"'' 1101 ]) 1102 (mkIf cfg.useEFIBoot [ 1103 "-drive if=pflash,format=raw,unit=0,readonly=on,file=${cfg.efi.firmware}" 1104 "-drive if=pflash,format=raw,unit=1,readonly=off,file=$NIX_EFI_VARS" 1105 ]) 1106 (mkIf (cfg.bios != null) [ 1107 "-bios ${cfg.bios}/bios.bin" 1108 ]) 1109 (mkIf (!cfg.graphics) [ 1110 "-nographic" 1111 ]) 1112 (mkIf (cfg.tpm.enable) [ 1113 "-chardev socket,id=chrtpm,path=\"$NIX_SWTPM_DIR\"/socket" 1114 "-tpmdev emulator,id=tpm_dev_0,chardev=chrtpm" 1115 "-device ${cfg.tpm.deviceModel},tpmdev=tpm_dev_0" 1116 ]) 1117 (mkIf (pkgs.stdenv.hostPlatform.isx86 && cfg.efi.OVMF.systemManagementModeRequired) [ 1118 "-machine" "q35,smm=on" 1119 "-global" "driver=cfi.pflash01,property=secure,value=on" 1120 ]) 1121 ]; 1122 1123 virtualisation.qemu.drives = mkMerge [ 1124 (mkIf (cfg.diskImage != null) [{ 1125 name = "root"; 1126 file = ''"$NIX_DISK_IMAGE"''; 1127 driveExtraOpts.cache = "writeback"; 1128 driveExtraOpts.werror = "report"; 1129 deviceExtraOpts.bootindex = "1"; 1130 deviceExtraOpts.serial = rootDriveSerialAttr; 1131 }]) 1132 (mkIf cfg.useNixStoreImage [{ 1133 name = "nix-store"; 1134 file = ''"$TMPDIR"/store.img''; 1135 deviceExtraOpts.bootindex = "2"; 1136 driveExtraOpts.format = "raw"; 1137 }]) 1138 (imap0 (idx: _: { 1139 file = "$(pwd)/empty${toString idx}.qcow2"; 1140 driveExtraOpts.werror = "report"; 1141 }) cfg.emptyDiskImages) 1142 ]; 1143 1144 # By default, use mkVMOverride to enable building test VMs (e.g. via 1145 # `nixos-rebuild build-vm`) of a system configuration, where the regular 1146 # value for the `fileSystems' attribute should be disregarded (since those 1147 # filesystems don't necessarily exist in the VM). You can disable this 1148 # override by setting `virtualisation.fileSystems = lib.mkForce { };`. 1149 fileSystems = lib.mkIf (cfg.fileSystems != { }) (mkVMOverride cfg.fileSystems); 1150 1151 virtualisation.fileSystems = let 1152 mkSharedDir = tag: share: 1153 { 1154 name = share.target; 1155 value.device = tag; 1156 value.fsType = "9p"; 1157 value.neededForBoot = true; 1158 value.options = 1159 [ "trans=virtio" "version=9p2000.L" "msize=${toString cfg.msize}" ] 1160 ++ lib.optional (tag == "nix-store") "cache=loose"; 1161 }; 1162 in lib.mkMerge [ 1163 (lib.mapAttrs' mkSharedDir cfg.sharedDirectories) 1164 { 1165 "/" = lib.mkIf cfg.useDefaultFilesystems (if cfg.diskImage == null then { 1166 device = "tmpfs"; 1167 fsType = "tmpfs"; 1168 } else { 1169 device = cfg.rootDevice; 1170 fsType = "ext4"; 1171 }); 1172 "/tmp" = lib.mkIf config.boot.tmp.useTmpfs { 1173 device = "tmpfs"; 1174 fsType = "tmpfs"; 1175 neededForBoot = true; 1176 # Sync with systemd's tmp.mount; 1177 options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmp.tmpfsSize}" ]; 1178 }; 1179 "/nix/store" = lib.mkIf (cfg.useNixStoreImage || cfg.mountHostNixStore) (if cfg.writableStore then { 1180 overlay = { 1181 lowerdir = [ "/nix/.ro-store" ]; 1182 upperdir = "/nix/.rw-store/upper"; 1183 workdir = "/nix/.rw-store/work"; 1184 }; 1185 } else { 1186 device = "/nix/.ro-store"; 1187 options = [ "bind" ]; 1188 }); 1189 "/nix/.ro-store" = lib.mkIf cfg.useNixStoreImage { 1190 device = "/dev/disk/by-label/${nixStoreFilesystemLabel}"; 1191 fsType = "erofs"; 1192 neededForBoot = true; 1193 options = [ "ro" ]; 1194 }; 1195 "/nix/.rw-store" = lib.mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) { 1196 fsType = "tmpfs"; 1197 options = [ "mode=0755" ]; 1198 neededForBoot = true; 1199 }; 1200 "${config.boot.loader.efi.efiSysMountPoint}" = lib.mkIf (cfg.useBootLoader && cfg.bootPartition != null) { 1201 device = cfg.bootPartition; 1202 fsType = "vfat"; 1203 }; 1204 } 1205 ]; 1206 1207 swapDevices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) [ ]; 1208 boot.initrd.luks.devices = (if cfg.useDefaultFilesystems then mkVMOverride else mkDefault) {}; 1209 1210 # Don't run ntpd in the guest. It should get the correct time from KVM. 1211 services.timesyncd.enable = false; 1212 1213 services.qemuGuest.enable = cfg.qemu.guestAgent.enable; 1214 1215 system.build.vm = hostPkgs.runCommand "nixos-vm" { 1216 preferLocalBuild = true; 1217 meta.mainProgram = "run-${config.system.name}-vm"; 1218 } 1219 '' 1220 mkdir -p $out/bin 1221 ln -s ${config.system.build.toplevel} $out/system 1222 ln -s ${hostPkgs.writeScript "run-nixos-vm" startVM} $out/bin/run-${config.system.name}-vm 1223 ''; 1224 1225 # When building a regular system configuration, override whatever 1226 # video driver the host uses. 1227 services.xserver.videoDrivers = mkVMOverride [ "modesetting" ]; 1228 services.xserver.defaultDepth = mkVMOverride 0; 1229 services.xserver.resolutions = mkVMOverride [ cfg.resolution ]; 1230 services.xserver.monitorSection = 1231 '' 1232 # Set a higher refresh rate so that resolutions > 800x600 work. 1233 HorizSync 30-140 1234 VertRefresh 50-160 1235 ''; 1236 1237 # Wireless won't work in the VM. 1238 networking.wireless.enable = mkVMOverride false; 1239 services.connman.enable = mkVMOverride false; 1240 1241 # Speed up booting by not waiting for ARP. 1242 networking.dhcpcd.extraConfig = "noarp"; 1243 1244 networking.usePredictableInterfaceNames = false; 1245 1246 system.requiredKernelConfig = with config.lib.kernelConfig; 1247 [ (isEnabled "VIRTIO_BLK") 1248 (isEnabled "VIRTIO_PCI") 1249 (isEnabled "VIRTIO_NET") 1250 (isEnabled "EXT4_FS") 1251 (isEnabled "NET_9P_VIRTIO") 1252 (isEnabled "9P_FS") 1253 (isYes "BLK_DEV") 1254 (isYes "PCI") 1255 (isYes "NETDEVICES") 1256 (isYes "NET_CORE") 1257 (isYes "INET") 1258 (isYes "NETWORK_FILESYSTEMS") 1259 ] ++ optionals (!cfg.graphics) [ 1260 (isYes "SERIAL_8250_CONSOLE") 1261 (isYes "SERIAL_8250") 1262 ] ++ optionals (cfg.writableStore) [ 1263 (isEnabled "OVERLAY_FS") 1264 ]; 1265 1266 }; 1267 1268 # uses types of services/x11/xserver.nix 1269 meta.buildDocsInSandbox = false; 1270}