mirror of
https://github.com/oSoWoSo/DistroHopper.git
synced 2024-08-14 22:46:53 +00:00
205023fd50
Using the -display none option will start the VM with SPICE enabled but no display attached. The .ports file in the VM directory can be used to lookup the SSH and SPICE ports used by the VM.
1175 lines
38 KiB
Bash
Executable file
1175 lines
38 KiB
Bash
Executable file
#!/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 permenant by running: 'quickemu --ignore-msrs-always'"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
function disk_delete() {
|
|
if [ -e "${disk_img}" ]; then
|
|
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
|
|
rm "${VMDIR}/${VMNAME}-vars.fd" >/dev/null 2>&1
|
|
rm "${VMPATH}/${VMDIR}/${VMNAME}-vars.fd" > /dev/null 2>&1
|
|
echo "SUCCESS! Deleted ${disk_img}"
|
|
else
|
|
echo "NOTE! ${disk_img} not found. Doing nothing."
|
|
fi
|
|
local VMNAME=""
|
|
VMNAME=$(basename "${VM}" .conf)
|
|
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 snapshot_apply() {
|
|
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 "SUCCESS! 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() {
|
|
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 "SUCCESS! Created snapshot ${TAG} of ${disk_img}"
|
|
else
|
|
echo "ERROR! Failed to create snapshot ${TAG} of ${disk_img}"
|
|
fi
|
|
else
|
|
echo "NOTE! ${disk_img} not found. Doing nothing."
|
|
fi
|
|
}
|
|
|
|
function snapshot_delete() {
|
|
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 "SUCCESS! Deleted snapshot ${TAG} of ${disk_img}"
|
|
else
|
|
echo "ERROR! Failed to delete snapshot ${TAG} of ${disk_img}"
|
|
fi
|
|
else
|
|
echo "NOTE! ${disk_img} not found. Doing nothing."
|
|
fi
|
|
}
|
|
|
|
function snapshot_info() {
|
|
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
|
|
(echo -n "" >/dev/tcp/127.0.0.1/"${PORT}") >/dev/null 2>&1
|
|
if [ ${?} -ne 0 ]; 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 [ -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 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 MAC_BOOTLOADER=""
|
|
local MAC_MISSING=""
|
|
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
|
|
|
|
echo "Quickemu ${VERSION} using ${QEMU} v${QEMU_VER_LONG}"
|
|
echo " - Host: ${LSB_DESCRIPTION} running ${KERNEL_NAME} ${KERNEL_VER} ${KERNEL_NODE}"
|
|
echo " - Guest: Starting ${VM} as ${VMNAME}"
|
|
|
|
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')
|
|
|
|
# A CPU with Intel VT-x / AMD SVM support is required
|
|
if [ "${HOST_CPU_VENDOR}" == "AuthenticIntel" ]; 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
|
|
|
|
# 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"
|
|
|
|
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.
|
|
|
|
# https://bugzilla.redhat.com/show_bug.cgi?id=1929357#c5
|
|
case ${secureboot} in
|
|
on)
|
|
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
|
|
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
|
|
EFI_CODE="/usr/share/OVMF/x64/OVMF_CODE.secboot.fd"
|
|
efi_vars "/usr/share/OVMF/x64/OVMF_VARS.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/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}"
|
|
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.
|
|
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
|
|
freebsd|linux|openbsd)
|
|
CPU="-cpu host,kvm=on"
|
|
if [ "${HOST_CPU_VENDOR}" == "AuthenticAMD" ]; then
|
|
CPU="${CPU},topoext"
|
|
fi
|
|
if [ "${guest_os}" == "freebsd" ]; then
|
|
MOUSE="usb-mouse"
|
|
fi
|
|
if [ -z "${disk_size}" ]; then
|
|
disk_size="16G"
|
|
fi
|
|
;;
|
|
macos)
|
|
#https://www.nicksherlock.com/2020/06/installing-macos-big-sur-on-proxmox/
|
|
# A CPU with SSE4.1 support is required for >= macOS Sierra
|
|
# A CPU with AVX2 support is required for >= macOS Mojave
|
|
if check_cpu_flag sse4_1 && check_cpu_flag avx2; then
|
|
case ${HOST_CPU_VENDOR} in
|
|
AuthenticIntel)
|
|
CPU="-cpu host,kvm=on,vendor=GenuineIntel,+hypervisor,+invtsc,+kvm_pv_eoi,+kvm_pv_unhalt";;
|
|
AuthenticAMD|*)
|
|
# Used in past versions: +movbe,+smep,+xgetbv1,+xsavec
|
|
# Warn on AMD: +fma4,+pcid
|
|
CPU="-cpu Penryn,kvm=on,vendor=GenuineIntel,+aes,+avx,+avx2,+bmi1,+bmi2,+fma,+hypervisor,+invtsc,+kvm_pv_eoi,+kvm_pv_unhalt,+popcnt,+ssse3,+sse4.2,vmware-cpuid-freq=on,+xsave,+xsaveopt,check";;
|
|
esac
|
|
else
|
|
echo "ERROR! macOS requires a CPU with SSE 4.1 and AVX2 support."
|
|
exit 1
|
|
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 in Big Sur
|
|
# * VirtIO Memory Balloning is supported in Big Sur (https://pmhahn.github.io/virtio-balloon/)
|
|
# * VirtIO RNG is supported in Big Sur, but exposed to all guests.
|
|
case ${macos_release} in
|
|
catalina)
|
|
BALLOON=""
|
|
MAC_DISK_DEV="virtio-blk-pci"
|
|
NET_DEVICE="vmxnet3"
|
|
USB_HOST_PASSTHROUGH_CONTROLLER="usb-ehci"
|
|
;;
|
|
big-sur)
|
|
BALLOON="-device virtio-balloon"
|
|
MAC_DISK_DEV="virtio-blk-pci"
|
|
NET_DEVICE="virtio-net"
|
|
USB_HOST_PASSTHROUGH_CONTROLLER="qemu-xhci"
|
|
;;
|
|
*)
|
|
# 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
|
|
OSK=$(echo "bheuneqjbexolgurfrjbeqfthneqrqcyrnfrqbagfgrny(p)NccyrPbzchgreVap" | tr 'A-Za-z' 'N-ZA-Mn-za-m')
|
|
GUEST_TWEAKS="-device isa-applesmc,osk=${OSK} -no-hpet -global kvm-pit.lost_tick_policy=discard"
|
|
if [ -z "${disk_size}" ]; then
|
|
disk_size="96G"
|
|
fi
|
|
;;
|
|
windows)
|
|
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
|
|
GUEST_TWEAKS="-no-hpet -global kvm-pit.lost_tick_policy=discard"
|
|
if [ -z "${disk_size}" ]; then
|
|
disk_size="64G"
|
|
fi
|
|
SMM="on"
|
|
;;
|
|
*)
|
|
CPU="-cpu host,kvm=on"
|
|
NET_DEVICE="rtl8139"
|
|
if [ -z "${disk_size}" ]; then
|
|
disk_size="32G"
|
|
fi
|
|
echo "WARNING! Unrecognised guest OS: ${guest_os}"
|
|
;;
|
|
esac
|
|
|
|
# Disable suspend to RAM if SecureBoot/SMM is enabled
|
|
if [ "${secureboot}" == "on" ] || [ "${SMM}" == "on" ]; then
|
|
GUEST_TWEAKS="${GUEST_TWEAKS} -global ICH9-LPC.disable_s3=1"
|
|
fi
|
|
|
|
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 qcow2 -o lazy_refcounts=on,preallocation="${preallocation}" "${disk_img}" "${disk_size}"; then
|
|
echo "ERROR! Failed to create ${disk_img}"
|
|
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
|
|
# 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
|
|
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
|
|
fi
|
|
fi
|
|
|
|
if [ "${DISK_USED}" == "yes" ]; 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
|
|
|
|
# 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
|
|
# Tweak video device based on the guest macOS release.
|
|
# Displays in System Preferences can be used to select a resolution if:
|
|
# - qxl is used on Big Sur and Catalina
|
|
# - VGA is used on Mojave, although available resolutions are all 4:3
|
|
# - High Sierra will run at the default 1920x1080 only.
|
|
case ${macos_release} in
|
|
catalina|big-sur) DISPLAY_DEVICE="qxl";;
|
|
*) DISPLAY_DEVICE="VGA";;
|
|
esac
|
|
elif [ "${guest_os}" == "windows" ]; then
|
|
DISPLAY_DEVICE="qxl-vga"
|
|
else
|
|
DISPLAY_DEVICE="qxl-vga"
|
|
fi
|
|
|
|
echo -n " - Display: ${OUTPUT^^}, ${DISPLAY_DEVICE}"
|
|
|
|
# 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
|
|
if [ "${DISPLAY_DEVICE}" == "qxl-vga" ] || [ "${DISPLAY_DEVICE}" == "VGA" ]; then
|
|
VIDEO="${VIDEO},vgamem_mb=128"
|
|
fi
|
|
VIDEO="${VIDEO} ${FULLSCREEN}"
|
|
|
|
# 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
|
|
echo ", GL (${GL}), VirGL (on)"
|
|
else
|
|
echo ", GL (${GL}), VirGL (off)"
|
|
fi
|
|
|
|
# Set the hostname of the VM
|
|
local NET="user,hostname=${VMNAME}"
|
|
|
|
echo -n "" > "${VMDIR}/${VMNAME}.ports"
|
|
|
|
# Find a free port to expose ssh to the guest
|
|
local SSH_PORT=""
|
|
SSH_PORT=$(get_port 22220 9)
|
|
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}"
|
|
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
|
|
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
|
|
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 "${FULLSPICY}"
|
|
SPICE="${SPICE},port=${SPICE_PORT}"
|
|
fi
|
|
|
|
# Reference: https://gitlab.gnome.org/GNOME/phodav/-/issues/5
|
|
if [ "${guest_os}" != "macos" ] && [ -n "${PUBLIC}" ]; then
|
|
echo " - WebDAV: On guest: dav://localhost:9843/"
|
|
fi
|
|
fi
|
|
|
|
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 👆"
|
|
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
|
|
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 1
|
|
fi
|
|
|
|
# Boot the VM
|
|
local args=()
|
|
|
|
# shellcheck disable=SC2054,SC2206,SC2140
|
|
args+=(-name ${VMNAME},process=${VMNAME} -pidfile "${VMDIR}/${VMNAME}.pid"
|
|
-enable-kvm -machine q35,smm=${SMM},vmport=off ${GUEST_TWEAKS}
|
|
${CPU} ${SMP}
|
|
-m ${RAM_VM} ${BALLOON}
|
|
-smbios type=2,manufacturer="Wimpys World",product="Quickemu",version="${VERSION}",serial="jvzclfjbeyq.pbz",location="wimpysworld.com",asset="${VMNAME}"
|
|
${VIDEO} -display ${DISPLAY_RENDER}
|
|
-device usb-ehci,id=input
|
|
-device usb-kbd,bus=input.0
|
|
-device ${MOUSE},bus=input.0
|
|
-audiodev pa,id=audio0,out.mixing-engine=off,out.stream-name=${LAUNCHER}-${VMNAME},in.stream-name=${LAUNCHER}-${VMNAME}
|
|
-device intel-hda -device hda-duplex,audiodev=audio0
|
|
-rtc base=localtime,clock=host,driftfix=slew
|
|
-spice ${SPICE}
|
|
-device virtio-serial-pci
|
|
-chardev spicevmc,id=vdagent0,name=vdagent
|
|
-device virtserialport,chardev=vdagent0,name=com.redhat.spice.0
|
|
-device virtio-rng-pci,rng=rng0
|
|
-object rng-random,id=rng0,filename=/dev/urandom
|
|
-monitor none
|
|
-serial mon:stdio)
|
|
|
|
# Disables network interface during Windows install.
|
|
# See https://github.com/wimpysworld/quickemu/issues/115
|
|
if [ "${guest_os}" == "windows" ] && [ -n "${iso}" ]; then
|
|
args+=(-nic none)
|
|
else
|
|
# 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 "${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
|
|
|
|
# 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
|
|
args+=(-drive media=cdrom,index=2,file="${VMDIR}/unattended.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})
|
|
else
|
|
# shellcheck disable=SC2054,SC2206
|
|
args+=(-device virtio-blk-pci,drive=SystemDisk
|
|
-drive id=SystemDisk,if=none,format=qcow2,file="${disk_img}" ${STATUS_QUO}
|
|
-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 usb-ccid
|
|
-chardev spicevmc,id=ccid,name=smartcard
|
|
-device ccid-card-passthru,chardev=ccid
|
|
-device virtio-serial-pci
|
|
-chardev spiceport,id=webdav0,name=org.spice-space.webdav.0
|
|
-device virtserialport,chardev=webdav0,name=org.spice-space.webdav.0)
|
|
fi
|
|
|
|
# https://wiki.qemu.org/Documentation/9psetup
|
|
# https://askubuntu.com/questions/772784/9p-libvirt-qemu-share-modes
|
|
if [ "${guest_os}" != "windows" ] && [ -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
|
|
|
|
# The OSK parameter contains parenthesis, they need to be escaped in the shell scripts
|
|
# The vendor name, Wimpys World, contains a space. It needs to be double-quoted.
|
|
SHELL_ARGS="${args[*]}"
|
|
SHELL_ARGS="${SHELL_ARGS//(/\\(}"
|
|
SHELL_ARGS="${SHELL_ARGS//)/\\)}"
|
|
SHELL_ARGS="${SHELL_ARGS//Wimpys World/\"Wimpys World\"}"
|
|
|
|
echo "${QEMU}" "${SHELL_ARGS}" >> "${VMDIR}/${VMNAME}.sh"
|
|
${QEMU} "${args[@]}" > "${VMDIR}/${VMNAME}.log" &
|
|
|
|
# If output is 'none' then SPICE was requested.
|
|
if [ "${OUTPUT}" == "spice" ]; then
|
|
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
|
|
elif [ "${OUTPUT}" == "none" ]; then
|
|
sleep 0.25
|
|
echo " - PID: ${VMNAME} ($(cat "${VMDIR}/${VMNAME}.pid"))"
|
|
fi
|
|
}
|
|
|
|
function shortcut_create {
|
|
local filename="${HOME}/.local/share/applications/${VMNAME}.desktop"
|
|
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 "Created ${VMNAME}.desktop file"
|
|
}
|
|
|
|
function usage() {
|
|
echo
|
|
echo "Usage"
|
|
echo " ${LAUNCHER} --vm ubuntu.conf"
|
|
echo
|
|
echo "You can also pass optional parameters"
|
|
echo " --delete : Delete the disk image."
|
|
echo " --display : Select display backend. 'sdl' (default), 'gtk', 'none', or 'spice'"
|
|
echo " --fullscreen : Starts VM in full screen mode (Ctl+Alt+f to exit)"
|
|
echo " --ignore-msrs-always : Configure KVM to always ignore unhandle machine-specific registers"
|
|
echo " --screen <screen> : Use specified screen to determine the window size."
|
|
echo " --shortcut : Create a desktop shortcut"
|
|
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."
|
|
echo " --version : Print version"
|
|
exit 1
|
|
}
|
|
|
|
# Lowercase variables are used in the VM config file only
|
|
boot="efi"
|
|
cpu_cores=""
|
|
disk_img=""
|
|
disk_size=""
|
|
fixed_iso=""
|
|
floppy=""
|
|
guest_os="linux"
|
|
img=""
|
|
iso=""
|
|
macos_release=""
|
|
port_forwards=()
|
|
preallocation="off"
|
|
ram=""
|
|
secureboot="off"
|
|
tpm="off"
|
|
usb_devices=()
|
|
|
|
DELETE=0
|
|
FULLSCREEN=""
|
|
FULLSPICY=""
|
|
OUTPUT="sdl"
|
|
PUBLIC=""
|
|
PUBLIC_PERMS=""
|
|
PUBLIC_TAG=""
|
|
SCREEN=""
|
|
SHORTCUT=0
|
|
SNAPSHOT_ACTION=""
|
|
SNAPSHOT_TAG=""
|
|
STATUS_QUO=""
|
|
USB_PASSTHROUGH=""
|
|
VM=""
|
|
VMDIR=""
|
|
VMNAME=""
|
|
VMPATH=""
|
|
|
|
readonly LAUNCHER=$(basename "${0}")
|
|
readonly DISK_MIN_SIZE=$((197632 * 8))
|
|
readonly VERSION="2.2.7"
|
|
|
|
# PUBLICSHARE is the only directory exposed to guest VMs for file
|
|
# sharing via 9P, spice-webdavd and Samba. This path is not configurable.
|
|
if command -v xdg-user-dir &>/dev/null; then
|
|
PUBLIC=$(xdg-user-dir PUBLICSHARE)
|
|
if [ "${PUBLIC%/}" != "${HOME}" ]; then
|
|
if [ ! -d "${PUBLIC}" ]; then
|
|
mkdir -p "${PUBLIC}"
|
|
fi
|
|
PUBLIC_TAG="Public-${USER,,}"
|
|
# shellcheck disable=SC2012
|
|
PUBLIC_PERMS=$(ls -ld "${PUBLIC}" | cut -d' ' -f1)
|
|
else
|
|
PUBLIC=""
|
|
fi
|
|
fi
|
|
|
|
# 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 install qemu."
|
|
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
|
|
exit 0
|
|
else
|
|
while [ $# -gt 0 ]; do
|
|
case "${1}" in
|
|
-delete|--delete)
|
|
DELETE=1
|
|
shift;;
|
|
-display|--display)
|
|
OUTPUT="${2}"
|
|
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
|
|
shift
|
|
shift;;
|
|
-fullscreen|--fullscreen|-full-screen|--full-screen)
|
|
FULLSCREEN="-full-screen"
|
|
FULLSPICY="--full-screen"
|
|
shift;;
|
|
-ignore-msrs-always|--ignore-msrs-always)
|
|
ignore_msrs_always
|
|
exit;;
|
|
-screen|--screen)
|
|
SCREEN="${2}"
|
|
shift
|
|
shift;;
|
|
-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;;
|
|
-shortcut|--shortcut)
|
|
SHORTCUT=1
|
|
shift;;
|
|
-vm|--vm)
|
|
VM="${2}"
|
|
shift
|
|
shift;;
|
|
-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}"
|
|
if [ -z "${disk_img}" ]; then
|
|
echo "ERROR! No disk_img defined."
|
|
exit 1
|
|
fi
|
|
|
|
VMDIR=$(dirname "${disk_img}")
|
|
VMNAME=$(basename "${VM}" .conf)
|
|
VMPATH=$(realpath "$(dirname "${VM}")")
|
|
|
|
# Backwards compatibility for ${driver_iso}
|
|
if [ -n "${driver_iso}" ] && [ -z "${fixed_iso}" ]; then
|
|
fixed_iso="${driver_iso}"
|
|
fi
|
|
|
|
# Backwards compatibility for ${disk} (size)
|
|
if [ -n "${disk}" ]; then
|
|
disk_size="${disk}"
|
|
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 [ ${DELETE} -eq 1 ]; then
|
|
disk_delete
|
|
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)
|
|
snapshot_info
|
|
exit;;
|
|
*)
|
|
echo "ERROR! \"${SNAPSHOT_ACTION}\" is not a supported snapshot action."
|
|
usage;;
|
|
esac
|
|
fi
|
|
|
|
if [ ${SHORTCUT} -eq 1 ]; then
|
|
shortcut_create
|
|
exit
|
|
fi
|
|
|
|
vm_boot
|