mirror of
https://github.com/oSoWoSo/DistroHopper.git
synced 2024-08-14 22:46:53 +00:00
6ea4fa812f
quickget it the tool to acquire macOS with compatible firmware and bootloader. quickemu is backwards compatible with Clover.
829 lines
26 KiB
Bash
Executable file
829 lines
26 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
export LC_ALL=C
|
|
|
|
function disk_delete() {
|
|
if [ -e "${disk_img}" ]; then
|
|
rm "${disk_img}"
|
|
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 -v "${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=$2
|
|
while true; do
|
|
local CANDIDATE=$((PORT_START + (RANDOM % PORT_RANGE)))
|
|
(echo "" >/dev/tcp/127.0.0.1/${CANDIDATE}) >/dev/null 2>&1
|
|
if [ ${?} -ne 0 ]; then
|
|
echo "${CANDIDATE}"
|
|
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 vm_boot() {
|
|
local VMNAME=""
|
|
VMNAME=$(basename "${VM}" .conf)
|
|
local VMDIR=""
|
|
VMDIR=$(dirname "${disk_img}")
|
|
local BALLOON="-device virtio-balloon"
|
|
local CPU=""
|
|
local DISPLAY_DEVICE=""
|
|
local GL="on"
|
|
local GUEST_TWEAKS=""
|
|
local HOST_CPU=""
|
|
local MAC_MISSING=""
|
|
local NET_DEVICE="virtio-net"
|
|
local OSK=""
|
|
local QEMU_VER=""
|
|
local USB_HOST_PASSTHROUGH_CONTROLLER="qemu-xhci"
|
|
local VIDEO=""
|
|
QEMU_VER=$(${QEMU} -version | head -n1 | cut -d' ' -f4 | cut -d'(' -f1)
|
|
echo "Quickemu ${VERSION} starting ${VM}"
|
|
echo " - QEMU: ${QEMU} v${QEMU_VER}"
|
|
|
|
# Force to lowercase.
|
|
boot=${boot,,}
|
|
|
|
# Always Boot macOS using EFI
|
|
if [ "${guest_os}" == "macos" ]; then
|
|
boot="efi"
|
|
echo " - BOOT: EFI"
|
|
if [ -e "${VMDIR}/OVMF_CODE.fd" ] && [ -e "${VMDIR}/OVMF_VARS-1024x768.fd" ]; then
|
|
local EFI_CODE="${VMDIR}/OVMF_CODE.fd"
|
|
local EFI_VARS="${VMDIR}/OVMF_VARS-1024x768.fd"
|
|
else
|
|
MAC_MISSING="Firmware"
|
|
fi
|
|
|
|
if [ -e "${VMDIR}/OpenCore.qcow2" ]; then
|
|
local MAC_BOOTLOADER="${VMDIR}/OpenCore.qcow2"
|
|
elif [ -e "${VMDIR}/ESP.qcow2" ]; then
|
|
# Backwards compatibility for Clover
|
|
local 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
|
|
if [ -e "/usr/share/OVMF/OVMF_CODE_4M.fd" ]; then
|
|
echo " - BOOT: EFI"
|
|
local EFI_CODE="/usr/share/OVMF/OVMF_CODE_4M.fd"
|
|
local EFI_VARS="${VMDIR}/OVMF_VARS_4M.fd"
|
|
if [ -e "${VMDIR}/${VMNAME}-vars.fd" ]; then
|
|
# Preserve backward compatibility
|
|
mv "${VMDIR}/${VMNAME}-vars.fd" "${EFI_VARS}"
|
|
elif [ ! -e "${EFI_VARS}" ]; then
|
|
cp "/usr/share/OVMF/OVMF_VARS_4M.fd" "${EFI_VARS}"
|
|
fi
|
|
else
|
|
boot="legacy"
|
|
echo " - BOOT: Legacy BIOS - EFI requested but no EFI firmware found."
|
|
fi
|
|
else
|
|
echo " - BOOT: Legacy BIOS"
|
|
fi
|
|
|
|
# Force to lowercase.
|
|
guest_os=${guest_os,,}
|
|
|
|
HOST_CPU=$(grep vendor /proc/cpuinfo | uniq | cut -d':' -f2 | sed 's/ //g')
|
|
|
|
# Make any OS specific adjustments
|
|
case ${guest_os} in
|
|
linux)
|
|
CPU="-cpu host,kvm=on"
|
|
disk="16G"
|
|
;;
|
|
macos)
|
|
BALLOON=""
|
|
#https://www.nicksherlock.com/2020/06/installing-macos-big-sur-on-proxmox/
|
|
if [ "${HOST_CPU}" == "AuthenticIntel" ]; then
|
|
CPU="-cpu host,kvm=on,vendor=GenuineIntel,+hypervisor,+invtsc,+kvm_pv_eoi,+kvm_pv_unhalt"
|
|
elif [ "${HOST_CPU}" == "AuthenticAMD" ]; then
|
|
# CPU flags used in past Quickemu: +movbe,+smep,+xgetbv1,+xsavec
|
|
# CPU flags that 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"
|
|
else
|
|
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"
|
|
fi
|
|
OSK=$(echo "bheuneqjbexolgurfrjbeqfthneqrqcyrnfrqbagfgrny(p)NccyrPbzchgreVap" | tr 'A-Za-z' 'N-ZA-Mn-za-m')
|
|
GUEST_TWEAKS="-device isa-applesmc,osk=${OSK} -global kvm-pit.lost_tick_policy=discard"
|
|
disk="64G"
|
|
NET_DEVICE="vmxnet3"
|
|
USB_HOST_PASSTHROUGH_CONTROLLER="usb-ehci"
|
|
;;
|
|
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"
|
|
GUEST_TWEAKS="-no-hpet -global kvm-pit.lost_tick_policy=discard"
|
|
disk="64G"
|
|
;;
|
|
*)
|
|
CPU="-cpu host,kvm=on"
|
|
NET_DEVICE="rtl8139"
|
|
echo "WARNING! Unrecognised guest OS: ${guest_os}"
|
|
;;
|
|
esac
|
|
echo " - Guest: ${guest_os^} optimised"
|
|
|
|
echo " - Disk: ${disk_img} (${disk})"
|
|
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}"; 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}"
|
|
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
|
|
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
|
|
# If there is a disk image, assume there is an install and do not boot
|
|
# from installation media.
|
|
iso=""
|
|
img=""
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Has the status quo been requested?
|
|
if [ "${STATUS_QUO}" == "-snapshot" ] && [ -z "${iso}" ]; then
|
|
echo " Existing disk state will be preserved, no writes will be committed."
|
|
fi
|
|
|
|
if [ -n "${iso}" ] && [ -e "${iso}" ]; then
|
|
echo " - Boot: ${iso}"
|
|
fi
|
|
|
|
if [ -n "${fixed_iso}" ] && [ -e "${fixed_iso}" ]; then
|
|
echo " - CD-ROM: ${fixed_iso}"
|
|
fi
|
|
|
|
local CORES_VM="1"
|
|
if [ -z "$cpu_cores" ]; then
|
|
local CORES_HOST=""
|
|
CORES_HOST=$(nproc --all)
|
|
if [ "${CORES_HOST}" -ge 32 ]; then
|
|
CORES_VM="16"
|
|
elif [ "${CORES_HOST}" -ge 16 ]; then
|
|
CORES_VM="8"
|
|
elif [ "${CORES_HOST}" -ge 8 ]; then
|
|
CORES_VM="4"
|
|
elif [ "${CORES_HOST}" -ge 4 ]; then
|
|
CORES_VM="2"
|
|
fi
|
|
else
|
|
CORES_VM="$cpu_cores"
|
|
fi
|
|
local SMP="-smp ${CORES_VM},sockets=1,cores=${CORES_VM},threads=1"
|
|
echo " - CPU: ${CORES_VM} Core(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 256 ]; then
|
|
RAM_VM="32G"
|
|
elif [ "${RAM_HOST}" -ge 128 ]; then
|
|
RAM_VM="16G"
|
|
elif [ "${RAM_HOST}" -ge 64 ]; then
|
|
RAM_VM="8G"
|
|
elif [ "${RAM_HOST}" -ge 32 ]; then
|
|
RAM_VM="4G"
|
|
elif [ "${RAM_HOST}" -ge 16 ]; then
|
|
RAM_VM="3G"
|
|
fi
|
|
else
|
|
RAM_VM="$ram"
|
|
fi
|
|
echo " - RAM: ${RAM_VM}"
|
|
|
|
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
|
|
|
|
echo " - Screen: ${X_RES}x${Y_RES}"
|
|
echo " - Display: ${OUTPUT^^}"
|
|
|
|
# https://www.kraxel.org/blog/2019/09/display-devices-in-qemu/
|
|
if [ "${guest_os}" == "linux" ]; then
|
|
DISPLAY_DEVICE="virtio-vga"
|
|
elif [ "${guest_os}" == "macos" ]; then
|
|
DISPLAY_DEVICE="qxl"
|
|
elif [ "${guest_os}" == "windows" ]; then
|
|
DISPLAY_DEVICE="qxl-vga"
|
|
else
|
|
DISPLAY_DEVICE="qxl-vga"
|
|
fi
|
|
|
|
if [ "${OUTPUT}" == "spice" ]; then
|
|
if [ "${guest_os}" == "linux" ]; then
|
|
DISPLAY_DEVICE="qxl-vga"
|
|
fi
|
|
OUTPUT="none"
|
|
fi
|
|
|
|
echo " - Video: ${DISPLAY_DEVICE}"
|
|
|
|
# Build the video configuration
|
|
VIDEO="-device ${DISPLAY_DEVICE}"
|
|
|
|
# Do not try and coerce the display resolution for macOS
|
|
if [ "${guest_os}" != "macos" ]; 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
|
|
echo " - GL: ${GL^^}"
|
|
|
|
if [ "${GL}" == "on" ] && [[ "${DISPLAY_DEVICE}" == *"virtio"* ]]; then
|
|
DISPLAY_DEVICE="${DISPLAY_DEVICE},virgl=on"
|
|
echo " - Virgil3D: ON"
|
|
else
|
|
echo " - Virgil3D: 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" ]; 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" ]; then
|
|
echo " - WebDAV: On guest: dav://localhost:9843/"
|
|
fi
|
|
|
|
if [ "${guest_os}" != "windows" ]; 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} ~/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 [ -n "${PUBLIC}" ] && [ "${PUBLIC_PERMS}" != "drwxrwxrwx" ]; then
|
|
echo " - 9P: On host: chmod 777 ${PUBLIC}"
|
|
echo " Required for macOS integration 👆"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
enable_usb_passthrough
|
|
|
|
# Boot the VM
|
|
local args=()
|
|
|
|
# shellcheck disable=SC2054,SC2206,SC2140
|
|
args+=(-name ${VMNAME},process=${VMNAME}
|
|
-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 usb-tablet,bus=input.0
|
|
-device ${NET_DEVICE},netdev=nic -netdev ${NET},id=nic
|
|
-audiodev pa,id=pa,out.stream-name=${LAUNCHER}-${VMNAME},in.stream-name=${LAUNCHER}-${VMNAME}
|
|
-device intel-hda -device hda-duplex,audiodev=pa,mixer=off
|
|
-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
|
|
-monitor none
|
|
-serial mon:stdio)
|
|
|
|
if [ -n "${floppy}" ] && [ -e "${floppy}" ]; then
|
|
# shellcheck disable=SC2054
|
|
args+=(-drive file="${floppy}",index=0,if=floppy,format=raw)
|
|
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 [ "${guest_os}" == "macos" ]; then
|
|
# shellcheck disable=SC2054
|
|
args+=(-device ahci,id=ahci
|
|
-device ide-hd,bus=ahci.0,drive=OpenCore
|
|
-drive id=OpenCore,if=none,cache=none,aio=native,format=qcow2,file="${VMDIR}/OpenCore.qcow2")
|
|
|
|
if [ -n "${img}" ]; then
|
|
# shellcheck disable=SC2054
|
|
args+=(-device ide-hd,bus=ahci.1,drive=InstallMedia
|
|
-drive id=InstallMedia,if=none,cache=none,aio=native,format=raw,file="${img}")
|
|
fi
|
|
|
|
if [ "${virtio_blk}" == "on" ]; then
|
|
# shellcheck disable=SC2054
|
|
args+=(-device virtio-blk-pci,drive=SystemDisk,scsi=off
|
|
-drive id=SystemDisk,if=none,cache=none,aio=native,format=qcow2,file="${disk_img}" ${STATUS_QUO})
|
|
else
|
|
# shellcheck disable=SC2054,SC2206
|
|
args+=(-device ide-hd,bus=ahci.2,drive=SystemDisk
|
|
-drive id=SystemDisk,if=none,cache=none,aio=native,format=qcow2,file="${disk_img}" ${STATUS_QUO})
|
|
fi
|
|
|
|
if [ -n "${fixed_iso}" ]; then
|
|
# shellcheck disable=SC2054
|
|
args+=(-drive media=cdrom,index=0,file="${fixed_iso}")
|
|
fi
|
|
else
|
|
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
|
|
|
|
# shellcheck disable=SC2054,SC2206
|
|
args+=(-device virtio-blk-pci,drive=drive0,scsi=off
|
|
-drive id=drive0,if=none,cache=none,aio=native,format=qcow2,file="${disk_img}" ${STATUS_QUO}
|
|
-device qemu-xhci,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
|
|
-device virtio-rng-pci,rng=rng0
|
|
-object rng-random,id=rng0,filename=/dev/urandom)
|
|
fi
|
|
|
|
# https://wiki.qemu.org/Documentation/9psetup
|
|
# https://askubuntu.com/questions/772784/9p-libvirt-qemu-share-modes
|
|
if [ "${guest_os}" != "windows" ]; 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
|
|
|
|
echo "#!/usr/bin/env bash" > "${VMDIR}/${VMNAME}.sh"
|
|
echo "${QEMU}" "${args[@]}" >> "${VMDIR}/${VMNAME}.sh"
|
|
|
|
${QEMU} "${args[@]}" > "${VMDIR}/${VMNAME}.log" &
|
|
echo " - PID: ${!}"
|
|
|
|
# If output is 'none' then SPICE was requested.
|
|
if [ ${OUTPUT} == "none" ]; then
|
|
spicy --title "${VMNAME}" --port "${SPICE_PORT}" --spice-shared-dir "${PUBLIC}" "${FULLSPICY}" >/dev/null 2>&1 &
|
|
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="64G"
|
|
fixed_iso=""
|
|
floppy=""
|
|
guest_os="linux"
|
|
img=""
|
|
iso=""
|
|
port_forwards=()
|
|
preallocation="metadata"
|
|
ram=""
|
|
usb_devices=()
|
|
virtio_blk="on"
|
|
|
|
DELETE=0
|
|
FULLSCREEN=""
|
|
FULLSPICY=""
|
|
OUTPUT="sdl"
|
|
PUBLIC=""
|
|
PUBLIC_PERMS=""
|
|
PUBLIC_TAG=""
|
|
SCREEN=""
|
|
SHORTCUT=0
|
|
SNAPSHOT_ACTION=""
|
|
SNAPSHOT_TAG=""
|
|
STATUS_QUO=""
|
|
USB_PASSTHROUGH=""
|
|
VM=""
|
|
|
|
readonly LAUNCHER=$(basename "${0}")
|
|
readonly DISK_MIN_SIZE=$((197632 * 8))
|
|
readonly VERSION="2.1.0"
|
|
|
|
# PUBLICSHARE is the only directory exposed to guest VMs for file
|
|
# sharing via 9P and spice-webdavd. This path is not configurable.
|
|
PUBLIC=$(xdg-user-dir PUBLICSHARE)
|
|
if [ "${PUBLIC}" != ${HOME} ]; then
|
|
if [ ! -d "${PUBLIC}" ]; then
|
|
mkdir -p "${PUBLIC}"
|
|
fi
|
|
PUBLIC_TAG=$(basename ${PUBLIC})-${USER,,}
|
|
PUBLIC_PERMS=$(ls -ld ${PUBLIC} | cut -d' ' -f1)
|
|
else
|
|
PUBLIC=""
|
|
fi
|
|
|
|
# TODO: Make this run the native architecture binary
|
|
QEMU=$(which qemu-system-x86_64)
|
|
QEMU_IMG=$(which qemu-img)
|
|
if [ ! -e "${QEMU}" ] && [ ! -e "${QEMU_IMG}" ]; then
|
|
echo "ERROR! qemu not found. Please install qemu."
|
|
exit 1
|
|
fi
|
|
|
|
QEMU_VER=$(${QEMU} -version | head -n1 | cut -d' ' -f4 | cut -d'(' -f1 | sed 's/\.//g' | cut -c1-2)
|
|
if [ "${QEMU_VER}" -lt 60 ]; then
|
|
echo "ERROR! Qemu 6.0.0 or newer is required, detected $(${QEMU} -version | head -n1 | cut -d' ' -f4 | cut -d'(' -f1)."
|
|
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" ] && [ ! "$(which spicy)" ]; 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
|
|
|
|
# Backwards compatibility for ${driver_iso}
|
|
if [ -n "${driver_iso}" ] && [ -z "${fixed_iso}" ]; then
|
|
fixed_iso="${driver_iso}"
|
|
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
|