mirror of
https://github.com/oSoWoSo/DistroHopper.git
synced 2024-08-14 22:46:53 +00:00
78b9a3f7f2
* attempt to offer appropriate setting of ignore_msrs if needed * Removed done TODO * Revert "Removed done TODO" to resolve merge conflict since all TODOS removed upstream This reverts commit eb18fcf82932489534e3c9a244e124740dfcdf01.
1106 lines
36 KiB
Bash
Executable file
1106 lines
36 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
export LC_ALL=C
|
|
|
|
function ask_option_msrs() {
|
|
ignore_msrs=$(cat /sys/module/kvm/parameters/ignore_msrs)
|
|
if [ ${ignore_msrs} = "N" ]; then
|
|
while true ; do
|
|
read -p "Do you wish to set ignore_msrs now? (y/N)" msrs_response
|
|
case $msrs_response:0:1 in
|
|
[yY]* ) echo 1 | sudo tee /sys/module/kvm/parameters/ignore_msrs; break;;
|
|
* ) break ;;
|
|
|
|
esac
|
|
done
|
|
fi
|
|
}
|
|
|
|
function ask_persist_msrs() {
|
|
ignore_msrs=$(cat /sys/module/kvm/parameters/ignore_msrs)
|
|
#Skip if already persisted, assuming initramfs rebuilt
|
|
grep -lq 'ignore_msrs=Y' /etc/modprobe.d/kvm.conf >/dev/null 2>&1 || \
|
|
if [ ${ignore_msrs} = "Y" ]; then
|
|
echo
|
|
echo "To make setting ignore_msrs a more persistant solution you may run the following:"
|
|
echo 'echo "options kvm ignore_msrs=Y" | sudo tee -a /etc/modprobe.d/kvm.conf && sudo update-initramfs -k all -u'
|
|
echo
|
|
|
|
while true ; do
|
|
read -p "Do you wish to set ignore_msrs at boot? (y/N)" bootrsp
|
|
case $bootrsp:0:1 in
|
|
[yY]* ) echo "options kvm ignore_msrs=Y" | sudo tee -a /etc/modprobe.d/kvm.conf && sudo update-initramfs -k all -u ; break;;
|
|
* ) break ;;
|
|
|
|
esac
|
|
done
|
|
fi
|
|
}
|
|
|
|
function ignore_msrs_alert() {
|
|
ignore_msrs=$(cat /sys/module/kvm/parameters/ignore_msrs)
|
|
|
|
if [ ${ignore_msrs} = "N" ]; then
|
|
# Some macos and windows installations need this to successfully boot
|
|
echo '
|
|
If you experience issues booting macos or windows like those mentioned in
|
|
|
|
https://github.com/wimpysworld/quickemu/issues/88
|
|
|
|
then you can try the provided solution as a temporary fix by executing:
|
|
|
|
"echo 1 | sudo tee /sys/module/kvm/parameters/ignore_msrs"'
|
|
|
|
echo -n "I can do that for you now. " ; ask_option_msrs
|
|
fi
|
|
ask_persist_msrs
|
|
}
|
|
|
|
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 vm_boot() {
|
|
local BALLOON="-device virtio-balloon"
|
|
local CPU=""
|
|
local DISK_USED=""
|
|
local DISPLAY_DEVICE=""
|
|
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=""
|
|
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 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)
|
|
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"
|
|
echo " - BOOT: EFI (${guest_os})"
|
|
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
|
|
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
|
|
|
|
if [ -e "/usr/share/OVMF/OVMF_CODE_4M.fd" ] ||
|
|
[ -e "/usr/share/OVMF/x64/OVMF_CODE.fd" ] ||
|
|
[ -e "/usr/share/OVMF/OVMF_CODE.fd" ]; then
|
|
echo " - BOOT: EFI (${guest_os})"
|
|
|
|
if [ -e "/usr/share/OVMF/OVMF_CODE_4M.fd" ]; then
|
|
EFI_CODE="/usr/share/OVMF/OVMF_CODE_4M.fd"
|
|
elif [ -e "/usr/share/OVMF/x64/OVMF_CODE.fd" ]; then
|
|
EFI_CODE="/usr/share/OVMF/x64/OVMF_CODE.fd"
|
|
elif [ -e "/usr/share/OVMF/OVMF_CODE.fd" ]; then
|
|
EFI_CODE="/usr/share/OVMF/OVMF_CODE.fd"
|
|
fi
|
|
|
|
if [ ! -e "${EFI_VARS}" ]; then
|
|
if [ -e "/usr/share/OVMF/OVMF_VARS_4M.fd" ]; then
|
|
cp "/usr/share/OVMF/OVMF_VARS_4M.fd" "${EFI_VARS}"
|
|
elif [ -e "/usr/share/OVMF/x64/OVMF_VARS.fd" ]; then
|
|
cp "/usr/share/OVMF/x64/OVMF_VARS.fd" "${EFI_VARS}"
|
|
elif [ -e "/usr/share/OVMF/OVMF_VARS.fd" ]; then
|
|
cp "/usr/share/OVMF/OVMF_VARS.fd" "${EFI_VARS}"
|
|
fi
|
|
fi
|
|
else
|
|
boot="legacy"
|
|
echo " - BOOT: Legacy BIOS (${guest_os}) - EFI requested but no EFI firmware found."
|
|
fi
|
|
else
|
|
echo " - BOOT: Legacy BIOS (${guest_os})"
|
|
fi
|
|
|
|
# Make any OS specific adjustments
|
|
case ${guest_os} in
|
|
freebsd|linux)
|
|
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="64G"
|
|
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
|
|
;;
|
|
*)
|
|
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
|
|
|
|
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
|
|
|
|
# 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
|
|
|
|
# Enable TPM
|
|
if [ "${tpm}" == "on" ]; then
|
|
if command -v swtpm &>/dev/null; then
|
|
swtpm socket \
|
|
--ctrl type=unixio,path="${VMDIR}/${VMNAME}.swtpm-sock" \
|
|
--terminate \
|
|
--tpmstate dir="${VMDIR}" \
|
|
--tpm2 &
|
|
echo " - TPM: ${VMDIR}/${VMNAME}.swtpm-sock (${!})"
|
|
else
|
|
echo " - TPM: swtpm is not installed, TPM not available!"
|
|
fi
|
|
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
|
|
spice) DISPLAY_DEVICE="qxl-vga";;
|
|
*) DISPLAY_DEVICE="virtio-vga";;
|
|
esac
|
|
elif [ "${guest_os}" == "macos" ]; then
|
|
# offer ignore_msrs if not set
|
|
ignore_msrs_alert
|
|
# 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
|
|
# offer ignore_msrs if not set
|
|
ignore_msrs_alert
|
|
DISPLAY_DEVICE="qxl-vga"
|
|
else
|
|
DISPLAY_DEVICE="qxl-vga"
|
|
fi
|
|
|
|
echo -n " - Display: ${OUTPUT^^}, ${DISPLAY_DEVICE}"
|
|
|
|
if [ "${OUTPUT}" == "spice" ]; then
|
|
OUTPUT="none"
|
|
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
|
|
if [ "${DISPLAY_DEVICE}" == "qxl-vga" ] || [ "${DISPLAY_DEVICE}" == "VGA" ]; then
|
|
VIDEO="${VIDEO},vgamem_mb=128"
|
|
fi
|
|
VIDEO="${VIDEO} ${FULLSCREEN}"
|
|
|
|
if [ "${OUTPUT}" == "gtk" ]; then
|
|
OUTPUT="${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
|
|
fi
|
|
|
|
if [ "${OUTPUT}" != "none" ]; then
|
|
OUTPUT="${OUTPUT},gl=${GL}"
|
|
fi
|
|
|
|
if [ "${GL}" == "on" ] && [[ "${DISPLAY_DEVICE}" == *"virtio"* ]]; then
|
|
DISPLAY_DEVICE="${DISPLAY_DEVICE},virgl=on"
|
|
echo ", GL (${GL}), VirGL (on)"
|
|
else
|
|
echo ", GL (${GL}), VirGL (off)"
|
|
fi
|
|
|
|
# Set the hostname of the VM
|
|
local NET="user,hostname=${VMNAME}"
|
|
|
|
# Find a free port to expose ssh to the guest
|
|
local SSH_PORT=""
|
|
SSH_PORT=$(get_port 22220 9)
|
|
if [ -n "${SSH_PORT}" ]; then
|
|
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-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 -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
|
|
|
|
# Boot the VM
|
|
local args=()
|
|
|
|
# shellcheck disable=SC2054,SC2206,SC2140
|
|
args+=(-name ${VMNAME},process=${VMNAME} -pidfile "${VMDIR}/${VMNAME}.pid"
|
|
-enable-kvm -machine q35,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 ${OUTPUT}
|
|
-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+=(-drive if=pflash,format=raw,file="${EFI_CODE}",readonly=on
|
|
-drive if=pflash,format=raw,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}",cache=none,aio=native)
|
|
fi
|
|
|
|
# shellcheck disable=SC2054,SC2206
|
|
args+=(-device ${MAC_DISK_DEV},drive=SystemDisk
|
|
-drive id=SystemDisk,if=none,format=qcow2,file="${disk_img}",cache=none,aio=native ${STATUS_QUO})
|
|
else
|
|
# shellcheck disable=SC2054,SC2206
|
|
args+=(-device virtio-blk-pci,drive=SystemDisk
|
|
-drive id=SystemDisk,if=none,format=qcow2,file="${disk_img}",cache=none,aio=native ${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 "#!/usr/bin/env bash" > "${VMDIR}/${VMNAME}.sh"
|
|
echo "${QEMU}" "${SHELL_ARGS}" >> "${VMDIR}/${VMNAME}.sh"
|
|
|
|
${QEMU} "${args[@]}" > "${VMDIR}/${VMNAME}.log" &
|
|
|
|
# If output is 'none' then SPICE was requested.
|
|
if [ ${OUTPUT} == "none" ]; 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
|
|
fi
|
|
}
|
|
|
|
function shortcut_create {
|
|
local VMNAME=""
|
|
VMNAME=$(basename "${VM}" .conf)
|
|
local LAUNCHER_DIR=""
|
|
LAUNCHER_DIR="$(dirname "$(realpath "$0")")"
|
|
local filename="${HOME}/.local/share/applications/${VMNAME}.desktop"
|
|
cat << EOF > "${filename}"
|
|
[Desktop Entry]
|
|
Version=1.0
|
|
Type=Application
|
|
Terminal=true
|
|
Exec=${LAUNCHER_DIR}/${LAUNCHER} --vm ${VM}
|
|
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' or 'spice'"
|
|
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 " --fullscreen : Starts VM in full screen mode (Ctl+Alt+f to exit)"
|
|
echo " --screen <screen> : Use specified screen to determine the window size."
|
|
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=""
|
|
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.5"
|
|
|
|
# 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}" != "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;;
|
|
-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;;
|
|
-fullscreen|--fullscreen|-full-screen|--full-screen)
|
|
FULLSCREEN="-full-screen"
|
|
FULLSPICY="--full-screen"
|
|
shift;;
|
|
-vm|--vm)
|
|
VM="${2}"
|
|
shift
|
|
shift;;
|
|
-screen|--screen)
|
|
SCREEN="${2}"
|
|
shift
|
|
shift;;
|
|
-shortcut|--shortcut)
|
|
SHORTCUT=1
|
|
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=$(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
|
|
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
|