DistroHopper/quickemu

1313 lines
43 KiB
Plaintext
Raw Normal View History

2020-03-15 23:13:25 +00:00
#!/usr/bin/env bash
export LC_ALL=C
2020-03-15 23:13:25 +00:00
2021-10-21 16:20:26 +01:00
if ((BASH_VERSINFO[0] < 4))
then
echo "Sorry, you need bash 4.0 or newer to run this script."
exit 1
fi
2021-10-21 00:04:07 +01:00
function ignore_msrs_always() {
# Make sure the host has /etc/modprobe.d
if [ -d /etc/modprobe.d ]; then
2021-10-21 00:21:13 +01:00
# Skip if ignore_msrs is already enabled, assumes initramfs has been rebuilt
2021-10-21 00:04:07 +01:00
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
2021-10-20 23:26:55 +01:00
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 permenant 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 ${VM} desktop shortcut"
fi
}
function delete_disk() {
2020-03-20 18:12:13 +00:00
if [ -e "${disk_img}" ]; then
2020-03-15 23:13:25 +00:00
rm "${disk_img}"
# 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
2021-10-25 14:31:45 +01:00
rm "${VMDIR}/${VMNAME}-vars.fd" >/dev/null 2>&1
rm "${VMPATH}/${VMDIR}/${VMNAME}-vars.fd" > /dev/null 2>&1
echo "SUCCESS! Deleted ${disk_img}"
delete_shortcut
2020-03-20 18:12:13 +00:00
else
echo "NOTE! ${disk_img} not found. Doing nothing."
2020-03-15 23:13:25 +00:00
fi
}
2021-10-25 15:42:33 +01:00
function delete_vm() {
if [ -d "${VMDIR}" ]; then
rm -rf "${VMDIR}"
rm "${VM}"
echo "SUCCESS! Deleted ${VM} and ${VMDIR}"
delete_shortcut
else
echo "NOTE! ${VMDIR} not found. Doing nothing."
fi
2020-03-15 23:13:25 +00:00
}
2020-03-20 18:17:53 +00:00
function snapshot_apply() {
local TAG="${1}"
if [ -z "${TAG}" ]; then
2020-03-20 18:17:53 +00:00
echo "ERROR! No snapshot tag provided."
exit
fi
if [ -e "${disk_img}" ]; then
if ${QEMU_IMG} snapshot -q -a "${TAG}" "${disk_img}"; then
echo "SUCCESS! Applied snapshot ${TAG} to ${disk_img}"
2020-03-20 18:17:53 +00:00
else
echo "ERROR! Failed to apply snapshot ${TAG} to ${disk_img}"
2020-03-20 18:17:53 +00:00
fi
else
echo "NOTE! ${disk_img} not found. Doing nothing."
2020-03-15 23:13:25 +00:00
fi
}
2020-03-20 18:17:53 +00:00
function snapshot_create() {
local TAG="${1}"
if [ -z "${TAG}" ]; then
2020-03-20 18:17:53 +00:00
echo "ERROR! No snapshot tag provided."
exit
2020-03-15 23:13:25 +00:00
fi
2020-03-20 18:17:53 +00:00
if [ -e "${disk_img}" ]; then
if ${QEMU_IMG} snapshot -q -c "${TAG}" "${disk_img}"; then
echo "SUCCESS! Created snapshot ${TAG} of ${disk_img}"
2020-03-20 18:17:53 +00:00
else
echo "ERROR! Failed to create snapshot ${TAG} of ${disk_img}"
2020-03-20 18:17:53 +00:00
fi
2020-03-15 23:13:25 +00:00
else
2020-03-20 18:17:53 +00:00
echo "NOTE! ${disk_img} not found. Doing nothing."
fi
}
function snapshot_delete() {
local TAG="${1}"
if [ -z "${TAG}" ]; then
2020-03-20 18:17:53 +00:00
echo "ERROR! No snapshot tag provided."
exit
fi
if [ -e "${disk_img}" ]; then
if ${QEMU_IMG} snapshot -q -d "${TAG}" "${disk_img}"; then
echo "SUCCESS! Deleted snapshot ${TAG} of ${disk_img}"
2020-03-20 18:17:53 +00:00
else
echo "ERROR! Failed to delete snapshot ${TAG} of ${disk_img}"
2020-03-20 18:17:53 +00:00
fi
else
echo "NOTE! ${disk_img} not found. Doing nothing."
fi
}
function snapshot_info() {
if [ -e "${disk_img}" ]; then
${QEMU_IMG} info "${disk_img}"
2020-03-15 23:13:25 +00:00
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
2021-12-03 11:33:41 +00:00
# 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
}
2021-09-06 15:30:01 +01:00
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 [ -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
2021-09-28 02:14:30 +01:00
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
}
2021-10-04 23:46:43 +01:00
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
}
2020-03-15 23:13:25 +00:00
function vm_boot() {
local AUDIO_DEV=""
2021-09-28 15:27:02 +01:00
local BALLOON="-device virtio-balloon"
local BOOT_STATUS=""
local CPU=""
local DISK_USED=""
local DISPLAY_DEVICE=""
local DISPLAY_RENDER=""
local 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 GL="on"
local GUEST_TWEAKS=""
local KERNEL_NAME="Unknown"
local KERNEL_NODE=""
local KERNEL_VER="?"
local LSB_DESCRIPTION="Unknown OS"
local MACHINE_TYPE="q35"
2021-10-05 00:17:51 +01:00
local MAC_BOOTLOADER=""
local MAC_MISSING=""
2021-10-04 22:15:15 +01:00
local MAC_DISK_DEV="ide-hd,bus=ahci.2"
local MOUSE="usb-tablet"
local NET_DEVICE="virtio-net"
local OSK=""
local SMM="off"
local USB_HOST_PASSTHROUGH_CONTROLLER="qemu-xhci"
local VIDEO=""
KERNEL_NAME=$(uname --kernel-name)
KERNEL_NODE="($(uname --nodename))"
KERNEL_VER=$(uname --kernel-release | cut -d'.' -f1-2)
if command -v lsb_release &>/dev/null; then
LSB_DESCRIPTION=$(lsb_release --description --short)
elif [ -e /etc/os-release ]; then
LSB_DESCRIPTION=$(grep PRETTY_NAME /etc/os-release | cut -d'"' -f2)
fi
2021-10-19 02:17:23 +01:00
echo "Quickemu ${VERSION} using ${QEMU} v${QEMU_VER_LONG}"
echo " - Host: ${LSB_DESCRIPTION} running ${KERNEL_NAME} ${KERNEL_VER} ${KERNEL_NODE}"
HOST_CPU_CORES=$(nproc --all)
HOST_CPU_MODEL=$(lscpu | grep '^Model name:' | cut -d':' -f2 | sed 's/ //g')
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')
2021-09-27 22:46:26 +01:00
2021-10-04 23:47:37 +01:00
# A CPU with Intel VT-x / AMD SVM support is required
if [ "${HOST_CPU_VENDOR}" == "GenuineIntel" ]; then
2021-10-04 23:47:37 +01:00
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
# 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=""
RAM_HOST=$(free --mega -h | grep Mem | cut -d':' -f2 | cut -d'G' -f1 | sed 's/ //g')
#Round up - https://github.com/wimpysworld/quickemu/issues/11
RAM_HOST=$(printf '%.*f\n' 0 "${RAM_HOST}")
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"
2021-09-27 22:46:26 +01:00
2021-10-27 12:18:10 +01:00
if [ "${RAM_VM//G/}" -lt 4 ]; then
if [ "${guest_os}" == "macos" ] || [ "${guest_os}" == "windows" ]; then
echo "ERROR! You have insufficient RAM to run ${guest_os} in a VM"
exit 1
fi
fi
# Force to lowercase.
boot=${boot,,}
guest_os=${guest_os,,}
# 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"
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.
2021-10-19 23:35:58 +01:00
# https://bugzilla.redhat.com/show_bug.cgi?id=1929357#c5
case ${secureboot} in
on)
2022-01-31 03:19:45 +00:00
if [ -e "/usr/share/OVMF/OVMF_CODE_4M.secboot.fd" ]; then
EFI_CODE="/usr/share/OVMF/OVMF_CODE_4M.secboot.fd"
efi_vars "/usr/share/OVMF/OVMF_VARS_4M.fd" "${EFI_VARS}"
elif [ -e "/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd" ]; then
2021-10-25 09:50:56 +01:00
EFI_CODE="/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd"
efi_vars "/usr/share/edk2/ovmf/OVMF_VARS.fd" "${EFI_VARS}"
elif [ -e "/usr/share/OVMF/x64/OVMF_CODE.secboot.fd" ]; then
2021-10-25 09:50:56 +01:00
EFI_CODE="/usr/share/OVMF/x64/OVMF_CODE.secboot.fd"
efi_vars "/usr/share/OVMF/x64/OVMF_VARS.fd" "${EFI_VARS}"
2022-01-31 03:19:45 +00:00
elif [ -e "/usr/share/edk2-ovmf/OVMF_CODE.secboot.fd" ]; then
EFI_CODE="/usr/share/edk2-ovmf/OVMF_CODE.secboot.fd"
efi_vars "/usr/share/edk2-ovmf/OVMF_VARS.fd" "${EFI_VARS}"
2022-01-31 03:19:45 +00:00
elif [ -e "/usr/share/qemu/ovmf-x86_64-smm-ms-code.bin" ]; then
EFI_CODE="/usr/share/qemu/ovmf-x86_64-smm-ms-code.bin"
efi_vars "/usr/share/qemu/ovmf-x86_64-smm-ms-vars.bin" "${EFI_VARS}"
2022-01-16 03:38:45 +00:00
elif [ -e "/usr/share/qemu/edk2-x86_64-secure-code.fd" ]; then
EFI_CODE="/usr/share/qemu/edk2-x86_64-secure-code.fd"
efi_vars "/usr/share/qemu/edk2-x86_64-code.fd" "${EFI_VARS}"
else
echo "ERROR! SecureBoot was requested but no SecureBoot capable firmware was found."
echo " Please install OVMF firmware."
exit 1
fi
;;
*)
if [ -e "/usr/share/OVMF/OVMF_CODE_4M.fd" ]; then
EFI_CODE="/usr/share/OVMF/OVMF_CODE_4M.fd"
efi_vars "/usr/share/OVMF/OVMF_VARS_4M.fd" "${EFI_VARS}"
elif [ -e "/usr/share/edk2/ovmf/OVMF_CODE.fd" ]; then
EFI_CODE="/usr/share/edk2/ovmf/OVMF_CODE.fd"
efi_vars "/usr/share/edk2/ovmf/OVMF_VARS.fd" "${EFI_VARS}"
elif [ -e "/usr/share/OVMF/OVMF_CODE.fd" ]; then
EFI_CODE="/usr/share/OVMF/OVMF_CODE.fd"
efi_vars "/usr/share/OVMF/OVMF_VARS.fd" "${EFI_VARS}"
elif [ -e "/usr/share/OVMF/x64/OVMF_CODE.fd" ]; then
EFI_CODE="/usr/share/OVMF/x64/OVMF_CODE.fd"
efi_vars "/usr/share/OVMF/x64/OVMF_VARS.fd" "${EFI_VARS}"
elif [ -e "/usr/share/edk2-ovmf/OVMF_CODE.fd" ]; then
EFI_CODE="/usr/share/edk2-ovmf/OVMF_CODE.fd"
efi_vars "/usr/share/edk2-ovmf/OVMF_VARS.fd" "${EFI_VARS}"
2021-12-20 21:51:38 +00:00
elif [ -e "/usr/share/qemu/ovmf-x86_64-4m-code.bin" ]; then
EFI_CODE="/usr/share/qemu/ovmf-x86_64-4m-code.bin"
efi_vars "/usr/share/qemu/ovmf-x86_64-4m-vars.bin" "${EFI_VARS}"
2022-01-16 03:38:45 +00:00
elif [ -e "/usr/share/qemu/edk2-x86_64-code.fd" ]; then
2022-01-31 02:19:19 +00:00
EFI_CODE="/usr/share/qemu/edk2-x86_64-code.fd"
efi_vars "/usr/share/qemu/edk2-x86_64-code.fd" "${EFI_VARS}"
else
echo "ERROR! EFI boot requested but no EFI firmware found."
echo " Please install OVMF firmware."
exit 1
fi
;;
esac
# 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.
2021-10-19 23:44:30 +01:00
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
*bsd|haiku|linux)
2021-10-05 23:17:17 +01:00
CPU="-cpu host,kvm=on"
if [ "${HOST_CPU_VENDOR}" == "AuthenticAMD" ]; then
CPU="${CPU},topoext"
fi
if [ "${guest_os}" == "freebsd" ]; then
MOUSE="usb-mouse"
elif [ "${guest_os}" == "haiku" ]; then
MACHINE_TYPE="pc"
NET_DEVICE="rtl8139"
fi
2021-10-09 16:26:06 +01:00
if [ -z "${disk_size}" ]; then
disk_size="16G"
fi
;;
kolibrios)
CPU="-cpu qemu32,kvm=on"
if [ "${HOST_CPU_VENDOR}" == "AuthenticAMD" ]; then
CPU="${CPU},topoext"
fi
MACHINE_TYPE="pc"
NET_DEVICE="rtl8139"
if [ -z "${disk_size}" ]; then
disk_size="256M"
fi
;;
2020-04-04 12:54:30 +01:00
macos)
2021-09-27 22:52:56 +01:00
#https://www.nicksherlock.com/2020/06/installing-macos-big-sur-on-proxmox/
2021-10-04 23:47:37 +01:00
# A CPU with SSE4.1 support is required for >= macOS Sierra
if check_cpu_flag sse4_1; then
2021-10-04 23:47:37 +01:00
case ${HOST_CPU_VENDOR} in
GenuineIntel)
2021-10-04 23:47:37 +01:00
CPU="-cpu host,kvm=on,vendor=GenuineIntel,+hypervisor,+invtsc,+kvm_pv_eoi,+kvm_pv_unhalt";;
AuthenticAMD|*)
# Used in past versions: +movbe,+smep,+xgetbv1,+xsavec,+avx2
2021-10-04 23:47:37 +01:00
# Warn on AMD: +fma4,+pcid
CPU="-cpu Penryn,kvm=on,vendor=GenuineIntel,+aes,+avx,+bmi1,+bmi2,+fma,+hypervisor,+invtsc,+kvm_pv_eoi,+kvm_pv_unhalt,+popcnt,+ssse3,+sse4.2,vmware-cpuid-freq=on,+xsave,+xsaveopt,check";;
2021-10-04 23:47:37 +01:00
esac
2021-09-27 22:52:56 +01:00
else
echo "ERROR! macOS requires a CPU with SSE 4.1 support."
2021-10-04 23:47:37 +01:00
exit 1
2021-09-27 22:52:56 +01:00
fi
2021-12-07 11:51:01 +00:00
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="-no-hpet -global kvm-pit.lost_tick_policy=discard -global ICH9-LPC.disable_s3=1 -device isa-applesmc,osk=${OSK}"
# Tune Qemu optimisations based on the macOS release, or fallback to lowest
2021-10-08 12:17:20 +01:00
# common supported options if none is specified.
2021-10-04 22:15:15 +01:00
# * 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
2021-10-05 00:02:21 +01:00
catalina)
BALLOON=""
MAC_DISK_DEV="virtio-blk-pci"
NET_DEVICE="vmxnet3"
USB_HOST_PASSTHROUGH_CONTROLLER="usb-ehci"
;;
2021-10-27 11:56:56 +01:00
big-sur|monterey)
2021-10-05 00:02:21 +01:00
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"
2021-10-05 00:02:21 +01:00
;;
*)
# Backwards compatibility if no macos_release is specified.
# Also safe catch all for High Sierra and Mojave
BALLOON=""
MAC_DISK_DEV="ide-hd,bus=ahci.2"
NET_DEVICE="vmxnet3"
USB_HOST_PASSTHROUGH_CONTROLLER="usb-ehci"
;;
esac
2021-10-09 16:26:06 +01:00
if [ -z "${disk_size}" ]; then
disk_size="96G"
2021-10-09 16:26:06 +01:00
fi
2020-04-04 12:54:30 +01:00
;;
windows)
2021-10-05 23:17:17 +01:00
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"
if [ "${HOST_CPU_VENDOR}" == "AuthenticAMD" ]; then
CPU="${CPU},topoext"
fi
2021-09-28 22:43:46 +01:00
GUEST_TWEAKS="-no-hpet -global kvm-pit.lost_tick_policy=discard"
# 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="${GUEST_TWEAKS} -global ICH9-LPC.disable_s3=1"
2021-10-09 16:26:06 +01:00
if [ -z "${disk_size}" ]; then
disk_size="64G"
fi
SMM="on"
;;
*)
CPU="-cpu host,kvm=on"
NET_DEVICE="rtl8139"
2021-10-09 16:26:06 +01:00
if [ -z "${disk_size}" ]; then
disk_size="32G"
fi
echo "WARNING! Unrecognised guest OS: ${guest_os}"
;;
esac
2021-10-05 00:03:03 +01:00
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
2021-10-05 00:03:03 +01:00
if ! ${QEMU_IMG} create -q -f qcow2 -o lazy_refcounts=on,preallocation="${preallocation}" "${disk_img}" "${disk_size}"; then
echo "ERROR! Failed to create ${disk_img}"
exit 1
fi
2020-04-04 12:54:30 +01:00
if [ -z "${iso}" ] && [ -z "${img}" ]; then
echo "ERROR! You haven't specified a .iso or .img image to boot from."
exit 1
fi
2020-04-04 12:54:30 +01:00
echo " Just created, booting from ${iso}${img}"
DISK_USED="no"
elif [ -e "${disk_img}" ]; 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
2020-03-20 15:31:24 +00:00
else
# 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
2020-03-20 15:31:24 +00:00
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
if [ "${guest_os}" == "macos" ] || [ "${guest_os}" == "windows" ]; then
# Display MSRs alert if the guest is macOS
ignore_msrs_alert
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
case ${OUTPUT} in
2022-02-21 05:23:43 +00:00
spice|spice-app|none) AUDIO_DEV="spice,id=audio0";;
*) AUDIO_DEV="pa,id=audio0,out.mixing-engine=off,out.stream-name=${LAUNCHER}-${VMNAME},in.stream-name=${LAUNCHER}-${VMNAME}";;
esac
# Determine a sane resolution for Linux guests.
if [ "${guest_os}" == "linux" ]; then
local X_RES=1152
local Y_RES=648
if [ "${XDG_SESSION_TYPE}" == "x11" ]; then
local LOWEST_WIDTH=""
if [ -z "${SCREEN}" ]; then
LOWEST_WIDTH=$(xrandr --listmonitors | grep -v Monitors | cut -d' ' -f4 | cut -d'/' -f1 | sort | head -n1)
else
LOWEST_WIDTH=$(xrandr --listmonitors | grep -v Monitors | grep "^ ${SCREEN}:" | cut -d' ' -f4 | cut -d'/' -f1 | head -n1)
fi
if [ "${FULLSCREEN}" ]; then
if [ -z "${SCREEN}" ]; then
X_RES=$(xrandr --listmonitors | grep -v Monitors | cut -d' ' -f4 | cut -d'/' -f1 | sort | head -n1)
Y_RES=$(xrandr --listmonitors | grep -v Monitors | cut -d' ' -f4 | cut -d'/' -f2 | cut -d'x' -f2 | sort | head -n1)
else
X_RES=$(xrandr --listmonitors | grep -v Monitors | grep "^ ${SCREEN}:" | cut -d' ' -f4 | cut -d'/' -f1 | head -n1)
Y_RES=$(xrandr --listmonitors | grep -v Monitors | grep "^ ${SCREEN}:" | cut -d' ' -f4 | cut -d'/' -f2 | cut -d'x' -f2 | head -n1)
fi
elif [ "${LOWEST_WIDTH}" -ge 3840 ]; then
X_RES=3200
Y_RES=1800
elif [ "${LOWEST_WIDTH}" -ge 2560 ]; then
X_RES=2048
Y_RES=1152
elif [ "${LOWEST_WIDTH}" -ge 1920 ]; then
X_RES=1664
Y_RES=936
elif [ "${LOWEST_WIDTH}" -ge 1280 ]; then
X_RES=1152
Y_RES=648
fi
fi
fi
# https://www.kraxel.org/blog/2019/09/display-devices-in-qemu/
if [ "${guest_os}" == "linux" ]; then
case ${OUTPUT} in
none|spice) DISPLAY_DEVICE="qxl-vga";;
*) DISPLAY_DEVICE="virtio-vga";;
esac
elif [ "${guest_os}" == "macos" ]; then
# Displays in System Preferences can be used to select a resolution if:
# - Mojave only offers 4:3 resolutions
# - High Sierra will run at the default 1920x1080 only.
# QXL prevents seamless mouse working with a SPICE client
# - https://github.com/wimpysworld/quickemu/issues/222
DISPLAY_DEVICE="VGA"
elif [ "${guest_os}" == "windows" ]; then
DISPLAY_DEVICE="qxl-vga"
else
DISPLAY_DEVICE="qxl-vga"
fi
2021-10-04 22:19:37 +01:00
echo -n " - Display: ${OUTPUT^^}, ${DISPLAY_DEVICE}"
2021-11-13 15:56:58 +00:00
# Map Quickemu OUTPUT to QEMU -display
case ${OUTPUT} in
gtk)
DISPLAY_RENDER="${OUTPUT},grab-on-hover=on,zoom-to-fit=off"
# GL is not working with GTK and virtio-vga
if [ "${DISPLAY_DEVICE}" == "virtio-vga" ]; then
GL="off"
fi
;;
none|spice)
DISPLAY_RENDER="none";;
*)
DISPLAY_RENDER="${OUTPUT},gl=${GL}";;
esac
if [ "${GL}" == "on" ] && [[ "${DISPLAY_DEVICE}" == *"virtio"* ]]; then
if [ "${QEMU_VER_SHORT}" -ge 61 ]; then
DISPLAY_DEVICE="${DISPLAY_DEVICE}-gl"
else
DISPLAY_DEVICE="${DISPLAY_DEVICE},virgl=on"
fi
2021-10-04 22:19:37 +01:00
echo ", GL (${GL}), VirGL (on)"
else
2021-10-04 22:19:37 +01:00
echo ", GL (${GL}), VirGL (off)"
fi
# Build the video configuration
VIDEO="-device ${DISPLAY_DEVICE}"
# Try and coerce the display resolution for Linux guests only.
if [ "${guest_os}" == "linux" ]; then
VIDEO="${VIDEO},xres=${X_RES},yres=${Y_RES}"
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) VIDEO="${VIDEO},vgamem_mb=64";;
esac
# Add fullscreen options
VIDEO="${VIDEO} ${FULLSCREEN}"
# Set the hostname of the VM
local NET="user,hostname=${VMNAME}"
2021-10-25 13:40:35 +01:00
echo -n "" > "${VMDIR}/${VMNAME}.ports"
2020-03-19 17:29:01 +00:00
# Find a free port to expose ssh to the guest
2021-09-25 13:14:32 +01:00
local SSH_PORT=""
SSH_PORT=$(get_port 22220 9)
if [ -n "${SSH_PORT}" ]; then
2021-10-25 13:40:35 +01:00
echo "ssh,${SSH_PORT}" >> "${VMDIR}/${VMNAME}.ports"
2021-09-25 13:14:32 +01:00
NET="${NET},hostfwd=tcp::${SSH_PORT}-:22"
2021-09-29 04:09:58 +01:00
echo " - ssh: On host: ssh user@localhost -p ${SSH_PORT}"
2020-03-19 17:29:01 +00:00
else
2021-09-29 03:31:14 +01:00
echo " - ssh: All ssh ports have been exhausted."
2020-03-19 17:29:01 +00:00
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}"
done
fi
# Find a free port for spice
local SPICE="disable-ticketing=on"
local SPICE_PORT=""
SPICE_PORT=$(get_port 5930 9)
if [ -z "${SPICE_PORT}" ]; then
2021-09-29 03:31:14 +01:00
echo " - SPICE: All SPICE ports have been exhausted."
if [ "${OUTPUT}" == "none" ] || [ "${OUTPUT}" == "spice" ] || [ "${OUTPUT}" == "spice-app" ]; then
echo " ERROR! Requested SPICE display, but no SPICE ports are free."
exit 1
fi
else
if [ "${OUTPUT}" == "spice-app" ]; then
echo " - SPICE: Enabled"
else
2021-10-25 13:40:35 +01:00
echo "spice,${SPICE_PORT}" >> "${VMDIR}/${VMNAME}.ports"
2021-09-29 04:09:58 +01:00
echo -n " - SPICE: On host: spicy --title \"${VMNAME}\" --port ${SPICE_PORT}"
2021-10-09 15:22:18 +01:00
if [ "${guest_os}" != "macos" ] && [ -n "${PUBLIC}" ]; then
echo -n " --spice-shared-dir ${PUBLIC}"
fi
2021-09-30 17:40:40 +01:00
echo "${FULLSPICY}"
SPICE="${SPICE},port=${SPICE_PORT}"
fi
if [ -n "${PUBLIC}" ]; then
case ${guest_os} in
macos)
# 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/"
;;
*)
echo " - WebDAV: On guest: dav://localhost:9843/";;
esac
fi
fi
2021-09-29 03:30:16 +01:00
if [ "${guest_os}" != "windows" ] && [ -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 👆"
2021-09-29 03:30:16 +01:00
fi
fi
fi
# If smbd is available and ~/Public is present export it to the guest via samba
if [[ -e "/usr/sbin/smbd" && -n ${PUBLIC} ]]; then
2021-10-09 14:34:22 +01:00
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)
2021-10-25 14:31:45 +01:00
echo "${SWTPM} ${tpm_args[*]} &" >> "${VMDIR}/${VMNAME}.sh"
${SWTPM} "${tpm_args[@]}" >> "${VMDIR}/${VMNAME}.log" &
echo " - TPM: ${VMDIR}/${VMNAME}.swtpm-sock (${!})"
2021-10-25 15:57:15 +01:00
sleep 0.25
fi
# Boot the VM
local args=()
2021-09-28 17:44:02 +01:00
# shellcheck disable=SC2054,SC2206,SC2140
2021-10-19 20:49:57 +01:00
args+=(-name ${VMNAME},process=${VMNAME} -pidfile "${VMDIR}/${VMNAME}.pid"
-enable-kvm -machine ${MACHINE_TYPE},smm=${SMM},vmport=off ${GUEST_TWEAKS}
${CPU} ${SMP}
2021-09-28 15:48:18 +01:00
-m ${RAM_VM} ${BALLOON}
-smbios type=2,manufacturer="Quickemu Project",product="Quickemu",version="${VERSION}",serial="0xDEADBEEF",location="quickemu.com",asset="${VMNAME}"
${VIDEO} -display ${DISPLAY_RENDER}
-device usb-ehci,id=input
-device usb-kbd,bus=input.0
-device ${MOUSE},bus=input.0
-audiodev ${AUDIO_DEV}
-device intel-hda -device hda-duplex,audiodev=audio0
2021-09-28 22:44:24 +01:00
-rtc base=localtime,clock=host,driftfix=slew
-spice ${SPICE}
-device virtio-serial-pci
2021-10-27 22:20:47 +01:00
-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
-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
-chardev spicevmc,id=ccid,name=smartcard
-device ccid-card-passthru,chardev=ccid
2021-09-28 15:48:18 +01:00
-monitor none
-serial mon:stdio)
if [[ "${ACCESSIBLE}" == "braille" ]] || [[ "${ACCESSIBLE}" == "brl" ]]; then
# shellcheck disable=SC2054
args+=(-chardev braille,id=brltty
-device usb-braille,id=usbbrl,chardev=brltty)
fi
2021-12-06 15:02:54 +00:00
if [ -n "${bridge}" ]; then
# Enable bridge mode networking
2021-11-13 15:56:36 +00:00
# shellcheck disable=SC2054,SC2206
args+=(-nic bridge,br=${bridge},model=virtio-net-pci)
else
# shellcheck disable=SC2054,SC2206
args+=(-device ${NET_DEVICE},netdev=nic -netdev ${NET},id=nic)
fi
# Add the disks
2021-09-28 22:48:38 +01:00
# - https://turlucode.com/qemu-disk-io-performance-comparison-native-or-threads-windows-10-version/
2021-09-29 04:11:26 +01:00
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 "${floppy}" ]; then
# shellcheck disable=SC2054
args+=(-drive if=floppy,format=raw,file="${floppy}")
fi
# Since there is bug (probably) in KolibriOS: cdrom indexes 0 or 1 make system show an extra unexisting iso, so we use index=2
if [ -n "${iso}" ] && [ "${guest_os}" == "kolibrios" ]; then
# shellcheck disable=SC2054
args+=(-drive media=cdrom,index=2,file="${iso}")
iso=""
fi
if [ -n "${iso}" ]; then
2021-12-06 15:02:22 +00:00
# shellcheck disable=SC2054
args+=(-drive media=cdrom,index=0,file="${iso}")
fi
if [ -n "${fixed_iso}" ]; then
2021-12-06 15:02:22 +00:00
# shellcheck disable=SC2054
args+=(-drive media=cdrom,index=1,file="${fixed_iso}")
fi
2021-10-08 02:42:08 +01:00
# Attach the unattended configuration to Windows guests when booting from ISO
if [ -n "${iso}" ] && [ "${guest_os}" == "windows" ] && [ -e "${VMDIR}/unattended.iso" ]; then
# shellcheck disable=SC2054
2021-12-06 15:02:22 +00:00
args+=(-drive media=cdrom,index=2,file="${VMDIR}/unattended.iso")
fi
if [ "${guest_os}" == "macos" ]; then
# shellcheck disable=SC2054
args+=(-device ahci,id=ahci
2021-10-07 16:05:10 +01:00
-device ide-hd,bus=ahci.0,drive=BootLoader,bootindex=0
-drive id=BootLoader,if=none,format=qcow2,file="${MAC_BOOTLOADER}")
2021-09-28 22:48:38 +01:00
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})
else
2021-11-13 15:56:36 +00:00
# shellcheck disable=SC2054,SC2206
args+=(-device virtio-blk-pci,drive=SystemDisk
-drive id=SystemDisk,if=none,format=qcow2,file="${disk_img}" ${STATUS_QUO})
fi
2021-09-29 03:30:16 +01:00
# https://wiki.qemu.org/Documentation/9psetup
# https://askubuntu.com/questions/772784/9p-libvirt-qemu-share-modes
2021-10-09 15:22:18 +01:00
if [ "${guest_os}" != "windows" ] && [ -n "${PUBLIC}" ]; then
2021-09-29 03:30:16 +01:00
# shellcheck disable=SC2054
args+=(-fsdev local,id=fsdev0,path="${PUBLIC}",security_model=mapped-xattr
-device virtio-9p-pci,fsdev=fsdev0,mount_tag="${PUBLIC_TAG}")
2021-09-29 03:30:16 +01:00
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
2021-10-06 11:01:33 +01:00
# shellcheck disable=SC2054
args+=(-chardev socket,id=chrtpm,path="${VMDIR}/${VMNAME}.swtpm-sock"
2021-10-06 11:01:33 +01:00
-tpmdev emulator,id=tpm0,chardev=chrtpm
-device tpm-tis,tpmdev=tpm0)
fi
if [ -n "$extra_args" ]; then
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[*]}"
2022-02-21 02:32:45 +00:00
SHELL_ARGS="${SHELL_ARGS//\(/\\(}"
SHELL_ARGS="${SHELL_ARGS//)/\\)}"
SHELL_ARGS="${SHELL_ARGS//Quickemu Project/\"Quickemu Project\"}"
2021-10-05 23:13:59 +01:00
echo "${QEMU}" "${SHELL_ARGS}" >> "${VMDIR}/${VMNAME}.sh"
2021-09-28 15:51:06 +01:00
${QEMU} "${args[@]}" > "${VMDIR}/${VMNAME}.log" &
# If output is 'none' then SPICE was requested.
if [ "${OUTPUT}" == "spice" ]; then
2021-10-09 15:22:18 +01:00
if [ -n "${PUBLIC}" ]; then
spicy --title "${VMNAME}" --port "${SPICE_PORT}" --spice-shared-dir "${PUBLIC}" "${FULLSPICY}" >/dev/null 2>&1 &
else
spicy --title "${VMNAME}" --port "${SPICE_PORT}" "${FULLSPICY}" >/dev/null 2>&1 &
fi
2021-09-06 22:26:48 +01:00
fi
2021-10-27 11:42:28 +01:00
sleep 0.25
echo " - Process: Starting ${VM} as ${VMNAME} ($(cat "${VMDIR}/${VMNAME}.pid"))"
2020-03-15 23:13:25 +00:00
}
function shortcut_create {
2021-11-02 18:39:04 +00:00
local dirname="${HOME}/.local/share/applications"
2021-09-29 03:31:46 +01:00
local filename="${HOME}/.local/share/applications/${VMNAME}.desktop"
2021-11-02 18:39:04 +00:00
if [ ! -d ${dirname} ]; then
mkdir -p ${dirname}
fi
cat << EOF > "${filename}"
[Desktop Entry]
Version=1.0
Type=Application
2021-10-25 14:32:19 +01:00
Terminal=false
Exec=${0} --vm ${VM}
Path=${VMPATH}
Name=${VMNAME}
2021-09-06 22:21:06 +01:00
Icon=/usr/share/icons/hicolor/scalable/apps/qemu.svg
EOF
echo "Created ${VMNAME}.desktop file"
}
2020-03-15 23:13:25 +00:00
function usage() {
echo
echo "Usage"
echo " ${LAUNCHER} --vm ubuntu.conf"
echo
echo "You can also pass optional parameters"
echo " --accessible : Select accessible facilitation. 'braille' (default - currently requires --display sdl )"
echo " --delete-disk : Delete the disk image and EFI variables"
echo " --delete-vm : Delete the entire VM and it's configuration"
echo " --display : Select display backend. 'sdl' (default), 'gtk', 'none', or 'spice'"
2021-10-21 00:09:43 +01:00
echo " --fullscreen : Starts VM in full screen mode (Ctl+Alt+f to exit)"
2021-10-25 15:42:01 +01:00
echo " --ignore-msrs-always : Configure KVM to always ignore unhandled machine-specific registers"
2021-10-21 00:09:43 +01:00
echo " --screen <screen> : Use specified screen to determine the window size."
echo " --shortcut : Create a desktop shortcut"
2020-03-20 18:22:03 +00:00
echo " --snapshot apply <tag> : Apply/restore a snapshot."
echo " --snapshot create <tag> : Create a snapshot."
echo " --snapshot delete <tag> : Delete a snapshot."
echo " --snapshot info : Show disk/snapshot info."
echo " --status-quo : Do not commit any changes to disk/snapshot."
2021-09-28 17:43:38 +01:00
echo " --version : Print version"
2020-03-15 23:13:25 +00:00
exit 1
}
#
function accessible_param_check() {
if [ "${ACCESSIBLE}" != "braille" ] && [ "${ACCESSIBLE}" != "brl" ] ; then
echo "ERROR! Requested accessiblility '${ACCESSIBLE}' is not recognised."
exit 1
# TODO can we check the device/API is available
fi
}
2021-11-08 16:03:58 +00:00
function display_param_check() {
if [ "${OUTPUT}" != "gtk" ] && [ "${OUTPUT}" != "none" ] && [ "${OUTPUT}" != "sdl" ] && [ "${OUTPUT}" != "spice" ]; then
echo "ERROR! Requested output '${OUTPUT}' is not recognised."
exit 1
elif [ "${OUTPUT}" == "spice" ] && ! command -v spicy &>/dev/null; then
echo "ERROR! Requested SPICE display, but 'spicy' is not installed."
exit 1
fi
}
# Lowercase variables are used in the VM config file only
2021-09-25 23:25:18 +01:00
boot="efi"
bridge=""
2021-09-25 13:57:22 +01:00
cpu_cores=""
disk_img=""
2021-10-09 16:26:06 +01:00
disk_size=""
fixed_iso=""
2021-09-28 19:01:44 +01:00
floppy=""
guest_os="linux"
2020-04-04 12:54:30 +01:00
img=""
iso=""
macos_release=""
2021-09-25 13:42:04 +01:00
port_forwards=()
preallocation="off"
ram=""
secureboot="off"
2021-10-06 11:01:33 +01:00
tpm="off"
2021-09-25 13:57:22 +01:00
usb_devices=()
extra_args=""
DELETE_DISK=0
DELETE_VM=0
FULLSCREEN=""
FULLSPICY=""
2021-11-08 16:03:58 +00:00
OUTPUT=""
PUBLIC=""
PUBLIC_PERMS=""
PUBLIC_TAG=""
2021-09-25 13:57:22 +01:00
SCREEN=""
SHORTCUT=0
2020-03-20 18:17:53 +00:00
SNAPSHOT_ACTION=""
SNAPSHOT_TAG=""
STATUS_QUO=""
2021-09-06 15:27:49 +01:00
USB_PASSTHROUGH=""
2020-03-15 23:13:25 +00:00
VM=""
VMDIR=""
VMNAME=""
VMPATH=""
readonly LAUNCHER=$(basename "${0}")
readonly DISK_MIN_SIZE=$((197632 * 8))
2022-02-21 05:30:27 +00:00
readonly VERSION="3.12"
2021-09-06 22:21:06 +01:00
# PUBLICSHARE is the only directory exposed to guest VMs for file
# sharing via 9P, spice-webdavd and Samba. This path is not configurable.
Deal with systems without xdg-user-dir better My system doesn't have xdg-user-dir, and shows the following errors on startup: ./quickemu: line 828: xdg-user-dir: command not found mkdir: cannot create directory '': No such file or directory basename: missing operand Try 'basename --help' for more information. Usage quickemu --vm ubuntu.conf You can also pass optional parameters --delete : Delete the disk image. --display : Select display backend. 'sdl' (default), 'gtk' or 'spice' --shortcut : Create a desktop shortcut --snapshot apply <tag> : Apply/restore a snapshot. --snapshot create <tag> : Create a snapshot. --snapshot delete <tag> : Delete a snapshot. --snapshot info : Show disk/snapshot info. --status-quo : Do not commit any changes to disk/snapshot. --fullscreen : Starts VM in full screen mode (Ctl+Alt+f to exit) --screen <screen> : Use specified screen to determine the window size. --version : Print version From a quick look it should still function correctly, but better to deal with this case explicitly. It's only used to get the PUBLIC, which is optional anyway. If I install it then it just returns ~, at which point it's set to "" anyway. I also removed it from the list of "requirements" for this reason. This also deals with an edge case I happened to notice: "xdg-user-dir PUBLICSHARE" returns "/home/martin/" on my system, whereas $HOME is "/home/martin", so the equality check fails. This is arguable a problem in xdg-user-dir, but better to fix it here. This only deals with a single slash (multiple slashes is harder in bash), which should be sufficient.
2021-10-08 11:52:11 +01:00
if command -v xdg-user-dir &>/dev/null; then
PUBLIC=$(xdg-user-dir PUBLICSHARE)
2021-10-09 15:41:53 +01:00
if [ "${PUBLIC%/}" != "${HOME}" ]; then
Deal with systems without xdg-user-dir better My system doesn't have xdg-user-dir, and shows the following errors on startup: ./quickemu: line 828: xdg-user-dir: command not found mkdir: cannot create directory '': No such file or directory basename: missing operand Try 'basename --help' for more information. Usage quickemu --vm ubuntu.conf You can also pass optional parameters --delete : Delete the disk image. --display : Select display backend. 'sdl' (default), 'gtk' or 'spice' --shortcut : Create a desktop shortcut --snapshot apply <tag> : Apply/restore a snapshot. --snapshot create <tag> : Create a snapshot. --snapshot delete <tag> : Delete a snapshot. --snapshot info : Show disk/snapshot info. --status-quo : Do not commit any changes to disk/snapshot. --fullscreen : Starts VM in full screen mode (Ctl+Alt+f to exit) --screen <screen> : Use specified screen to determine the window size. --version : Print version From a quick look it should still function correctly, but better to deal with this case explicitly. It's only used to get the PUBLIC, which is optional anyway. If I install it then it just returns ~, at which point it's set to "" anyway. I also removed it from the list of "requirements" for this reason. This also deals with an edge case I happened to notice: "xdg-user-dir PUBLICSHARE" returns "/home/martin/" on my system, whereas $HOME is "/home/martin", so the equality check fails. This is arguable a problem in xdg-user-dir, but better to fix it here. This only deals with a single slash (multiple slashes is harder in bash), which should be sufficient.
2021-10-08 11:52:11 +01:00
if [ ! -d "${PUBLIC}" ]; then
mkdir -p "${PUBLIC}"
fi
PUBLIC_TAG="Public-${USER,,}"
2021-10-09 15:41:53 +01:00
# shellcheck disable=SC2012
PUBLIC_PERMS=$(ls -ld "${PUBLIC}" | cut -d' ' -f1)
Deal with systems without xdg-user-dir better My system doesn't have xdg-user-dir, and shows the following errors on startup: ./quickemu: line 828: xdg-user-dir: command not found mkdir: cannot create directory '': No such file or directory basename: missing operand Try 'basename --help' for more information. Usage quickemu --vm ubuntu.conf You can also pass optional parameters --delete : Delete the disk image. --display : Select display backend. 'sdl' (default), 'gtk' or 'spice' --shortcut : Create a desktop shortcut --snapshot apply <tag> : Apply/restore a snapshot. --snapshot create <tag> : Create a snapshot. --snapshot delete <tag> : Delete a snapshot. --snapshot info : Show disk/snapshot info. --status-quo : Do not commit any changes to disk/snapshot. --fullscreen : Starts VM in full screen mode (Ctl+Alt+f to exit) --screen <screen> : Use specified screen to determine the window size. --version : Print version From a quick look it should still function correctly, but better to deal with this case explicitly. It's only used to get the PUBLIC, which is optional anyway. If I install it then it just returns ~, at which point it's set to "" anyway. I also removed it from the list of "requirements" for this reason. This also deals with an edge case I happened to notice: "xdg-user-dir PUBLICSHARE" returns "/home/martin/" on my system, whereas $HOME is "/home/martin", so the equality check fails. This is arguable a problem in xdg-user-dir, but better to fix it here. This only deals with a single slash (multiple slashes is harder in bash), which should be sufficient.
2021-10-08 11:52:11 +01:00
else
PUBLIC=""
fi
fi
2021-09-06 22:21:06 +01:00
# 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
2021-12-03 16:54:20 +00:00
echo "ERROR! QEMU not found. Please make install qemu-system-x86_64 and qemu-img"
2021-09-06 22:21:06 +01:00
exit 1
fi
2020-03-15 23:13:25 +00:00
2021-10-19 02:17:23 +01:00
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
2020-08-10 13:07:45 +01:00
# Take command line arguments
if [ $# -lt 1 ]; then
usage
exit 0
else
while [ $# -gt 0 ]; do
case "${1}" in
2022-02-21 05:33:23 +00:00
-accessible|--accessible|-accessibility|--accessibility)
ACCESSIBLE="${2}"
accessible_param_check
shift
shift;;
-delete|--delete|-delete-disk|--delete-disk)
DELETE_DISK=1
shift;;
-delete-vm|--delete-vm)
DELETE_VM=1
2020-08-10 13:07:45 +01:00
shift;;
-display|--display)
OUTPUT="${2}"
2021-11-08 16:03:58 +00:00
display_param_check
shift
shift;;
2021-10-21 00:10:11 +01:00
-fullscreen|--fullscreen|-full-screen|--full-screen)
FULLSCREEN="-full-screen"
FULLSPICY="--full-screen"
shift;;
-ignore-msrs-always|--ignore-msrs-always)
2021-10-21 00:04:07 +01:00
ignore_msrs_always
exit;;
2021-10-21 00:10:11 +01:00
-screen|--screen)
SCREEN="${2}"
shift
shift;;
2020-08-10 13:07:45 +01:00
-snapshot|--snapshot)
SNAPSHOT_ACTION="${2}"
if [ -z "${SNAPSHOT_ACTION}" ]; then
echo "ERROR! No snapshot action provided."
exit 1
fi
shift
SNAPSHOT_TAG="${2}"
if [ -z "${SNAPSHOT_TAG}" ] && [ "${SNAPSHOT_ACTION}" != "info" ]; then
echo "ERROR! No snapshot tag provided."
exit 1
fi
shift
shift;;
-status-quo|--status-quo)
STATUS_QUO="-snapshot"
shift;;
2021-10-21 00:10:11 +01:00
-shortcut|--shortcut)
SHORTCUT=1
2020-08-10 13:07:45 +01:00
shift;;
-vm|--vm)
VM="${2}"
shift
shift;;
2021-09-28 17:43:38 +01:00
-version|--version)
echo "${VERSION}"
exit;;
2020-08-10 13:07:45 +01:00
-h|--h|-help|--help)
usage;;
*)
echo "ERROR! \"${1}\" is not a supported parameter."
usage;;
esac
done
fi
2020-03-15 23:13:25 +00:00
if [ -n "${VM}" ] && [ -e "${VM}" ]; then
# shellcheck source=/dev/null
source "${VM}"
2020-03-20 18:16:08 +00:00
if [ -z "${disk_img}" ]; then
echo "ERROR! No disk_img defined."
exit 1
fi
VMDIR=$(dirname "${disk_img}")
2021-10-25 15:14:59 +01:00
VMNAME=$(basename "${VM}" .conf)
2021-10-25 14:31:07 +01:00
VMPATH=$(realpath "$(dirname "${VM}")")
# Backwards compatibility for ${driver_iso}
if [ -n "${driver_iso}" ] && [ -z "${fixed_iso}" ]; then
fixed_iso="${driver_iso}"
fi
2021-10-05 00:03:03 +01:00
# Backwards compatibility for ${disk} (size)
if [ -n "${disk}" ]; then
disk_size="${disk}"
fi
2021-11-08 16:03:58 +00:00
if [ -z "${OUTPUT}" ]; then
if [ -z "${display}" ]; then
OUTPUT="sdl"
else
OUTPUT="${display}"
display_param_check
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
2020-03-15 23:13:25 +00:00
fi
if [ ${DELETE_DISK} -eq 1 ]; then
delete_disk
exit
fi
if [ ${DELETE_VM} -eq 1 ]; then
delete_vm
2020-03-20 18:17:53 +00:00
exit
2020-03-15 23:13:25 +00:00
fi
2020-03-20 18:17:53 +00:00
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)
snapshot_info
exit;;
*)
echo "ERROR! \"${SNAPSHOT_ACTION}\" is not a supported snapshot action."
usage;;
esac
2020-03-15 23:13:25 +00:00
fi
if [ ${SHORTCUT} -eq 1 ]; then
shortcut_create
exit
fi
vm_boot