diff options
author | Timothy DeHerrera <tim.deh@pm.me> | 2021-09-28 21:35:23 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-28 21:35:23 -0600 |
commit | 895f3956d266e2e90b66d03ea97ebc082bffd12c (patch) | |
tree | b25ecd3db061bada8aba317bc8ef7ac370da7587 /nixos/modules | |
parent | 8a587c79f6c970059b98a9e30e10bb33674b6340 (diff) | |
parent | bd3cb03c6f7993050fe21b787e6f6a8595204be2 (diff) | |
download | nixpkgs-895f3956d266e2e90b66d03ea97ebc082bffd12c.tar nixpkgs-895f3956d266e2e90b66d03ea97ebc082bffd12c.tar.gz nixpkgs-895f3956d266e2e90b66d03ea97ebc082bffd12c.tar.bz2 nixpkgs-895f3956d266e2e90b66d03ea97ebc082bffd12c.tar.lz nixpkgs-895f3956d266e2e90b66d03ea97ebc082bffd12c.tar.xz nixpkgs-895f3956d266e2e90b66d03ea97ebc082bffd12c.tar.zst nixpkgs-895f3956d266e2e90b66d03ea97ebc082bffd12c.zip |
Merge pull request #127933 from rnhmjoj/qemu-restoration
Qemu restoration
Diffstat (limited to 'nixos/modules')
-rw-r--r-- | nixos/modules/misc/documentation.nix | 6 | ||||
-rw-r--r-- | nixos/modules/testing/test-instrumentation.nix | 17 | ||||
-rw-r--r-- | nixos/modules/virtualisation/qemu-vm.nix | 290 |
3 files changed, 241 insertions, 72 deletions
diff --git a/nixos/modules/misc/documentation.nix b/nixos/modules/misc/documentation.nix index 1d23b9b7244..ec6b2ad3b88 100644 --- a/nixos/modules/misc/documentation.nix +++ b/nixos/modules/misc/documentation.nix @@ -6,7 +6,11 @@ let cfg = config.documentation; - manualModules = baseModules ++ optionals cfg.nixos.includeAllModules (extraModules ++ modules); + manualModules = + baseModules + # Modules for which to show options even when not imported + ++ [ ../virtualisation/qemu-vm.nix ] + ++ optionals cfg.nixos.includeAllModules (extraModules ++ modules); /* For the purpose of generating docs, evaluate options with each derivation in `pkgs` (recursively) replaced by a fake with path "\${pkgs.attribute.path}". diff --git a/nixos/modules/testing/test-instrumentation.nix b/nixos/modules/testing/test-instrumentation.nix index be5fa88b8ad..a7011be7e04 100644 --- a/nixos/modules/testing/test-instrumentation.nix +++ b/nixos/modules/testing/test-instrumentation.nix @@ -4,7 +4,10 @@ { options, config, lib, pkgs, ... }: with lib; -with import ../../lib/qemu-flags.nix { inherit pkgs; }; + +let + qemu-common = import ../../lib/qemu-common.nix { inherit lib pkgs; }; +in { @@ -12,8 +15,8 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; }; systemd.services.backdoor = { wantedBy = [ "multi-user.target" ]; - requires = [ "dev-hvc0.device" "dev-${qemuSerialDevice}.device" ]; - after = [ "dev-hvc0.device" "dev-${qemuSerialDevice}.device" ]; + requires = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ]; + after = [ "dev-hvc0.device" "dev-${qemu-common.qemuSerialDevice}.device" ]; script = '' export USER=root @@ -30,7 +33,7 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; }; cd /tmp exec < /dev/hvc0 > /dev/hvc0 - while ! exec 2> /dev/${qemuSerialDevice}; do sleep 0.1; done + while ! exec 2> /dev/${qemu-common.qemuSerialDevice}; do sleep 0.1; done echo "connecting to host..." >&2 stty -F /dev/hvc0 raw -echo # prevent nl -> cr/nl conversion echo @@ -42,7 +45,7 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; }; # Prevent agetty from being instantiated on the serial device, since it # interferes with the backdoor (writes to it will randomly fail # with EIO). Likewise for hvc0. - systemd.services."serial-getty@${qemuSerialDevice}".enable = false; + systemd.services."serial-getty@${qemu-common.qemuSerialDevice}".enable = false; systemd.services."serial-getty@hvc0".enable = false; # Only set these settings when the options exist. Some tests (e.g. those @@ -57,7 +60,7 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; }; # we avoid defining consoles if not possible. # TODO: refactor such that test-instrumentation can import qemu-vm # or declare virtualisation.qemu.console option in a module that's always imported - consoles = [ qemuSerialDevice ]; + consoles = [ qemu-common.qemuSerialDevice ]; package = lib.mkDefault pkgs.qemu_test; }; }; @@ -88,7 +91,7 @@ with import ../../lib/qemu-flags.nix { inherit pkgs; }; # Panic if an error occurs in stage 1 (rather than waiting for # user intervention). boot.kernelParams = - [ "console=${qemuSerialDevice}" "panic=1" "boot.panic_on_fail" ]; + [ "console=${qemu-common.qemuSerialDevice}" "panic=1" "boot.panic_on_fail" ]; # `xwininfo' is used by the test driver to query open windows. environment.systemPackages = [ pkgs.xorg.xwininfo ]; diff --git a/nixos/modules/virtualisation/qemu-vm.nix b/nixos/modules/virtualisation/qemu-vm.nix index b51c29f83d6..f7b6b4eac39 100644 --- a/nixos/modules/virtualisation/qemu-vm.nix +++ b/nixos/modules/virtualisation/qemu-vm.nix @@ -10,10 +10,10 @@ { config, lib, pkgs, options, ... }: with lib; -with import ../../lib/qemu-flags.nix { inherit pkgs; }; let + qemu-common = import ../../lib/qemu-common.nix { inherit lib pkgs; }; cfg = config.virtualisation; @@ -75,7 +75,7 @@ let in "-drive ${driveOpts} ${device}"; - drivesCmdLine = drives: concatStringsSep " " (imap1 driveCmdline drives); + drivesCmdLine = drives: concatStringsSep "\\\n " (imap1 driveCmdline drives); # Creates a device name from a 1-based a numerical index, e.g. @@ -108,7 +108,7 @@ let '' #! ${pkgs.runtimeShell} - NIX_DISK_IMAGE=$(readlink -f ''${NIX_DISK_IMAGE:-${config.virtualisation.diskImage}}) + NIX_DISK_IMAGE=$(readlink -f "''${NIX_DISK_IMAGE:-${config.virtualisation.diskImage}}") if ! test -e "$NIX_DISK_IMAGE"; then ${qemu}/bin/qemu-img create -f qcow2 "$NIX_DISK_IMAGE" \ @@ -121,26 +121,29 @@ let fi # Create a directory for exchanging data with the VM. - mkdir -p $TMPDIR/xchg + mkdir -p "$TMPDIR/xchg" - ${if cfg.useBootLoader then '' + ${lib.optionalString cfg.useBootLoader + '' # Create a writable copy/snapshot of the boot disk. # A writable boot disk can be booted from automatically. - ${qemu}/bin/qemu-img create -f qcow2 -b ${bootDisk}/disk.img $TMPDIR/disk.img || exit 1 + ${qemu}/bin/qemu-img create -f qcow2 -F qcow2 -b ${bootDisk}/disk.img "$TMPDIR/disk.img" || exit 1 - NIX_EFI_VARS=$(readlink -f ''${NIX_EFI_VARS:-${cfg.efiVars}}) + NIX_EFI_VARS=$(readlink -f "''${NIX_EFI_VARS:-${cfg.efiVars}}") - ${if cfg.useEFIBoot then '' + ${lib.optionalString cfg.useEFIBoot + '' # VM needs writable EFI vars if ! test -e "$NIX_EFI_VARS"; then cp ${bootDisk}/efi-vars.fd "$NIX_EFI_VARS" || exit 1 chmod 0644 "$NIX_EFI_VARS" || exit 1 fi - '' else ""} - '' else ""} + ''} + ''} - cd $TMPDIR - idx=0 + cd "$TMPDIR" || exit 1 + + ${lib.optionalString (cfg.emptyDiskImages != []) "idx=0"} ${flip concatMapStrings cfg.emptyDiskImages (size: '' if ! test -e "empty$idx.qcow2"; then ${qemu}/bin/qemu-img create -f qcow2 "empty$idx.qcow2" "${toString size}M" @@ -149,17 +152,18 @@ let '')} # Start QEMU. - exec ${qemuBinary qemu} \ + exec ${qemu-common.qemuBinary qemu} \ -name ${config.system.name} \ -m ${toString config.virtualisation.memorySize} \ -smp ${toString config.virtualisation.cores} \ -device virtio-rng-pci \ ${concatStringsSep " " config.virtualisation.qemu.networkingOptions} \ - -virtfs local,path=/nix/store,security_model=none,mount_tag=store \ - -virtfs local,path=$TMPDIR/xchg,security_model=none,mount_tag=xchg \ - -virtfs local,path=''${SHARED_DIR:-$TMPDIR/xchg},security_model=none,mount_tag=shared \ + ${concatStringsSep " \\\n " + (mapAttrsToList + (tag: share: "-virtfs local,path=${share.source},security_model=none,mount_tag=${tag}") + config.virtualisation.sharedDirectories)} \ ${drivesCmdLine config.virtualisation.qemu.drives} \ - ${toString config.virtualisation.qemu.options} \ + ${concatStringsSep " \\\n " config.virtualisation.qemu.options} \ $QEMU_OPTS \ "$@" ''; @@ -270,20 +274,21 @@ in virtualisation.memorySize = mkOption { + type = types.ints.positive; default = 384; description = '' - Memory size (M) of virtual machine. + The memory size in megabytes of the virtual machine. ''; }; virtualisation.msize = mkOption { - default = null; - type = types.nullOr types.ints.unsigned; + type = types.ints.positive; + default = 16384; description = '' - msize (maximum packet size) option passed to 9p file systems, in + The msize (maximum packet size) option passed to 9p file systems, in bytes. Increasing this should increase performance significantly, at the cost of higher RAM usage. ''; @@ -291,15 +296,17 @@ in virtualisation.diskSize = mkOption { + type = types.nullOr types.ints.positive; default = 512; description = '' - Disk size (M) of virtual machine. + The disk size in megabytes of the virtual machine. ''; }; virtualisation.diskImage = mkOption { + type = types.str; default = "./${config.system.name}.qcow2"; description = '' @@ -311,7 +318,7 @@ in virtualisation.bootDevice = mkOption { - type = types.str; + type = types.path; example = "/dev/vda"; description = '' @@ -321,8 +328,8 @@ in virtualisation.emptyDiskImages = mkOption { + type = types.listOf types.ints.positive; default = []; - type = types.listOf types.int; description = '' Additional disk images to provide to the VM. The value is @@ -333,6 +340,7 @@ in virtualisation.graphics = mkOption { + type = types.bool; default = true; description = '' @@ -342,10 +350,20 @@ in ''; }; + virtualisation.resolution = + mkOption { + type = options.services.xserver.resolutions.type.nestedTypes.elemType; + default = { x = 1024; y = 768; }; + description = + '' + The resolution of the virtual machine display. + ''; + }; + virtualisation.cores = mkOption { + type = types.ints.positive; default = 1; - type = types.int; description = '' Specify the number of cores the guest is permitted to use. @@ -354,8 +372,34 @@ in ''; }; + virtualisation.sharedDirectories = + mkOption { + type = types.attrsOf + (types.submodule { + options.source = mkOption { + type = types.str; + description = "The path of the directory to share, can be a shell variable"; + }; + options.target = mkOption { + type = types.path; + description = "The mount point of the directory inside the virtual machine"; + }; + }); + default = { }; + example = { + my-share = { source = "/path/to/be/shared"; target = "/mnt/shared"; }; + }; + description = + '' + An attributes set of directories that will be shared with the + virtual machine using VirtFS (9P filesystem over VirtIO). + The attribute name will be used as the 9P mount tag. + ''; + }; + virtualisation.pathsInNixDB = mkOption { + type = types.listOf types.path; default = []; description = '' @@ -367,8 +411,78 @@ in ''; }; + virtualisation.forwardPorts = mkOption { + type = types.listOf + (types.submodule { + options.from = mkOption { + type = types.enum [ "host" "guest" ]; + default = "host"; + description = + '' + Controls the direction in which the ports are mapped: + + - <literal>"host"</literal> means traffic from the host ports + is forwarded to the given guest port. + + - <literal>"guest"</literal> means traffic from the guest ports + is forwarded to the given host port. + ''; + }; + options.proto = mkOption { + type = types.enum [ "tcp" "udp" ]; + default = "tcp"; + description = "The protocol to forward."; + }; + options.host.address = mkOption { + type = types.str; + default = ""; + description = "The IPv4 address of the host."; + }; + options.host.port = mkOption { + type = types.port; + description = "The host port to be mapped."; + }; + options.guest.address = mkOption { + type = types.str; + default = ""; + description = "The IPv4 address on the guest VLAN."; + }; + options.guest.port = mkOption { + type = types.port; + description = "The guest port to be mapped."; + }; + }); + default = []; + example = lib.literalExample + '' + [ # forward local port 2222 -> 22, to ssh into the VM + { from = "host"; host.port = 2222; guest.port = 22; } + + # forward local port 80 -> 10.0.2.10:80 in the VLAN + { from = "guest"; + guest.address = "10.0.2.10"; guest.port = 80; + host.address = "127.0.0.1"; host.port = 80; + } + ] + ''; + description = + '' + When using the SLiRP user networking (default), this option allows to + forward ports to/from the host/guest. + + <warning><para> + If the NixOS firewall on the virtual machine is enabled, you also + have to open the guest ports to enable the traffic between host and + guest. + </para></warning> + + <note><para>Currently QEMU supports only IPv4 forwarding.</para></note> + ''; + }; + virtualisation.vlans = mkOption { + type = types.listOf types.ints.unsigned; default = [ 1 ]; example = [ 1 2 ]; description = @@ -386,6 +500,7 @@ in virtualisation.writableStore = mkOption { + type = types.bool; default = true; # FIXME description = '' @@ -397,6 +512,7 @@ in virtualisation.writableStoreUseTmpfs = mkOption { + type = types.bool; default = true; description = '' @@ -407,6 +523,7 @@ in networking.primaryIPAddress = mkOption { + type = types.str; default = ""; internal = true; description = "Primary IP address used in /etc/hosts."; @@ -423,7 +540,7 @@ in options = mkOption { - type = types.listOf types.unspecified; + type = types.listOf types.str; default = []; example = [ "-vga std" ]; description = "Options passed to QEMU."; @@ -432,7 +549,7 @@ in consoles = mkOption { type = types.listOf types.str; default = let - consoles = [ "${qemuSerialDevice},115200n8" "tty0" ]; + consoles = [ "${qemu-common.qemuSerialDevice},115200n8" "tty0" ]; in if cfg.graphics then consoles else reverseList consoles; example = [ "console=tty1" ]; description = '' @@ -448,17 +565,18 @@ in networkingOptions = mkOption { - default = [ + type = types.listOf types.str; + default = [ ]; + example = [ "-net nic,netdev=user.0,model=virtio" - "-netdev user,id=user.0\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}" + "-netdev user,id=user.0,\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}" ]; - type = types.listOf types.str; description = '' Networking-related command-line options that should be passed to qemu. - The default is to use userspace networking (slirp). + The default is to use userspace networking (SLiRP). If you override this option, be advised to keep - ''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS} (as seen in the default) + ''${QEMU_NET_OPTS:+,$QEMU_NET_OPTS} (as seen in the example) to keep the default runtime behaviour. ''; }; @@ -472,16 +590,16 @@ in diskInterface = mkOption { + type = types.enum [ "virtio" "scsi" "ide" ]; default = "virtio"; example = "scsi"; - type = types.enum [ "virtio" "scsi" "ide" ]; description = "The interface used for the virtual hard disks."; }; guestAgent.enable = mkOption { - default = true; type = types.bool; + default = true; description = '' Enable the Qemu guest agent. ''; @@ -490,6 +608,7 @@ in virtualisation.useBootLoader = mkOption { + type = types.bool; default = false; description = '' @@ -504,6 +623,7 @@ in virtualisation.useEFIBoot = mkOption { + type = types.bool; default = false; description = '' @@ -515,6 +635,7 @@ in virtualisation.efiVars = mkOption { + type = types.str; default = "./${config.system.name}-efi-vars.fd"; description = '' @@ -525,8 +646,8 @@ in virtualisation.bios = mkOption { - default = null; type = types.nullOr types.package; + default = null; description = '' An alternate BIOS (such as <package>qboot</package>) with which to start the VM. @@ -539,6 +660,25 @@ in config = { + assertions = + lib.concatLists (lib.flip lib.imap cfg.forwardPorts (i: rule: + [ + { assertion = rule.from == "guest" -> rule.proto == "tcp"; + message = + '' + Invalid virtualisation.forwardPorts.<entry ${toString i}>.proto: + Guest forwarding supports only TCP connections. + ''; + } + { assertion = rule.from == "guest" -> lib.hasPrefix "10.0.2." rule.guest.address; + message = + '' + Invalid virtualisation.forwardPorts.<entry ${toString i}>.guest.address: + The address must be in the default VLAN (10.0.2.0/24). + ''; + } + ])); + # Note [Disk layout with `useBootLoader`] # # If `useBootLoader = true`, we configure 2 drives: @@ -560,6 +700,7 @@ in then driveDeviceName 2 # second disk else cfg.bootDevice ); + boot.loader.grub.gfxmodeBios = with cfg.resolution; "${toString x}x${toString y}"; boot.initrd.extraUtilsCommands = '' @@ -618,6 +759,28 @@ in virtualisation.pathsInNixDB = [ config.system.build.toplevel ]; + virtualisation.sharedDirectories = { + nix-store = { source = "/nix/store"; target = "/nix/store"; }; + xchg = { source = ''"$TMPDIR"/xchg''; target = "/tmp/xchg"; }; + shared = { source = ''"''${SHARED_DIR:-$TMPDIR/xchg}"''; target = "/tmp/shared"; }; + }; + + virtualisation.qemu.networkingOptions = + let + forwardingOptions = flip concatMapStrings cfg.forwardPorts + ({ proto, from, host, guest }: + if from == "host" + then "hostfwd=${proto}:${host.address}:${toString host.port}-" + + "${guest.address}:${toString guest.port}," + else "'guestfwd=${proto}:${guest.address}:${toString guest.port}-" + + "cmd:${pkgs.netcat}/bin/nc ${host.address} ${toString host.port}'," + ); + in + [ + "-net nic,netdev=user.0,model=virtio" + "-netdev user,id=user.0,${forwardingOptions}\${QEMU_NET_OPTS:+,$QEMU_NET_OPTS}" + ]; + # FIXME: Consolidate this one day. virtualisation.qemu.options = mkMerge [ (mkIf (pkgs.stdenv.isi686 || pkgs.stdenv.isx86_64) [ @@ -646,7 +809,7 @@ in virtualisation.qemu.drives = mkMerge [ [{ name = "root"; - file = "$NIX_DISK_IMAGE"; + file = ''"$NIX_DISK_IMAGE"''; driveExtraOpts.cache = "writeback"; driveExtraOpts.werror = "report"; }] @@ -655,7 +818,7 @@ in # note [Disk layout with `useBootLoader`]. { name = "boot"; - file = "$TMPDIR/disk.img"; + file = ''"$TMPDIR"/disk.img''; driveExtraOpts.media = "disk"; deviceExtraOpts.bootindex = "1"; } @@ -672,15 +835,26 @@ in # configuration, where the regular value for the `fileSystems' # attribute should be disregarded for the purpose of building a VM # test image (since those filesystems don't exist in the VM). - fileSystems = mkVMOverride ( - cfg.fileSystems // - { "/".device = cfg.bootDevice; - ${if cfg.writableStore then "/nix/.ro-store" else "/nix/store"} = - { device = "store"; - fsType = "9p"; - options = [ "trans=virtio" "version=9p2000.L" "cache=loose" ] ++ lib.optional (cfg.msize != null) "msize=${toString cfg.msize}"; - neededForBoot = true; - }; + fileSystems = + let + mkSharedDir = tag: share: + { + name = + if tag == "nix-store" && cfg.writableStore + then "/nix/.ro-store" + else share.target; + value.device = tag; + value.fsType = "9p"; + value.neededForBoot = true; + value.options = + [ "trans=virtio" "version=9p2000.L" "msize=${toString cfg.msize}" ] + ++ lib.optional (tag == "nix-store") "cache=loose"; + }; + in + mkVMOverride (cfg.fileSystems // + { + "/".device = cfg.bootDevice; + "/tmp" = mkIf config.boot.tmpOnTmpfs { device = "tmpfs"; fsType = "tmpfs"; @@ -688,32 +862,20 @@ in # Sync with systemd's tmp.mount; options = [ "mode=1777" "strictatime" "nosuid" "nodev" "size=${toString config.boot.tmpOnTmpfsSize}" ]; }; - "/tmp/xchg" = - { device = "xchg"; - fsType = "9p"; - options = [ "trans=virtio" "version=9p2000.L" ] ++ lib.optional (cfg.msize != null) "msize=${toString cfg.msize}"; - neededForBoot = true; - }; - "/tmp/shared" = - { device = "shared"; - fsType = "9p"; - options = [ "trans=virtio" "version=9p2000.L" ] ++ lib.optional (cfg.msize != null) "msize=${toString cfg.msize}"; - neededForBoot = true; - }; - } // optionalAttrs (cfg.writableStore && cfg.writableStoreUseTmpfs) - { "/nix/.rw-store" = + + "/nix/.rw-store" = mkIf (cfg.writableStore && cfg.writableStoreUseTmpfs) { fsType = "tmpfs"; options = [ "mode=0755" ]; neededForBoot = true; }; - } // optionalAttrs cfg.useBootLoader - { "/boot" = + + "/boot" = mkIf cfg.useBootLoader # see note [Disk layout with `useBootLoader`] { device = "${lookupDriveDeviceName "boot" cfg.qemu.drives}2"; # 2 for e.g. `vdb2`, as created in `bootDisk` fsType = "vfat"; noCheck = true; # fsck fails on a r/o filesystem }; - }); + } // lib.mapAttrs' mkSharedDir cfg.sharedDirectories); swapDevices = mkVMOverride [ ]; boot.initrd.luks.devices = mkVMOverride {}; @@ -734,7 +896,7 @@ in # video driver the host uses. services.xserver.videoDrivers = mkVMOverride [ "modesetting" ]; services.xserver.defaultDepth = mkVMOverride 0; - services.xserver.resolutions = mkVMOverride [ { x = 1024; y = 768; } ]; + services.xserver.resolutions = mkVMOverride [ cfg.resolution ]; services.xserver.monitorSection = '' # Set a higher refresh rate so that resolutions > 800x600 work. |