#!/usr/bin/env bash export LC_ALL=C if ((BASH_VERSINFO[0] < 4)); then echo "Sorry, you need bash 4.0 or newer to run this script." exit 1 fi function ignore_msrs_always() { # Make sure the host has /etc/modprobe.d if [ -d /etc/modprobe.d ]; then # Skip if ignore_msrs is already enabled, assumes initramfs has been rebuilt if grep -lq 'ignore_msrs=Y' /etc/modprobe.d/kvm-quickemu.conf >/dev/null 2>&1; then echo "options kvm ignore_msrs=Y" | sudo tee /etc/modprobe.d/kvm-quickemu.conf sudo update-initramfs -k all -u fi else echo "ERROR! /etc/modprobe.d was not found, I don't know how to configure this system." exit 1 fi } function ignore_msrs_alert() { local ignore_msrs="" if [ -e /sys/module/kvm/parameters/ignore_msrs ]; then ignore_msrs=$(cat /sys/module/kvm/parameters/ignore_msrs) if [ "${ignore_msrs}" == "N" ]; then echo " - MSR: WARNING! Ignoring unhandled Model-Specific Registers is disabled." echo echo " echo 1 | sudo tee /sys/module/kvm/parameters/ignore_msrs" echo echo " If you are unable to run macOS or Windows VMs then run the above 👆" echo " This will enable ignoring of unhandled MSRs until you reboot the host." echo " You can make this change permanent by running: 'quickemu --ignore-msrs-always'" fi fi } function delete_shortcut() { local SHORTCUT_DIR="${HOME}/.local/share/applications/" if [ -e "${SHORTCUT_DIR}/${VMNAME}.desktop" ]; then rm "${SHORTCUT_DIR}/${VMNAME}.desktop" echo " - Deleted ${SHORTCUT_DIR}/${VMNAME}.desktop" fi } function delete_disk() { echo "Deleting ${VMNAME} virtual hard disk" if [ -e "${disk_img}" ]; then rm "${disk_img}" >/dev/null 2>&1 # Remove any EFI vars, but not for macOS rm "${VMDIR}"/OVMF_VARS*.fd >/dev/null 2>&1 rm "${VMPATH}/${VMDIR}"/OVMF_VARS*.fd >/dev/null 2>&1 rm "${VMDIR}/${VMNAME}-vars.fd" >/dev/null 2>&1 rm "${VMPATH}/${VMDIR}/${VMNAME}-vars.fd" >/dev/null 2>&1 echo " - Deleted ${disk_img}" delete_shortcut else echo " - ${disk_img} not found. Doing nothing." fi } function delete_vm() { echo "Deleting ${VMNAME} completely" if [ -d "${VMDIR}" ]; then rm -rf "${VMDIR}" rm "${VM}" echo " - Deleted ${VM} and ${VMDIR}/" delete_shortcut else echo " - ${VMDIR} not found. Doing nothing." fi } function kill_vm() { echo "Killing ${VMNAME}" if [ -z "${VM_PID}" ]; then echo " - ${VMNAME} is not running." rm -f "${VMDIR}/${VMNAME}.pid" elif [ -n "${VM_PID}" ]; then if kill -9 "${VM_PID}" > /dev/null 2>&1; then echo " - ${VMNAME} (${VM_PID}) killed." rm -f "${VMDIR}/${VMNAME}.pid" else echo " - ${VMNAME} (${VM_PID}) was not killed." fi elif [ ! -r "${VMDIR}/${VMNAME}.pid" ]; then echo " - ${VMNAME} has no ${VMDIR}/${VMNAME}.pid" fi } function snapshot_apply() { echo "Snapshot apply to ${disk_img}" local TAG="${1}" if [ -z "${TAG}" ]; then echo " - ERROR! No snapshot tag provided." exit fi if [ -e "${disk_img}" ]; then if ${QEMU_IMG} snapshot -q -a "${TAG}" "${disk_img}"; then echo " - Applied snapshot '${TAG}' to ${disk_img}" else echo " - ERROR! Failed to apply snapshot '${TAG}' to ${disk_img}" fi else echo " - NOTE! ${disk_img} not found. Doing nothing." fi } function snapshot_create() { echo "Snapshotting ${disk_img}" local TAG="${1}" if [ -z "${TAG}" ]; then echo "- ERROR! No snapshot tag provided." exit fi if [ -e "${disk_img}" ]; then if ${QEMU_IMG} snapshot -q -c "${TAG}" "${disk_img}"; then echo " - Created snapshot '${TAG}' for ${disk_img}" else echo " - ERROR! Failed to create snapshot '${TAG}' for ${disk_img}" fi else echo " - NOTE! ${disk_img} not found. Doing nothing." fi } function snapshot_delete() { echo "Snapshot removal ${disk_img}" local TAG="${1}" if [ -z "${TAG}" ]; then echo " - ERROR! No snapshot tag provided." exit fi if [ -e "${disk_img}" ]; then if ${QEMU_IMG} snapshot -q -d "${TAG}" "${disk_img}"; then echo " - Deleted snapshot '${TAG}' from ${disk_img}" else echo " - ERROR! Failed to delete snapshot '${TAG}' from ${disk_img}" fi else echo " - NOTE! ${disk_img} not found. Doing nothing." fi } function snapshot_info() { echo if [ -e "${disk_img}" ]; then ${QEMU_IMG} info "${disk_img}" fi } function get_port() { local PORT_START=$1 local PORT_RANGE=$((PORT_START+$2)) local PORT for ((PORT = PORT_START; PORT <= PORT_RANGE; PORT++)); do # Make sure port scans do not block too long. timeout 0.1s bash -c "echo >/dev/tcp/127.0.0.1/${PORT}" >/dev/null 2>&1 if [ ${?} -eq 1 ]; then echo "${PORT}" break fi done } function enable_usb_passthrough() { local DEVICE="" local USB_BUS="" local USB_DEV="" local USB_NAME="" local VENDOR_ID="" local PRODUCT_ID="" local USB_NOT_READY=0 # Have any USB devices been requested for pass-through? if (( ${#usb_devices[@]} )); then echo " - USB: Host pass-through requested:" for DEVICE in "${usb_devices[@]}"; do VENDOR_ID=$(echo "${DEVICE}" | cut -d':' -f1) PRODUCT_ID=$(echo "${DEVICE}" | cut -d':' -f2) USB_BUS=$(lsusb -d "${VENDOR_ID}:${PRODUCT_ID}" | cut -d' ' -f2) USB_DEV=$(lsusb -d "${VENDOR_ID}:${PRODUCT_ID}" | cut -d' ' -f4 | cut -d':' -f1) USB_NAME=$(lsusb -d "${VENDOR_ID}:${PRODUCT_ID}" | cut -d' ' -f7-) if [ -z "${USB_NAME}" ]; then echo " ! USB device ${VENDOR_ID}:${PRODUCT_ID} not found. Check your configuration" continue elif [ -w "/dev/bus/usb/${USB_BUS}/${USB_DEV}" ]; then echo " o ${USB_NAME} on bus ${USB_BUS} device ${USB_DEV} is accessible." else echo " x ${USB_NAME} on bus ${USB_BUS} device ${USB_DEV} needs permission changes:" echo " sudo chown -v root:${USER} /dev/bus/usb/${USB_BUS}/${USB_DEV}" USB_NOT_READY=1 fi USB_PASSTHROUGH="${USB_PASSTHROUGH} -device usb-host,bus=hostpass.0,vendorid=0x${VENDOR_ID},productid=0x${PRODUCT_ID}" done if [ "${USB_NOT_READY}" -eq 1 ]; then echo " ERROR! USB permission changes are required 👆" exit 1 fi fi } function check_cpu_flag() { local HOST_CPU_FLAG="${1}" if lscpu | grep -o "^Flags\b.*: .*\b${HOST_CPU_FLAG}\b" > /dev/null; then return 0 else return 1 fi } function efi_vars() { local VARS_IN="" local VARS_OUT="" VARS_IN="${1}" VARS_OUT="${2}" if [ ! -e "${VARS_OUT}" ]; then if [ -e "${VARS_IN}" ]; then cp "${VARS_IN}" "${VARS_OUT}" else echo "ERROR! ${VARS_IN} was not found. Please install edk2." exit 1 fi fi } function vm_boot() { local AUDIO_DEV="" local BALLOON="-device virtio-balloon" local BOOT_STATUS="" local CPU="" local DISK_USED="" local DISPLAY_DEVICE="" local DISPLAY_RENDER="" local EFI_CODE="${EFI_CODE}" local EFI_VARS="" local GUEST_CPU_CORES="" local GUEST_CPU_LOGICAL_CORES="" local GUEST_CPU_THREADS="" local HOST_CPU_CORES="" local HOST_CPU_SMT="" local HOST_CPU_SOCKETS="" local HOST_CPU_VENDOR="" local GUEST_TWEAKS="" local KERNEL_NAME="Unknown" local KERNEL_NODE="" local KERNEL_VER="?" local LSB_DESCRIPTION="Unknown OS" local MACHINE_TYPE="${MACHINE_TYPE:-q35}" local MAC_BOOTLOADER="" local MAC_MISSING="" local MAC_DISK_DEV="${MAC_DISK_DEV:-ide-hd,bus=ahci.2}" local NET_DEVICE="${NET_DEVICE:-virtio-net}" local OSK="" local SOUND="" local SMM="${SMM:-off}" local TEMP_PORT="" local USB_HOST_PASSTHROUGH_CONTROLLER="qemu-xhci" local VGA="" local VIDEO="" KERNEL_NAME=$(uname --kernel-name) KERNEL_NODE="($(uname --nodename))" KERNEL_VER=$(uname --kernel-release | cut -d'.' -f1-2) if [ -e /etc/os-release ]; then LSB_DESCRIPTION=$(grep PRETTY_NAME /etc/os-release | cut -d'"' -f2) fi echo "Quickemu ${VERSION} using ${QEMU} v${QEMU_VER_LONG}" echo " - Host: ${LSB_DESCRIPTION} running ${KERNEL_NAME} ${KERNEL_VER} ${KERNEL_NODE}" HOST_CPU_CORES=$(nproc) HOST_CPU_MODEL=$(lscpu | grep '^Model name:' | cut -d':' -f2 | sed -e 's/^[[:space:]]*//') HOST_CPU_SOCKETS=$(lscpu | grep -E 'Socket' | cut -d':' -f2 | sed 's/ //g') HOST_CPU_VENDOR=$(lscpu | grep -E 'Vendor' | cut -d':' -f2 | sed 's/ //g') # A CPU with Intel VT-x / AMD SVM support is required if [ "${HOST_CPU_VENDOR}" == "GenuineIntel" ]; then if ! check_cpu_flag vmx; then echo "ERROR! Intel VT-x support is required." exit 1 fi elif [ "${HOST_CPU_VENDOR}" == "AuthenticAMD" ]; then if ! check_cpu_flag svm; then echo "ERROR! AMD SVM support is required." exit 1 fi fi if [ -z "${cpu_cores}" ]; then if [ "${HOST_CPU_CORES}" -ge 32 ]; then GUEST_CPU_CORES="16" elif [ "${HOST_CPU_CORES}" -ge 16 ]; then GUEST_CPU_CORES="8" elif [ "${HOST_CPU_CORES}" -ge 8 ]; then GUEST_CPU_CORES="4" elif [ "${HOST_CPU_CORES}" -ge 4 ]; then GUEST_CPU_CORES="2" else GUEST_CPU_CORES="1" fi else GUEST_CPU_CORES="${cpu_cores}" fi if [ "${guest_os}" == "macos" ] && [ "${GUEST_CPU_CORES}" -gt 10 ] || [ "${GUEST_CPU_CORES}" -eq 6 ] || [ "${GUEST_CPU_CORES}" -eq 7 ]; then # macOS guests cannot boot with most core counts not powers of 2. This will fix the issue by rounding the core count down to a power of 2. Uses wc and factor from coreutils. factorCPUCores=$(factor "${GUEST_CPU_CORES}") GUEST_CPU_CORES=$(( 2 ** $(echo "${factorCPUCores#*:}" | grep -o '[0-9]' | wc -l) )) fi # Account for Hyperthreading/SMT. if [ -e /sys/devices/system/cpu/smt/control ] && [ "${GUEST_CPU_CORES}" -ge 2 ]; then HOST_CPU_SMT=$(cat /sys/devices/system/cpu/smt/control) case ${HOST_CPU_SMT} in on) GUEST_CPU_THREADS=2 GUEST_CPU_LOGICAL_CORES=$(( GUEST_CPU_CORES / GUEST_CPU_THREADS ));; *) GUEST_CPU_THREADS=1 GUEST_CPU_LOGICAL_CORES=${GUEST_CPU_CORES};; esac else GUEST_CPU_THREADS=1 GUEST_CPU_LOGICAL_CORES=${GUEST_CPU_CORES} fi local SMP="-smp cores=${GUEST_CPU_LOGICAL_CORES},threads=${GUEST_CPU_THREADS},sockets=${HOST_CPU_SOCKETS}" echo " - CPU: ${HOST_CPU_MODEL}" echo -n " - CPU VM: ${HOST_CPU_SOCKETS} Socket(s), ${GUEST_CPU_LOGICAL_CORES} Core(s), ${GUEST_CPU_THREADS} Thread(s)" local RAM_VM="2G" if [ -z "${ram}" ]; then local RAM_HOST="" # Determine the number of gigabytes of RAM in the host by extracting the first numerical value from the output. RAM_HOST=$(free --giga | tr ' ' '\n' | grep -m 1 [0-9]) if [ "${RAM_HOST}" -ge 128 ]; then RAM_VM="32G" elif [ "${RAM_HOST}" -ge 64 ]; then RAM_VM="16G" elif [ "${RAM_HOST}" -ge 16 ]; then RAM_VM="8G" elif [ "${RAM_HOST}" -ge 8 ]; then RAM_VM="4G" fi else RAM_VM="${ram}" fi echo ", ${RAM_VM} RAM" if [ "${guest_os}" == "windows" ] || [ "${guest_os}" == "windows-server" ]; then if [ "${RAM_VM//G/}" -lt 4 ]; then echo "ERROR! The guest virtual machine has been allocated insufficient RAM to run Windows." echo " You can override the guest RAM allocation by adding 'ram=4G' to ${VM}" exit 1 fi elif [ "${guest_os}" == "macos" ]; then if [ "${RAM_VM//G/}" -lt 8 ]; then echo "ERROR! The guest virtual machine has been allocated insufficient RAM to run macOS." echo " You can override the guest RAM allocation by adding 'ram=8G' to ${VM}" exit 1 fi fi # Force to lowercase. boot=${boot,,} guest_os=${guest_os,,} if [ "${guest_os}" == "macos" ] || [ "${guest_os}" == "windows" ] || [ "${guest_os}" == "windows-server" ]; then # Display MSRs alert if the guest is macOS or windows ignore_msrs_alert fi # Always Boot macOS using EFI if [ "${guest_os}" == "macos" ]; then boot="efi" if [ -e "${VMDIR}/OVMF_CODE.fd" ] && [ -e "${VMDIR}/OVMF_VARS-1024x768.fd" ]; then EFI_CODE="${VMDIR}/OVMF_CODE.fd" EFI_VARS="${VMDIR}/OVMF_VARS-1024x768.fd" elif [ -e "${VMDIR}/OVMF_CODE.fd" ] && [ -e "${VMDIR}/OVMF_VARS-1920x1080.fd" ]; then EFI_CODE="${VMDIR}/OVMF_CODE.fd" EFI_VARS="${VMDIR}/OVMF_VARS-1920x1080.fd" else MAC_MISSING="Firmware" fi if [ -e "${VMDIR}/OpenCore.qcow2" ]; then MAC_BOOTLOADER="${VMDIR}/OpenCore.qcow2" elif [ -e "${VMDIR}/ESP.qcow2" ]; then # Backwards compatibility for Clover MAC_BOOTLOADER="${VMDIR}/ESP.qcow2" else MAC_MISSING="Bootloader" fi if [ -n "${MAC_MISSING}" ]; then echo "ERROR! macOS ${MAC_MISSING} was not found." echo " Use 'quickget' to download the required files." exit 1 fi BOOT_STATUS="EFI (macOS), OVMF ($(basename "${EFI_CODE}")), SecureBoot (${secureboot})." elif [[ "${boot}" == *"efi"* ]]; then EFI_VARS="${VMDIR}/OVMF_VARS.fd" # Preserve backward compatibility if [ -e "${VMDIR}/${VMNAME}-vars.fd" ]; then mv "${VMDIR}/${VMNAME}-vars.fd" "${EFI_VARS}" elif [ -e "${VMDIR}/OVMF_VARS_4M.fd" ]; then mv "${VMDIR}/OVMF_VARS_4M.fd" "${EFI_VARS}" fi # OVMF_CODE_4M.fd is for booting guests in non-Secure Boot mode. # While this image technically supports Secure Boot, it does so # without requiring SMM support from QEMU # OVMF_CODE.secboot.fd is like OVMF_CODE_4M.fd, but will abort if QEMU # does not support SMM. # https://bugzilla.redhat.com/show_bug.cgi?id=1929357#c5 if [ -n "${EFI_CODE}" ] || [ ! -e "${EFI_CODE}" ]; then case ${secureboot} in on) # shellcheck disable=SC2054,SC2140 ovmfs=("/usr/share/OVMF/OVMF_CODE_4M.secboot.fd","/usr/share/OVMF/OVMF_VARS_4M.fd" \ "/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd","/usr/share/edk2/ovmf/OVMF_VARS.fd" \ "/usr/share/OVMF/x64/OVMF_CODE.secboot.fd","/usr/share/OVMF/x64/OVMF_VARS.fd" \ "/usr/share/edk2-ovmf/OVMF_CODE.secboot.fd","/usr/share/edk2-ovmf/OVMF_VARS.fd" \ "/usr/share/qemu/ovmf-x86_64-smm-ms-code.bin","/usr/share/qemu/ovmf-x86_64-smm-ms-vars.bin" \ "/usr/share/qemu/edk2-x86_64-secure-code.fd","/usr/share/qemu/edk2-x86_64-code.fd" \ "/usr/share/edk2-ovmf/x64/OVMF_CODE.secboot.fd","/usr/share/edk2-ovmf/x64/OVMF_VARS.fd" );; *) # shellcheck disable=SC2054,SC2140 ovmfs=("/usr/share/OVMF/OVMF_CODE_4M.fd","/usr/share/OVMF/OVMF_VARS_4M.fd" \ "/usr/share/edk2/ovmf/OVMF_CODE.fd","/usr/share/edk2/ovmf/OVMF_VARS.fd" \ "/usr/share/OVMF/OVMF_CODE.fd","/usr/share/OVMF/OVMF_VARS.fd" \ "/usr/share/OVMF/x64/OVMF_CODE.fd","/usr/share/OVMF/x64/OVMF_VARS.fd" \ "/usr/share/edk2-ovmf/OVMF_CODE.fd","/usr/share/edk2-ovmf/OVMF_VARS.fd" \ "/usr/share/qemu/ovmf-x86_64-4m-code.bin","/usr/share/qemu/ovmf-x86_64-4m-vars.bin" \ "/usr/share/qemu/edk2-x86_64-code.fd","/usr/share/qemu/edk2-x86_64-code.fd" \ "/usr/share/edk2-ovmf/x64/OVMF_CODE.fd","/usr/share/edk2-ovmf/x64/OVMF_VARS.fd" );; esac # Attempt each EFI_CODE file one by one, selecting the corresponding code and vars # when an existing file is found. _IFS=$IFS IFS="," for f in "${ovmfs[@]}"; do # shellcheck disable=SC2086 set -- ${f}; if [ -e "${1}" ]; then EFI_CODE="${1}" EFI_EXTRA_VARS="${2}" fi done IFS=$_IFS fi if [ -z "${EFI_CODE}" ] || [ ! -e "${EFI_CODE}" ]; then if [ "$secureboot" == "on" ]; then echo "ERROR! SecureBoot was requested but no SecureBoot capable firmware was found." else echo "ERROR! EFI boot requested but no EFI firmware found." fi echo " Please install OVMF firmware." exit 1 fi if [ -n "${EFI_EXTRA_VARS}" ]; then if [ ! -e "${EFI_EXTRA_VARS}" ]; then echo " - EFI: ERROR! EFI_EXTRA_VARS file ${EFI_EXTRA_VARS} does not exist." exit 1 fi efi_vars "${EFI_EXTRA_VARS}" "${EFI_VARS}" fi # Make sure EFI_VARS references an actual, writeable, file if [ ! -f "${EFI_VARS}" ] || [ ! -w "${EFI_VARS}" ]; then echo " - EFI: ERROR! ${EFI_VARS} is not a regular file or not writeable." echo " Deleting ${EFI_VARS}. Please re-run quickemu." rm -f "${EFI_VARS}" exit 1 fi # If EFI_CODE references a symlink, resolve it to the real file. if [ -L "${EFI_CODE}" ]; then echo " - EFI: WARNING! ${EFI_CODE} is a symlink." echo -n " Resolving to... " EFI_CODE=$(realpath "${EFI_CODE}") echo "${EFI_CODE}" fi BOOT_STATUS="EFI (${guest_os^}), OVMF (${EFI_CODE}), SecureBoot (${secureboot})." else BOOT_STATUS="Legacy BIOS (${guest_os^})" boot="legacy" secureboot="off" fi echo " - BOOT: ${BOOT_STATUS}" # Make any OS specific adjustments case ${guest_os} in batocera|*bsd|freedos|haiku|linux*|*solaris) CPU="-cpu host,kvm=on" if [ "${HOST_CPU_VENDOR}" == "AuthenticAMD" ]; then CPU="${CPU},topoext" fi if [ "${guest_os}" == "freebsd" ] || [ "${guest_os}" == "ghostbsd" ]; then mouse="usb" elif [ "${guest_os}" == "batocera" ] || [ "${guest_os}" == "freedos" ] || [ "${guest_os}" == "haiku" ]; then MACHINE_TYPE="pc" NET_DEVICE="rtl8139" fi if [ "${guest_os}" == "freedos" ] ; then # fix for #382 SMM="on" sound_card="sb16" fi if [[ "${guest_os}" == *"solaris" ]]; then MACHINE_TYPE="pc" usb_controller="xhci" sound_card="ac97" fi ;; kolibrios|reactos) CPU="-cpu qemu32,kvm=on" if [ "${HOST_CPU_VENDOR}" == "AuthenticAMD" ]; then CPU="${CPU},topoext" fi MACHINE_TYPE="pc" case ${guest_os} in kolibrios) NET_DEVICE="rtl8139";; reactos) NET_DEVICE="e1000" keyboard="ps2";; esac ;; macos) # quickget current list: mojave catalina big-sur monterey ventura sonoma # A CPU with fma is required for Metal support # A CPU with invtsc is required for macOS to boot # A CPU with SSE4.1 support is required for >= macOS Sierra # A CPU with SSE4.2 support is required for >= macOS Catalina # A CPU with AVX2 support is required for >= macOS Ventura if ! check_cpu_flag fma && ! check_cpu_flag invtsc; then echo "ERROR! macOS requires a CPU with FMA and INV TSC support." exit 1 fi # TODO: Investigate if hosts with an Intel CPU can just use `-cpu host` case ${macos_release} in ventura|sonoma) if check_cpu_flag sse4_2 && check_cpu_flag avx2; then CPU="-cpu Haswell-v4,kvm=on,vendor=GenuineIntel,+avx,+avx2,+sse,+sse2,+sse3,+sse4.2,vmware-cpuid-freq=on" else echo "ERROR! macOS ${macos_release} requires a CPU with SSE 4.2 and AVX2 support." exit 1 fi;; catalina|big-sur|monterey) if check_cpu_flag sse4_2; then CPU="-cpu Haswell-v4,kvm=on,vendor=GenuineIntel,+avx,+sse,+sse2,+sse3,+sse4.2,vmware-cpuid-freq=on" else echo "ERROR! macOS ${macos_release} requires a CPU with SSE 4.2 support." exit 1 fi;; *) if check_cpu_flag sse4_1; then CPU="-cpu Penryn,kvm=on,vendor=GenuineIntel,+avx,+sse,+sse2,+sse3,+sse4.1,vmware-cpuid-freq=on" else echo "ERROR! macOS ${macos_release} requires a CPU with SSE 4.1 support." exit 1 fi;; esac for FLAG in abm adx aes amd-ssbd bmi1 bmi2 cx8 eist ept f16c fma invtsc \ mmx movbe mpx pdpe1gb popcnt smep vaes vbmi2 vpclmulqdq \ xgetbv1 xsave xsaveopt; do if check_cpu_flag "${FLAG}"; then CPU+=",+${FLAG}" fi done OSK=$(echo "bheuneqjbexolgurfrjbeqfthneqrqcyrnfrqbagfgrny(p)NccyrPbzchgreVap" | tr 'A-Za-z' 'N-ZA-Mn-za-m') # Disable S3 support in the VM to prevent macOS suspending during install GUEST_TWEAKS="-global kvm-pit.lost_tick_policy=discard -global ICH9-LPC.disable_s3=1 -device isa-applesmc,osk=${OSK}" # Disable High Precision Timer if [ "${QEMU_VER_SHORT}" -ge 70 ]; then MACHINE_TYPE+=",hpet=off" else GUEST_TWEAKS+=" -no-hpet" fi # Tune Qemu optimisations based on the macOS release, or fallback to lowest # common supported options if none is specified. # * VirtIO Block Media doesn't work in High Sierra (at all) or the Mojave (Recovery Image) # * VirtIO Network is supported since Big Sur # * VirtIO Memory Balloning is supported since Big Sur (https://pmhahn.github.io/virtio-balloon/) # * VirtIO RNG is supported since Big Sur, but exposed to all guests by default. case ${macos_release} in big-sur|monterey|ventura|sonoma) BALLOON="-device virtio-balloon" MAC_DISK_DEV="virtio-blk-pci" NET_DEVICE="virtio-net" USB_HOST_PASSTHROUGH_CONTROLLER="nec-usb-xhci" GUEST_TWEAKS="${GUEST_TWEAKS} -global nec-usb-xhci.msi=off" sound_card="${sound_card:-usb-audio}" usb_controller="xhci";; *) # Backwards compatibility if no macos_release is specified. # Also safe catch all for High Sierra and Mojave BALLOON="" if [ "${macos_release}" == "catalina" ]; then MAC_DISK_DEV="virtio-blk-pci" else MAC_DISK_DEV="ide-hd,bus=ahci.2" fi NET_DEVICE="vmxnet3" USB_HOST_PASSTHROUGH_CONTROLLER="usb-ehci";; esac ;; windows|windows-server) if [ "${QEMU_VER_SHORT}" -gt 60 ]; then CPU="-cpu host,kvm=on,+hypervisor,+invtsc,l3-cache=on,migratable=no,hv_passthrough" else CPU="-cpu host,kvm=on,+hypervisor,+invtsc,l3-cache=on,migratable=no,hv_frequencies,kvm_pv_unhalt,hv_reenlightenment,hv_relaxed,hv_spinlocks=8191,hv_stimer,hv_synic,hv_time,hv_vapic,hv_vendor_id=1234567890ab,hv_vpindex" fi if [ "${HOST_CPU_VENDOR}" == "AuthenticAMD" ]; then CPU="${CPU},topoext" fi # Disable S3 support in the VM to ensure Windows can boot with SecureBoot enabled # - https://wiki.archlinux.org/title/QEMU#VM_does_not_boot_when_using_a_Secure_Boot_enabled_OVMF GUEST_TWEAKS="-global kvm-pit.lost_tick_policy=discard -global ICH9-LPC.disable_s3=1" # Disable High Precision Timer if [ "${QEMU_VER_SHORT}" -ge 70 ]; then MACHINE_TYPE+=",hpet=off" else GUEST_TWEAKS+=" -no-hpet" fi SMM="on" ;; *) CPU="-cpu host,kvm=on" NET_DEVICE="rtl8139" echo "WARNING! Unrecognised guest OS: ${guest_os}";; esac echo " - Disk: ${disk_img} (${disk_size})" if [ ! -f "${disk_img}" ]; then # If there is no disk image, create a new image. mkdir -p "${VMDIR}" 2>/dev/null case ${preallocation} in off|metadata|falloc|full) true;; *) echo "ERROR! ${preallocation} is an unsupported disk preallocation option." exit 1;; esac # https://blog.programster.org/qcow2-performance if ! ${QEMU_IMG} create -q -f "${disk_format}" -o lazy_refcounts=on,preallocation="${preallocation}" "${disk_img}" "${disk_size}"; then echo "ERROR! Failed to create ${disk_img} using ${disk_format} format." exit 1 fi if [ -z "${iso}" ] && [ -z "${img}" ]; then echo "ERROR! You haven't specified a .iso or .img image to boot from." exit 1 fi echo " Just created, booting from ${iso}${img}" DISK_USED="no" elif [ -e "${disk_img}" ]; then # If the VM is not running, check for disk related issues. if [ -z "${VM_PID}" ]; then # Check there isn't already a process attached to the disk image. if ! ${QEMU_IMG} info "${disk_img}" >/dev/null; then echo " Failed to get \"write\" lock. Is another process using the disk?" exit 1 fi else if ! ${QEMU_IMG} check -q "${disk_img}"; then echo " Disk integrity check failed. Please run qemu-img check --help." echo "${QEMU_IMG}" check "${disk_img}" exit 1 fi fi # Only check disk image size if preallocation is off if [ "${preallocation}" == "off" ]; then DISK_CURR_SIZE=$(stat -c%s "${disk_img}") if [ "${DISK_CURR_SIZE}" -le "${DISK_MIN_SIZE}" ]; then echo " Looks unused, booting from ${iso}${img}" if [ -z "${iso}" ] && [ -z "${img}" ]; then echo "ERROR! You haven't specified a .iso or .img image to boot from." exit 1 fi else DISK_USED="yes" fi else DISK_USED="yes" fi fi if [ "${DISK_USED}" == "yes" ] && [ "${guest_os}" != "kolibrios" ]; then # If there is a disk image that appears to be used do not boot from installation media. iso="" img="" fi # Has the status quo been requested? if [ "${STATUS_QUO}" == "-snapshot" ]; then if [ -z "${img}" ] && [ -z "${iso}" ]; then echo " Existing disk state will be preserved, no writes will be committed." fi fi if [ -n "${iso}" ] && [ -e "${iso}" ]; then echo " - Boot ISO: ${iso}" elif [ -n "${img}" ] && [ -e "${img}" ]; then echo " - Recovery: ${img}" fi if [ -n "${fixed_iso}" ] && [ -e "${fixed_iso}" ]; then echo " - CD-ROM: ${fixed_iso}" fi # Setup the appropriate audio device based on the display output # https://www.kraxel.org/blog/2020/01/qemu-sound-audiodev/ case ${display} in none|spice|spice-app) AUDIO_DEV="spice,id=audio0";; *) AUDIO_DEV="pa,id=audio0";; esac # Determine a sane resolution for Linux guests. local X_RES="1280" local Y_RES="800" if [ -n "${width}" ] && [ -n "${height}" ]; then local X_RES="${width}" local Y_RES="${height}" fi # https://www.kraxel.org/blog/2019/09/display-devices-in-qemu/ case ${guest_os} in *bsd) DISPLAY_DEVICE="VGA";; linux_old|solaris) DISPLAY_DEVICE="vmware-svga";; linux) case ${display} in none|spice|spice-app) DISPLAY_DEVICE="virtio-gpu";; *) DISPLAY_DEVICE="virtio-vga";; esac;; macos) # qxl-vga and VGA supports seamless mouse and sane resolutions if only # one scanout is used. '-vga none' is added to the QEMU command line # to avoid having two scanouts. DISPLAY_DEVICE="VGA";; windows|windows-server) # virtio-gpu "works" with gtk but is limited to 1024x1024 and exhibits other issues # https://kevinlocke.name/bits/2021/12/10/windows-11-guest-virtio-libvirt/#video case ${display} in gtk|none|spice) DISPLAY_DEVICE="qxl-vga";; sdl|spice-app) DISPLAY_DEVICE="virtio-vga";; esac;; *) DISPLAY_DEVICE="qxl-vga";; esac # Map Quickemu $display to QEMU -display case ${display} in gtk) DISPLAY_RENDER="${display},grab-on-hover=on,zoom-to-fit=off,gl=${gl}";; none|spice) DISPLAY_RENDER="none";; sdl) DISPLAY_RENDER="${display},gl=${gl}";; spice-app) DISPLAY_RENDER="${display},gl=${gl}";; *) DISPLAY_RENDER="${display}";; esac # https://www.kraxel.org/blog/2021/05/virtio-gpu-qemu-graphics-update/ if [ "${gl}" == "on" ] && [ "${DISPLAY_DEVICE}" == "virtio-vga" ]; then if [ "${QEMU_VER_SHORT}" -ge 61 ]; then DISPLAY_DEVICE="${DISPLAY_DEVICE}-gl" else DISPLAY_DEVICE="${DISPLAY_DEVICE},virgl=on" fi echo -n " - Display: ${display^^}, ${DISPLAY_DEVICE}, GL (${gl}), VirGL (on)" else echo -n " - Display: ${display^^}, ${DISPLAY_DEVICE}, GL (${gl}), VirGL (off)" fi # Build the video configuration VIDEO="-device ${DISPLAY_DEVICE}" # Try and coerce the display resolution for Linux guests only. if [ "${DISPLAY_DEVICE}" != "vmware-svga" ]; then VIDEO="${VIDEO},xres=${X_RES},yres=${Y_RES}" echo " @ (${X_RES} x ${Y_RES})" else echo " " fi # Allocate VRAM to VGA devices case ${DISPLAY_DEVICE} in bochs-display) VIDEO="${VIDEO},vgamem=67108864";; qxl|qxl-vga) VIDEO="${VIDEO},ram_size=65536,vram_size=65536,vgamem_mb=64";; ati-vga|cirrus-vga|VGA|vmware-svga) VIDEO="${VIDEO},vgamem_mb=256";; esac # Configure multiscreen if max_outputs was provided in the .conf file if [ -v max_outputs ]; then VIDEO="${VIDEO},max_outputs=${max_outputs}" fi # Run QEMU with '-vga none' to avoid having two scanouts, one for VGA and # another for virtio-vga-gl. This works around a GTK assertion failure and # allows seamless mouse in macOS when using the qxl-vga device. # https://www.collabora.com/news-and-blog/blog/2021/11/26/venus-on-qemu-enabling-new-virtual-vulkan-driver/ # https://github.com/quickemu-project/quickemu/issues/222 VGA="-vga none" # Add fullscreen options VIDEO="${VGA} ${VIDEO} ${FULLSCREEN}" # Build the sound hardware configuration case ${sound_card} in ich9-intel-hda|intel-hda) SOUND="-device ${sound_card} -device ${sound_duplex},audiodev=audio0";; usb-audio) SOUND="-device ${sound_card},audiodev=audio0";; ac97|es1370|sb16) SOUND="-device ${sound_card},audiodev=audio0";; none) SOUND="";; esac echo " - Sound: ${sound_card} (${sound_duplex})" # Set the hostname of the VM local NET="user,hostname=${VMNAME}" echo -n "" > "${VMDIR}/${VMNAME}.ports" if [ -z "${ssh_port}" ]; then # Find a free port to expose ssh to the guest ssh_port=$(get_port 22220 9) fi if [ -n "${ssh_port}" ]; then echo "ssh,${ssh_port}" >> "${VMDIR}/${VMNAME}.ports" NET="${NET},hostfwd=tcp::${ssh_port}-:22" echo " - ssh: On host: ssh user@localhost -p ${ssh_port}" else echo " - ssh: All ssh ports have been exhausted." fi # Have any port forwards been requested? if (( ${#port_forwards[@]} )); then echo " - PORTS: Port forwards requested:" for FORWARD in "${port_forwards[@]}"; do HOST_PORT=$(echo "${FORWARD}" | cut -d':' -f1) GUEST_PORT=$(echo "${FORWARD}" | cut -d':' -f2) echo " - ${HOST_PORT} => ${GUEST_PORT}" NET="${NET},hostfwd=tcp::${HOST_PORT}-:${GUEST_PORT}" NET="${NET},hostfwd=udp::${HOST_PORT}-:${GUEST_PORT}" done fi if [ "${display}" == "none" ] || [ "${display}" == "spice" ] || [ "${display}" == "spice-app" ]; then local SPICE="disable-ticketing=on" # gl=on can be use with 'spice' too, but only over local connections (not tcp ports) if [ "${display}" == "spice-app" ]; then SPICE+=",gl=${gl}" fi # TODO: Don't use ports so local-only connections can be used with gl=on if [ -z "${spice_port}" ]; then # Find a free port for spice spice_port=$(get_port 5930 9) fi # ALLOW REMOTE ACCESS TO SPICE OVER LAN RATHER THAN JUST LOCALHOST if [ -z "${ACCESS}" ]; then SPICE_ADDR="127.0.0.1" else if [ "${ACCESS}" == "remote" ]; then SPICE_ADDR="" elif [ "${ACCESS}" == "local" ]; then SPICE_ADDR="127.0.0.1" else SPICE_ADDR="${ACCESS}" fi fi if [ -z "${spice_port}" ]; then echo " - SPICE: All SPICE ports have been exhausted." if [ "${display}" == "none" ] || [ "${display}" == "spice" ] || [ "${display}" == "spice-app" ]; then echo " ERROR! Requested SPICE display, but no SPICE ports are free." exit 1 fi else if [ "${display}" == "spice-app" ]; then echo " - SPICE: Enabled" else echo "spice,${spice_port}" >> "${VMDIR}/${VMNAME}.ports" echo -n " - SPICE: On host: spicy --title \"${VMNAME}\" --port ${spice_port}" if [ "${guest_os}" != "macos" ] && [ -n "${PUBLIC}" ]; then echo -n " --spice-shared-dir ${PUBLIC}" fi echo "${FULLSCREEN}" SPICE="${SPICE},port=${spice_port},addr=${SPICE_ADDR}" fi fi fi if [ -n "${PUBLIC}" ]; then case ${guest_os} in macos) if [ "${display}" == "none" ] || [ "${display}" == "spice" ] || [ "${display}" == "spice-app" ]; then # Reference: https://gitlab.gnome.org/GNOME/phodav/-/issues/5 echo " - WebDAV: On guest: build spice-webdavd (https://gitlab.gnome.org/GNOME/phodav/-/merge_requests/24)" echo " - WebDAV: On guest: Finder -> Connect to Server -> http://localhost:9843/" fi;; *) echo " - WebDAV: On guest: dav://localhost:9843/";; esac fi if [ "${guest_os}" != "windows" ] || [ "${guest_os}" == "windows-server" ] && [ -n "${PUBLIC}" ]; then echo -n " - 9P: On guest: " if [ "${guest_os}" == "linux" ]; then echo "sudo mount -t 9p -o trans=virtio,version=9p2000.L,msize=104857600 ${PUBLIC_TAG} ~/$(basename "${PUBLIC}")" elif [ "${guest_os}" == "macos" ]; then # PUBLICSHARE needs to be world writeable for seamless integration with # macOS. Test if it is world writeable, and prompt what to do if not. echo "sudo mount_9p ${PUBLIC_TAG}" if [ "${PUBLIC_PERMS}" != "drwxrwxrwx" ]; then echo " - 9P: On host: chmod 777 ${PUBLIC}" echo " Required for macOS integration 👆" fi fi fi # If smbd is available and ~/Public is present export it to the guest via samba if [[ -x "$(command -v smbd)" && -n ${PUBLIC} ]]; then NET="${NET},smb=${PUBLIC}" echo " - smbd: On guest: smb://10.0.2.4/qemu" fi enable_usb_passthrough echo "#!/usr/bin/env bash" > "${VMDIR}/${VMNAME}.sh" # Start TPM if [ "${tpm}" == "on" ]; then local tpm_args=() # shellcheck disable=SC2054 tpm_args+=(socket --ctrl type=unixio,path="${VMDIR}/${VMNAME}.swtpm-sock" --terminate --tpmstate dir="${VMDIR}" --tpm2) echo "${SWTPM} ${tpm_args[*]} &" >> "${VMDIR}/${VMNAME}.sh" ${SWTPM} "${tpm_args[@]}" >> "${VMDIR}/${VMNAME}.log" & echo " - TPM: ${VMDIR}/${VMNAME}.swtpm-sock (${!})" sleep 0.25 fi # Boot the VM local args=() # shellcheck disable=SC2054,SC2206,SC2140 args+=(-name ${VMNAME},process=${VMNAME} -pidfile "${VMDIR}/${VMNAME}.pid" -enable-kvm -machine ${MACHINE_TYPE},smm=${SMM},vmport=off ${GUEST_TWEAKS} ${CPU} ${SMP} -m ${RAM_VM} ${BALLOON} ${VIDEO} -display ${DISPLAY_RENDER} -rtc base=localtime,clock=host,driftfix=slew) # Only enable SPICE is using SPICE display if [ "${display}" == "none" ] || [ "${display}" == "spice" ] || [ "${display}" == "spice-app" ]; then # shellcheck disable=SC2054 args+=(-spice "${SPICE}" -device virtio-serial-pci -chardev socket,id=agent0,path="${VMDIR}/${VMNAME}-agent.sock",server=on,wait=off -device virtserialport,chardev=agent0,name=org.qemu.guest_agent.0 -chardev spicevmc,id=vdagent0,name=vdagent -device virtserialport,chardev=vdagent0,name=com.redhat.spice.0 -chardev spiceport,id=webdav0,name=org.spice-space.webdav.0 -device virtserialport,chardev=webdav0,name=org.spice-space.webdav.0) fi # shellcheck disable=SC2054 args+=(-device virtio-rng-pci,rng=rng0 -object rng-random,id=rng0,filename=/dev/urandom -device "${USB_HOST_PASSTHROUGH_CONTROLLER}",id=spicepass -chardev spicevmc,id=usbredirchardev1,name=usbredir -device usb-redir,chardev=usbredirchardev1,id=usbredirdev1 -chardev spicevmc,id=usbredirchardev2,name=usbredir -device usb-redir,chardev=usbredirchardev2,id=usbredirdev2 -chardev spicevmc,id=usbredirchardev3,name=usbredir -device usb-redir,chardev=usbredirchardev3,id=usbredirdev3 -device pci-ohci,id=smartpass -device usb-ccid ) if "${QEMU}" -chardev spicevmc,id=ccid,name= 2>&1 | grep -q smartcard; then # shellcheck disable=SC2054 args+=(-chardev spicevmc,id=ccid,name=smartcard -device ccid-card-passthru,chardev=ccid) else echo "WARNING! ${QEMU} was not compiled with support for smartcard devices" fi # setup usb-controller if [ "${usb_controller}" == "ehci" ]; then # shellcheck disable=SC2054 args+=(-device usb-ehci,id=input) elif [ "${usb_controller}" == "xhci" ]; then # shellcheck disable=SC2054 args+=(-device qemu-xhci,id=input) elif [ "${usb_controller}" == "none" ]; then # add nothing : else echo "WARNING! Unknown usb-controller value: '${usb_controller}'" fi # setup keyboard # @INFO: must be set after usb-controller if [ "${keyboard}" == "usb" ]; then # shellcheck disable=SC2054 args+=(-device usb-kbd,bus=input.0) elif [ "${keyboard}" == "virtio" ]; then # shellcheck disable=SC2054 args+=(-device virtio-keyboard) elif [ "${keyboard}" == "ps2" ]; then # add nothing, default is ps/2 keyboard : else echo "WARNING! Unknown keyboard value: '${keyboard}'; Fallback to ps2" fi # setup keyboard_layout # @INFO: When using the VNC display, you must use the -k parameter to set the keyboard layout if you are not using en-us. if [ -n "${keyboard_layout}" ]; then args+=(-k "${keyboard_layout}") fi # FIXME: Check for device availability. qemu will fail to start otherwise if [ -n "${BRAILLE}" ]; then # shellcheck disable=SC2054 args+=(-chardev braille,id=brltty -device usb-braille,id=usbbrl,chardev=brltty) fi # setup mouse # @INFO: must be set after usb-controller if [ "${mouse}" == "usb" ]; then # shellcheck disable=SC2054 args+=(-device usb-mouse,bus=input.0) elif [ "${mouse}" == "tablet" ]; then # shellcheck disable=SC2054 args+=(-device usb-tablet,bus=input.0) elif [ "${mouse}" == "virtio" ]; then # shellcheck disable=SC2054 args+=(-device virtio-mouse) elif [ "${mouse}" == "ps2" ]; then # add nothing, default is ps/2 mouse : else echo "WARNING! Unknown mouse value: '${mouse}; Fallback to ps2'" fi # setup audio # @INFO: must be set after usb-controller; in case usb-audio is used # shellcheck disable=SC2206 args+=(-audiodev ${AUDIO_DEV} ${SOUND}) # $bridge backwards compatibility for Quickemu <= 4.0 if [ -n "${bridge}" ]; then network="${bridge}" fi if [ "${network}" == "none" ]; then # Disable all networking echo " - Network: Disabled" args+=(-nic none) elif [ "${network}" == "restrict" ]; then echo " - Network: Restricted (${NET_DEVICE})" # shellcheck disable=SC2054,SC2206 args+=(-device ${NET_DEVICE},netdev=nic -netdev ${NET},restrict=y,id=nic) elif [ -n "${network}" ]; then # Enable bridge mode networking echo " - Network: Bridged (${network})" # If a persistent MAC address is provided, use it. local MAC="" if [ -n "${macaddr}" ]; then MAC=",mac=${macaddr}" fi # shellcheck disable=SC2054,SC2206 args+=(-nic bridge,br=${network},model=virtio-net-pci${MAC}) else echo " - Network: User (${NET_DEVICE})" # shellcheck disable=SC2054,SC2206 args+=(-device ${NET_DEVICE},netdev=nic -netdev ${NET},id=nic) fi # Add the disks # - https://turlucode.com/qemu-disk-io-performance-comparison-native-or-threads-windows-10-version/ if [[ "${boot}" == *"efi"* ]]; then # shellcheck disable=SC2054 args+=(-global driver=cfi.pflash01,property=secure,value=on -drive if=pflash,format=raw,unit=0,file="${EFI_CODE}",readonly=on -drive if=pflash,format=raw,unit=1,file="${EFI_VARS}") fi if [ -n "${iso}" ] && [ "${guest_os}" == "freedos" ]; then # FreeDOS reboots after partitioning the disk, and QEMU tries to boot from disk after first restart # This flag sets the boot order to cdrom,disk. It will persist until powering down the VM args+=(-boot order=dc) elif [ -n "${iso}" ] && [ "${guest_os}" == "kolibrios" ]; then # Since there is bug (probably) in KolibriOS: cdrom indexes 0 or 1 make system show an extra unexisting iso, so we use index=2 # shellcheck disable=SC2054 args+=(-drive media=cdrom,index=2,file="${iso}") iso="" elif [ -n "${iso}" ] && [ "${guest_os}" == "reactos" ]; then # https://reactos.org/wiki/QEMU # shellcheck disable=SC2054 args+=(-boot order=d -drive if=ide,index=2,media=cdrom,file="${iso}") iso="" elif [ -n "${iso}" ] && [ "${guest_os}" == "windows" ] && [ -e "${VMDIR}/unattended.iso" ]; then # Attach the unattended configuration to Windows guests when booting from ISO # shellcheck disable=SC2054 args+=(-drive media=cdrom,index=2,file="${VMDIR}/unattended.iso") fi if [ -n "${floppy}" ]; then # shellcheck disable=SC2054 args+=(-drive if=floppy,format=raw,file="${floppy}") fi if [ -n "${iso}" ]; then # shellcheck disable=SC2054 args+=(-drive media=cdrom,index=0,file="${iso}") fi if [ -n "${fixed_iso}" ]; then # shellcheck disable=SC2054 args+=(-drive media=cdrom,index=1,file="${fixed_iso}") fi if [ "${guest_os}" == "macos" ]; then # shellcheck disable=SC2054 args+=(-device ahci,id=ahci -device ide-hd,bus=ahci.0,drive=BootLoader,bootindex=0 -drive id=BootLoader,if=none,format=qcow2,file="${MAC_BOOTLOADER}") if [ -n "${img}" ]; then # shellcheck disable=SC2054 args+=(-device ide-hd,bus=ahci.1,drive=RecoveryImage -drive id=RecoveryImage,if=none,format=raw,file="${img}") fi # shellcheck disable=SC2054,SC2206 args+=(-device ${MAC_DISK_DEV},drive=SystemDisk -drive id=SystemDisk,if=none,format=qcow2,file="${disk_img}" ${STATUS_QUO}) elif [ "${guest_os}" == "kolibrios" ]; then # shellcheck disable=SC2054,SC2206 args+=(-device ahci,id=ahci -device ide-hd,bus=ahci.0,drive=SystemDisk -drive id=SystemDisk,if=none,format=qcow2,file="${disk_img}" ${STATUS_QUO}) elif [ "${guest_os}" == "batocera" ] ; then # shellcheck disable=SC2054,SC2206 args+=(-device virtio-blk-pci,drive=BootDisk -drive id=BootDisk,if=none,format=raw,file="${img}" -device virtio-blk-pci,drive=SystemDisk -drive id=SystemDisk,if=none,format=qcow2,file="${disk_img}" ${STATUS_QUO}) elif [ "${guest_os}" == "reactos" ]; then # https://reactos.org/wiki/QEMU # shellcheck disable=SC2054,SC2206 args+=(-drive if=ide,index=0,media=disk,file="${disk_img}") elif [ "${guest_os}" == "windows-server" ]; then # shellcheck disable=SC2054,SC2206 args+=(-device ide-hd,drive=SystemDisk -drive id=SystemDisk,if=none,format=qcow2,file="${disk_img}" ${STATUS_QUO}) else # shellcheck disable=SC2054,SC2206 args+=(-device virtio-blk-pci,drive=SystemDisk -drive id=SystemDisk,if=none,format=${disk_format},file="${disk_img}" ${STATUS_QUO}) fi # https://wiki.qemu.org/Documentation/9psetup # https://askubuntu.com/questions/772784/9p-libvirt-qemu-share-modes if [ "${guest_os}" != "windows" ] || [ "${guest_os}" == "windows-server" ] && [ -n "${PUBLIC}" ]; then # shellcheck disable=SC2054 args+=(-fsdev local,id=fsdev0,path="${PUBLIC}",security_model=mapped-xattr -device virtio-9p-pci,fsdev=fsdev0,mount_tag="${PUBLIC_TAG}") fi if [ -n "${USB_PASSTHROUGH}" ]; then # shellcheck disable=SC2054,SC2206 args+=(-device ${USB_HOST_PASSTHROUGH_CONTROLLER},id=hostpass ${USB_PASSTHROUGH}) fi if [ "${tpm}" == "on" ] && [ -S "${VMDIR}/${VMNAME}.swtpm-sock" ]; then # shellcheck disable=SC2054 args+=(-chardev socket,id=chrtpm,path="${VMDIR}/${VMNAME}.swtpm-sock" -tpmdev emulator,id=tpm0,chardev=chrtpm -device tpm-tis,tpmdev=tpm0) fi if [ -n "${monitor_telnet_port}" ] && ! is_numeric "${monitor_telnet_port}"; then echo "ERROR: telnet port must be a number!" exit 1 fi if [ "${monitor}" == "none" ]; then args+=(-monitor none) echo " - Monitor: (off)" elif [ "${monitor}" == "telnet" ]; then # Find a free port to expose monitor-telnet to the guest TEMP_PORT="$(get_port "${monitor_telnet_port}" 9)" if [ -z "${TEMP_PORT}" ]; then echo " - Monitor: All Monitor-Telnet ports have been exhausted." else monitor_telnet_port="${TEMP_PORT}" # shellcheck disable=SC2054 args+=(-monitor telnet:"${monitor_telnet_host}:${monitor_telnet_port}",server,nowait) echo " - Monitor: On host: telnet ${monitor_telnet_host} ${monitor_telnet_port}" echo "monitor-telnet,${monitor_telnet_port},${monitor_telnet_host}" >> "${VMDIR}/${VMNAME}.ports" fi elif [ "${monitor}" == "socket" ]; then # shellcheck disable=SC2054,SC2206 args+=(-monitor unix:${SOCKET_MONITOR},server,nowait) echo " - Monitor: On host: nc -U \"${SOCKET_MONITOR}\"" echo " or : socat -,echo=0,icanon=0 unix-connect:${SOCKET_MONITOR}" else echo "ERROR! \"${monitor}\" is an unknown monitor option." exit 1 fi if [ -n "${serial_telnet_port}" ] && ! is_numeric "${serial_telnet_port}"; then echo "ERROR: serial port must be a number!" exit 1 fi if [ "${serial}" == "none" ]; then args+=(-serial none) echo " - Serial: (off)" elif [ "${serial}" == "telnet" ]; then # Find a free port to expose serial-telnet to the guest TEMP_PORT="$(get_port "${serial_telnet_port}" 9)" if [ -z "${TEMP_PORT}" ]; then echo " - Serial: All Serial Telnet ports have been exhausted." else serial_telnet_port="${TEMP_PORT}" # shellcheck disable=SC2054,SC2206 args+=(-serial telnet:${serial_telnet_host}:${serial_telnet_port},server,nowait) echo " - Serial: On host: telnet ${serial_telnet_host} ${serial_telnet_port}" echo "serial-telnet,${serial_telnet_port},${serial_telnet_host}" >> "${VMDIR}/${VMNAME}.ports" fi elif [ "${serial}" == "socket" ]; then # shellcheck disable=SC2054,SC2206 args+=(-serial unix:${SOCKET_SERIAL},server,nowait) echo " - Serial: On host: nc -U \"${SOCKET_SERIAL}\"" echo " or : socat -,echo=0,icanon=0 unix-connect:${SOCKET_SERIAL}" else echo "ERROR! \"${serial}\" is an unknown serial option." exit 1 fi if [ -n "${extra_args}" ]; then # shellcheck disable=SC2206 args+=(${extra_args}) fi # The OSK parameter contains parenthesis, they need to be escaped in the shell # scripts. The vendor name, Quickemu Project, contains a space. It needs to be # double-quoted. SHELL_ARGS="${args[*]}" SHELL_ARGS="${SHELL_ARGS//\(/\\(}" SHELL_ARGS="${SHELL_ARGS//)/\\)}" SHELL_ARGS="${SHELL_ARGS//Quickemu Project/\"Quickemu Project\"}" if [ -z "${VM_PID}" ]; then # Enable grab-on-hover for SDL: https://github.com/quickemu-project/quickemu/issues/541 case "${display}" in sdl) export SDL_MOUSE_FOCUS_CLICKTHROUGH=1;; esac echo "${QEMU}" "${SHELL_ARGS}" "2>/dev/null" >> "${VMDIR}/${VMNAME}.sh" sed -i -e 's/ -/ \\\n -/g' "${VMDIR}/${VMNAME}.sh" ${QEMU} "${args[@]}" &> "${VMDIR}/${VMNAME}.log" & local VM_PID=$! sleep 0.25 if kill -0 "${VM_PID}" 2>/dev/null; then echo " - Process: Started ${VM} as ${VMNAME} (${VM_PID})" else echo " - Process: ERROR! Failed to start ${VM} as ${VMNAME}" rm -f "${VMDIR}/${VMNAME}.pid" echo && cat "${VMDIR}/${VMNAME}.log" exit 1 fi fi } function start_viewer { errno=0 if [ "${viewer}" != "none" ]; then # If output is 'none' then SPICE was requested. if [ "${display}" == "spice" ]; then if [ "${viewer}" == "remote-viewer" ]; then # show via viewer: remote-viewer if [ -n "${PUBLIC}" ]; then echo " - Viewer: ${viewer} --title \"${VMNAME}\" --spice-shared-dir \"${PUBLIC}\" ${FULLSCREEN} \"spice://localhost:${spice_port}\" >/dev/null 2>&1 &" ${viewer} --title "${VMNAME}" --spice-shared-dir "${PUBLIC}" ${FULLSCREEN} "spice://localhost:${spice_port}" >/dev/null 2>&1 & errno=$? else echo " - Viewer: ${viewer} --title \"${VMNAME}\" ${FULLSCREEN} \"spice://localhost:${spice_port}\" >/dev/null 2>&1 &" ${viewer} --title "${VMNAME}" ${FULLSCREEN} "spice://localhost:${spice_port}" >/dev/null 2>&1 & errno=$? fi elif [ "${viewer}" == "spicy" ]; then # show via viewer: spicy if [ -n "${PUBLIC}" ]; then echo " - Viewer: ${viewer} --title \"${VMNAME}\" --port \"${spice_port}\" --spice-shared-dir \"${PUBLIC}\" \"${FULLSCREEN}\" >/dev/null 2>&1 &" ${viewer} --title "${VMNAME}" --port "${spice_port}" --spice-shared-dir "${PUBLIC}" "${FULLSCREEN}" >/dev/null 2>&1 & errno=$? else echo " - Viewer: ${viewer} --title \"${VMNAME}\" --port \"${spice_port}\" \"${FULLSCREEN}\" >/dev/null 2>&1 &" ${viewer} --title "${VMNAME}" --port "${spice_port}" "${FULLSCREEN}" >/dev/null 2>&1 & errno=$? fi fi if [ ${errno} -ne 0 ]; then echo "WARNING! Could not start viewer (${viewer}) Err: ${errno}" fi fi fi } function shortcut_create { local dirname="${HOME}/.local/share/applications" local filename="${HOME}/.local/share/applications/${VMNAME}.desktop" echo "Creating ${VMNAME} desktop shortcut file" if [ ! -d "${dirname}" ]; then mkdir -p "${dirname}" fi cat << EOF > "${filename}" [Desktop Entry] Version=1.0 Type=Application Terminal=false Exec=${0} --vm ${VM} Path=${VMPATH} Name=${VMNAME} Icon=/usr/share/icons/hicolor/scalable/apps/qemu.svg EOF echo " - ${filename} created." } function usage() { echo " _ _" echo " __ _ _ _(_) ___| | _____ _ __ ___ _ _" echo " / _' | | | | |/ __| |/ / _ \ '_ ' _ \| | | |" echo "| (_| | |_| | | (__| < __/ | | | | | |_| |" echo " \__, |\__,_|_|\___|_|\_\___|_| |_| |_|\__,_|" echo " |_| v${VERSION}, using qemu ${QEMU_VER_LONG}" echo "--------------------------------------------------------------------------------" echo " Project - https://github.com/quickemu-project/quickemu" echo " Discord - https://wimpysworld.io/discord" echo "--------------------------------------------------------------------------------" echo echo "Usage" echo " ${LAUNCHER} --vm ubuntu.conf " echo echo "Arguments" echo " --access : Enable remote spice access support. 'local' (default), 'remote', 'clientipaddress'" echo " --braille : Enable braille support. Requires SDL." echo " --delete-disk : Delete the disk image and EFI variables" echo " --delete-vm : Delete the entire VM and its configuration" echo " --display : Select display backend. 'sdl' (default), 'gtk', 'none', 'spice' or 'spice-app'" echo " --fullscreen : Starts VM in full screen mode (Ctl+Alt+f to exit)" echo " --ignore-msrs-always : Configure KVM to always ignore unhandled machine-specific registers" echo " --kill : Kill the VM process if it is running" echo " --offline : Override all network settings and start the VM offline" echo " --shortcut : Create a desktop shortcut" echo " --snapshot apply : Apply/restore a snapshot." echo " --snapshot create : Create a snapshot." echo " --snapshot delete : Delete a snapshot." echo " --snapshot info : Show disk/snapshot info." echo " --status-quo : Do not commit any changes to disk/snapshot." echo " --viewer : Choose an alternative viewer. @Options: 'spicy' (default), 'remote-viewer', 'none'" echo " --width : Set VM screen width; requires '--height'" echo " --height : Set VM screen height; requires '--width'" echo " --ssh-port : Set SSH port manually" echo " --spice-port : Set SPICE port manually" echo " --public-dir : Expose share directory. @Options: '' (default: xdg-user-dir PUBLICSHARE), '', 'none'" echo " --monitor : Set monitor connection type. @Options: 'socket' (default), 'telnet', 'none'" echo " --monitor-telnet-host : Set telnet host for monitor. (default: 'localhost')" echo " --monitor-telnet-port : Set telnet port for monitor. (default: '4440')" echo " --monitor-cmd : Send command to monitor if available. (Example: system_powerdown)" echo " --serial : Set serial connection type. @Options: 'socket' (default), 'telnet', 'none'" echo " --serial-telnet-host : Set telnet host for serial. (default: 'localhost')" echo " --serial-telnet-port : Set telnet port for serial. (default: '6660')" echo " --keyboard : Set keyboard. @Options: 'usb' (default), 'ps2', 'virtio'" echo " --keyboard_layout : Set keyboard layout: 'en-us' (default)" echo " --mouse : Set mouse. @Options: 'tablet' (default), 'ps2', 'usb', 'virtio'" echo " --usb-controller : Set usb-controller. @Options: 'ehci' (default), 'xhci', 'none'" echo " --sound-card : Set sound card. @Options: 'intel-hda' (default), 'ac97', 'es1370', 'sb16', 'usb-audio', 'none'" echo " --sound-duplex : Set sound card duplex. @Options: 'hda-micro' (default: speaker/mic), 'hda-duplex' (line-in/line-out), 'hda-output' (output-only)" echo " --extra_args : Pass additional arguments to qemu" echo " --version : Print version" exit 1 } function display_param_check() { if [ "${display}" != "gtk" ] && [ "${display}" != "none" ] && [ "${display}" != "sdl" ] && [ "${display}" != "spice" ] && [ "${display}" != "spice-app" ]; then echo "ERROR! Requested output '${display}' is not recognised." exit 1 fi } function sound_card_param_check() { if [ "${sound_card}" != "ac97" ] && [ "${sound_card}" != "es1370" ] && [ "${sound_card}" != "ich9-intel-hda" ] && [ "${sound_card}" != "intel-hda" ] && [ "${sound_card}" != "sb16" ] && [ "${sound_card}" != "usb-audio" ] && [ "${sound_card}" != "none" ]; then echo "ERROR! Requested sound card '${sound_card}' is not recognised." exit 1 fi # USB audio requires xhci controller if [ "${sound_card}" == "usb-audio" ]; then usb_controller="xhci"; fi #name "hda-duplex", bus HDA, desc "HDA Audio Codec, duplex (line-out, line-in)" #name "hda-micro", bus HDA, desc "HDA Audio Codec, duplex (speaker, microphone)" #name "hda-output", bus HDA, desc "HDA Audio Codec, output-only (line-out)" if [ "${sound_duplex}" != "hda-duplex" ] && [ "${sound_duplex}" != "hda-micro" ] && [ "${sound_duplex}" != "hda-output" ]; then echo "ERROR! Requested sound duplex '${sound_duplex}' is not recognised." exit 1 fi } function viewer_param_check() { if [ "${viewer}" != "none" ] && [ "${viewer}" != "spicy" ] && [ "${viewer}" != "remote-viewer" ]; then echo "ERROR! Requested viewer '${viewer}' is not recognised." exit 1 fi if [ "${viewer}" == "spicy" ] && ! command -v spicy &>/dev/null; then echo "ERROR! Requested 'spicy' as viewer, but 'spicy' is not installed." exit 1 elif [ "${viewer}" == "remote-viewer" ] && ! command -v remote-viewer &>/dev/null; then echo "ERROR! Requested 'remote-viewer' as viewer, but 'remote-viewer' is not installed." exit 1 fi } function parse_ports_from_file { local FILE="${VMDIR}/${VMNAME}.ports" local host_name="" local port_name="" local port_number="" # Loop over each line in the file while IFS= read -r CONF || [ -n "${CONF}" ]; do # parse ports port_name=$(echo "${CONF}" | cut -d',' -f 1) port_number=$(echo "${CONF}" | cut -d',' -f 2) host_name=$(echo "${CONF}" | awk 'FS="," {print $3,"."}') if [ "${port_name}" == "ssh" ]; then ssh_port="${port_number}" elif [ "${port_name}" == "spice" ]; then spice_port="${port_number}" elif [ "${port_name}" == "monitor-telnet" ]; then monitor_telnet_port="${port_number}" monitor_telnet_host="${host_name}" elif [ "${port_name}" == "serial-telnet" ]; then serial_telnet_port="${port_number}" serial_telnet_host="${host_name}" fi done < "${FILE}" } function is_numeric { [[ "$1" =~ ^[0-9]+$ ]] } function monitor_send_cmd { local MSG="${1}" if [ -z "${MSG}" ]; then echo "WARNING! Send to QEMU-Monitor: Message empty!" return 1 fi case "${monitor}" in socket) echo -e " - Sending: via socket ${MSG}" echo -e "${MSG}" | socat -,shut-down unix-connect:"${SOCKET_MONITOR}" > /dev/null 2>&1;; telnet) echo -e " - Sending: via telnet ${MSG}" echo -e "${MSG}" | socat - tcp:"${monitor_telnet_host}":"${monitor_telnet_port}" > /dev/null 2>&1;; *) echo "WARNING! No qemu-monitor channel available - Couldn't send message to monitor!" return 1;; esac return 0 } ### MAIN # Lowercase variables are used in the VM config file only boot="efi" cpu_cores="" disk_format="${disk_format:-qcow2}" disk_img="${disk_img:-}" disk_size="${disk_size:-16G}" display="${display:-sdl}" extra_args="${extra_args:-}" fixed_iso="" floppy="" guest_os="linux" img="" iso="" macaddr="" macos_release="" network="" port_forwards=() preallocation="off" ram="" secureboot="off" tpm="off" usb_devices=() viewer="${viewer:-spicy}" width="${width:-}" height="${height:-}" ssh_port="${ssh_port:-}" spice_port="${spice_port:-}" public_dir="" monitor="${monitor:-socket}" monitor_telnet_port="${monitor_telnet_port:-4440}" monitor_telnet_host="${monitor_telnet_host:-localhost}" serial="${serial:-socket}" serial_telnet_port="${serial_telnet_port:-6660}" serial_telnet_host="${serial_telnet_host:-localhost}" # options: ehci (USB2.0), xhci (USB3.0) usb_controller="${usb_controller:-ehci}" keyboard="${keyboard:-usb}" keyboard_layout="${keyboard_layout:-en-us}" mouse="${mouse:-tablet}" sound_card="${sound_card:-intel-hda}" sound_duplex="${sound_duplex:-hda-micro}" ACCESS="" ACTIONS=() BRAILLE="" FULLSCREEN="" MONITOR_CMD="" PUBLIC="" PUBLIC_PERMS="" PUBLIC_TAG="" SNAPSHOT_ACTION="" SNAPSHOT_TAG="" SOCKET_MONITOR="" SOCKET_SERIAL="" STATUS_QUO="" USB_PASSTHROUGH="" VM="" VMDIR="" VMNAME="" VMPATH="" # shellcheck disable=SC2155 readonly LAUNCHER=$(basename "${0}") readonly DISK_MIN_SIZE=$((197632 * 8)) readonly VERSION="4.9.5" # TODO: Make this run the native architecture binary QEMU=$(command -v qemu-system-x86_64) QEMU_IMG=$(command -v qemu-img) if [ ! -e "${QEMU}" ] || [ ! -e "${QEMU_IMG}" ]; then echo "ERROR! QEMU not found. Please make install qemu-system-x86_64 and qemu-img" exit 1 fi QEMU_VER_LONG=$(${QEMU} -version | head -n1 | cut -d' ' -f4 | cut -d'(' -f1) QEMU_VER_SHORT=$(${QEMU} -version | head -n1 | cut -d' ' -f4 | cut -d'(' -f1 | sed 's/\.//g' | cut -c1-2) if [ "${QEMU_VER_SHORT}" -lt 60 ]; then echo "ERROR! Qemu 6.0.0 or newer is required, detected ${QEMU_VER_LONG}." exit 1 fi # Take command line arguments if [ $# -lt 1 ]; then usage else while [ $# -gt 0 ]; do case "${1}" in -access|--access) ACCESS="${2}" shift 2;; -braille|--braille) BRAILLE="on" shift;; -delete|--delete|-delete-disk|--delete-disk) ACTIONS+=(delete_disk) shift;; -delete-vm|--delete-vm) ACTIONS+=(delete_vm) shift;; -display|--display) display="${2}" display_param_check shift 2;; -fullscreen|--fullscreen|-full-screen|--full-screen) FULLSCREEN="--full-screen" shift;; -ignore-msrs-always|--ignore-msrs-always) ignore_msrs_always exit;; -kill|--kill) ACTIONS+=(kill_vm) shift;; -offline|--offline) network="none" shift;; -snapshot|--snapshot) if [ -z "${2}" ]; then echo "ERROR! '--snapshot' needs an action to perform." exit 1 fi SNAPSHOT_ACTION="${2}" if [ -z "${3}" ] && [ "${SNAPSHOT_ACTION}" != "info" ]; then echo "ERROR! '--snapshot ${SNAPSHOT_ACTION}' needs a tag." exit 1 fi SNAPSHOT_TAG="${3}" if [ "${SNAPSHOT_ACTION}" == "info" ]; then shift 2 else shift 3 fi;; -status-quo|--status-quo) STATUS_QUO="-snapshot" shift;; -shortcut|--shortcut) ACTIONS+=(shortcut_create) shift;; -vm|--vm) VM="${2}" shift 2;; -viewer|--viewer) viewer="${2}" shift 2;; -width|--width) width="${2}" shift 2;; -height|--height) height="${2}" shift 2;; -ssh-port|--ssh-port) ssh_port="${2}" shift 2;; -spice-port|--spice-port) spice_port="${2}" shift 2;; -public-dir|--public-dir) PUBLIC="${2}" shift 2;; -monitor|--monitor) monitor="${2}" shift 2;; -monitor-cmd|--monitor-cmd) MONITOR_CMD="${2}" shift 2;; -monitor-telnet-host|--monitor-telnet-host) monitor_telnet_host="${2}" shift 2;; -monitor-telnet-port|--monitor-telnet-port) monitor_telnet_port="${2}" shift 2;; -serial|--serial) serial="${2}" shift 2;; -serial-telnet-host|--serial-telnet-host) serial_telnet_host="${2}" shift 2;; -serial-telnet-port|--serial-telnet-port) serial_telnet_port="${2}" shift 2;; -keyboard|--keyboard) keyboard="${2}" shift 2;; -keyboard_layout|--keyboard_layout) keyboard_layout="${2}" shift 2;; -mouse|--mouse) mouse="${2}" shift 2;; -usb-controller|--usb-controller) usb_controller="${2}" shift 2;; -extra_args|--extra_args) extra_args+="${2}" shift 2;; -sound-card|--sound-card) sound_card="${2}" shift 2;; -sound-duplex|--sound-duplex) sound_duplex="${2}" shift 2;; -version|--version) echo "${VERSION}" exit;; -h|--h|-help|--help) usage;; *) echo "ERROR! \"${1}\" is not a supported parameter." usage;; esac done fi if [ -n "${VM}" ] && [ -e "${VM}" ]; then # shellcheck source=/dev/null source "${VM}" VMDIR=$(dirname "${disk_img}") # directory the VM disk and state files are stored VMNAME=$(basename "${VM}" .conf) # name of the VM VMPATH=$(realpath "$(dirname "${VM}")") # path to the top-level VM directory SOCKET_MONITOR="${VMDIR}/${VMNAME}-monitor.socket" SOCKET_SERIAL="${VMDIR}/${VMNAME}-serial.socket" # if not disk_img is configured, do the right thing. if [ -z "${disk_img}" ]; then disk_img="${VMDIR}/disk.${disk_format}" fi # Iterate over any actions and exit. if [ ${#ACTIONS[@]} -ge 1 ]; then for ACTION in "${ACTIONS[@]}"; do ${ACTION} done exit fi if [ -n "${SNAPSHOT_ACTION}" ]; then case ${SNAPSHOT_ACTION} in apply) snapshot_apply "${SNAPSHOT_TAG}" snapshot_info exit;; create) snapshot_create "${SNAPSHOT_TAG}" snapshot_info exit;; delete) snapshot_delete "${SNAPSHOT_TAG}" snapshot_info exit;; info) echo "Snapshot information ${disk_img}" snapshot_info exit;; *) echo "ERROR! \"${SNAPSHOT_ACTION}\" is not a supported snapshot action." usage;; esac fi # Braille support requires SDL. Override $display if braille was requested. if [ -n "${BRAILLE}" ]; then display="sdl" fi display_param_check sound_card_param_check viewer_param_check # Set the default 3D acceleration. if [ -z "${gl}" ]; then if command -v glxinfo &>/dev/null; then GLSL_VER=$(glxinfo | grep "OpenGL ES GLSL" | awk '{print $NF}') case ${GLSL_VER} in 1*|2*) gl="off";; *) gl="on";; esac else gl="on" fi fi if [ -z "${PUBLIC}" ]; then PUBLIC="${public_dir}" fi if [ "${PUBLIC}" == "none" ]; then PUBLIC="" else # PUBLICSHARE is the only directory exposed to guest VMs for file # sharing via 9P, spice-webdavd and Samba. This path is not configurable. if [ -z "${PUBLIC}" ]; then if command -v xdg-user-dir &>/dev/null; then PUBLIC=$(xdg-user-dir PUBLICSHARE) fi fi if [ ! -d "${PUBLIC}" ]; then echo "ERROR! Public directory: '${PUBLIC}' doesn't exist!" exit 1 fi PUBLIC_TAG="Public-${USER,,}" # shellcheck disable=SC2012 PUBLIC_PERMS=$(ls -ld "${PUBLIC}" | cut -d' ' -f1) fi if [ -n "${ssh_port}" ] && ! is_numeric "${ssh_port}"; then echo "ERROR: ssh_port must be a number!" exit 1 fi if [ -n "${spice_port}" ] && ! is_numeric "${spice_port}"; then echo "ERROR: spice_port must be a number!" exit 1 fi # Check if vm is already run VM_PID="" if [ -r "${VMDIR}/${VMNAME}.pid" ]; then VM_PID=$(head -1 "${VMDIR}/${VMNAME}.pid") if ! kill -0 "${VM_PID}" > /dev/null 2>&1; then # VM is not running, cleaning up. VM_PID="" rm -f "${VMDIR}/${VMNAME}.pid" fi fi if [ "${tpm}" == "on" ]; then SWTPM=$(command -v swtpm) if [ ! -e "${SWTPM}" ]; then echo "ERROR! TPM is enabled, but swtpm was not found." exit 1 fi fi else echo "ERROR! Virtual machine configuration not found." usage fi if [ -z "${VM_PID}" ]; then #TODO: double quote the args array to prevent word splitting and this can be removed # Fix failing to start VM with spaces in the path # https://github.com/quickemu-project/quickemu/pull/875 if [ ! -f "${disk_img}" ]; then pushd "${VMPATH}" || exit fi vm_boot # If the VM being started is an uninstalled Windows VM then auto-skip the press-any key prompt. if [ -n "${iso}" ] && [ "${guest_os}" == "windows" ]; then sleep 3.5 monitor_send_cmd "sendkey ret" fi if [ -n "${iso}" ] && [ "${guest_os}" == "windows-server" ]; then sleep 7 monitor_send_cmd "sendkey ret" fi start_viewer else echo "${VMNAME}" echo " - Process: Already running ${VM} as ${VMNAME} (${VM_PID})" parse_ports_from_file start_viewer fi if [ -n "${MONITOR_CMD}" ]; then monitor_send_cmd "${MONITOR_CMD}" fi # vim:tabstop=2:shiftwidth=2:expandtab