mirror of
https://github.com/oSoWoSo/DistroHopper.git
synced 2024-08-14 22:46:53 +00:00
df4f12a3ad
USB host pass-through no longer builds a script and executes it. It will now display any required USB permission changes and halt execution until they are correct.
714 lines
22 KiB
Bash
Executable file
714 lines
22 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
export LC_ALL=C
|
|
|
|
function web_get() {
|
|
local URL="${1}"
|
|
local FILE=""
|
|
FILE="${URL##*/}"
|
|
if [ ! -e "${VMDIR}/${FILE}" ]; then
|
|
if ! wget -q -c "${URL}" -O "${VMDIR}/${FILE}"; then
|
|
echo "ERROR! Failed to download ${URL}"
|
|
exit 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
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/${USER}/.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,vendorid=0x${VENDOR_ID},productid=0x${PRODUCT_ID},bus=hostpass.0"
|
|
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 CPU=""
|
|
local DISPLAY_DEVICE=""
|
|
local GL="on"
|
|
local GUEST_TWEAKS=""
|
|
local OSK=""
|
|
local QEMU_VER=""
|
|
local VIDEO=""
|
|
QEMU_VER=$(${QEMU} -version | head -n1 | cut -d' ' -f4 | cut -d'(' -f1)
|
|
echo "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"
|
|
fi
|
|
|
|
if [ "${boot}" == "efi" ] || [ "${boot}" == "uefi" ]; then
|
|
if [ -e "/usr/share/OVMF/OVMF_CODE_4M.fd" ] ; then
|
|
if [ "${guest_os}" == "macos" ]; then
|
|
web_get "https://github.com/foxlet/macOS-Simple-KVM/raw/master/ESP.qcow2"
|
|
web_get "https://github.com/foxlet/macOS-Simple-KVM/raw/master/firmware/OVMF_CODE.fd"
|
|
web_get "https://github.com/foxlet/macOS-Simple-KVM/raw/master/firmware/OVMF_VARS-1024x768.fd"
|
|
local EFI_CODE="${VMDIR}/OVMF_CODE.fd"
|
|
local EFI_VARS="${VMDIR}/OVMF_VARS-1024x768.fd"
|
|
else
|
|
local EFI_CODE="/usr/share/OVMF/OVMF_CODE_4M.fd"
|
|
local EFI_VARS="${VMDIR}/${VMNAME}-vars.fd"
|
|
if [ ! -e "${EFI_VARS}" ]; then
|
|
cp "/usr/share/OVMF/OVMF_VARS_4M.fd" "${EFI_VARS}"
|
|
fi
|
|
fi
|
|
echo " - BOOT: EFI"
|
|
else
|
|
echo " - BOOT: Legacy BIOS"
|
|
echo " - EFI Booting requested but no EFI firmware found."
|
|
fi
|
|
else
|
|
echo " - BOOT: Legacy BIOS"
|
|
fi
|
|
|
|
# Force to lowercase.
|
|
guest_os=${guest_os,,}
|
|
# Make any OS specific adjustments
|
|
case ${guest_os} in
|
|
linux)
|
|
CPU="-cpu host,kvm=on"
|
|
;;
|
|
macos)
|
|
CPU="-cpu Penryn,vendor=GenuineIntel,kvm=on,+aes,+avx,+avx2,+bmi1,+bmi2,+fma,+invtsc,+movbe,+pcid,+smep,+sse3,+sse4.2,+xgetbv1,+xsave,+xsavec,+xsaveopt"
|
|
OSK=$(echo "bheuneqjbexolgurfrjbeqfthneqrqcyrnfrqbagfgrny(p)NccyrPbzchgreVap" | rot13)
|
|
GUEST_TWEAKS="-device isa-applesmc,osk=${OSK}"
|
|
;;
|
|
windows)
|
|
CPU="-cpu host,kvm=on,hv_time"
|
|
GUEST_TWEAKS="-no-hpet"
|
|
;;
|
|
*)
|
|
CPU="-cpu host,kvm=on"
|
|
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
|
|
if ! ${QEMU_IMG} create -q -f qcow2 "${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, that appears to have an install
|
|
# then do not boot from the iso/img
|
|
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 "${driver_iso}" ] && [ -e "${driver_iso}" ]; then
|
|
echo " - Drivers: ${driver_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="VGA"
|
|
#DISPLAY_DEVICE="bochs-display"
|
|
elif [ "${guest_os}" == "windows" ]; then
|
|
DISPLAY_DEVICE="qxl-vga"
|
|
else
|
|
DISPLAY_DEVICE="qxl-vga"
|
|
fi
|
|
|
|
if [ "${OUTPUT}" == "spice" ]; then
|
|
if [ "${guest_os}" != "macos" ]; then
|
|
DISPLAY_DEVICE="qxl-vga"
|
|
fi
|
|
OUTPUT="none"
|
|
fi
|
|
|
|
echo " - Video: ${DISPLAY_DEVICE}"
|
|
|
|
# Allocate VRAM to VGA devices
|
|
if [ "${DISPLAY_DEVICE}" == "qxl-vga" ] || [ "${DISPLAY_DEVICE}" == "VGA" ]; then
|
|
VIDEO="-device ${DISPLAY_DEVICE},xres=${X_RES},yres=${Y_RES},vgamem_mb=128 ${FULLSCREEN}"
|
|
else
|
|
VIDEO="-device ${DISPLAY_DEVICE},xres=${X_RES},yres=${Y_RES} ${FULLSCREEN}"
|
|
fi
|
|
|
|
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-vga" ]; 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: ${SSH_PORT}/tcp is connected. Login via 'ssh user@localhost -p ${SSH_PORT}'"
|
|
else
|
|
echo " - ssh: All ports for exposing ssh 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 " - SPICE: ${SPICE_PORT}/tcp is connected. Login via 'spicy --title \"${VMNAME}\" --port ${SPICE_PORT} --spice-shared-dir ${HOME}' ${FULLSPICY}"
|
|
SPICE="${SPICE},port=${SPICE_PORT}"
|
|
fi
|
|
|
|
# Reference: https://gitlab.gnome.org/GNOME/phodav/-/issues/5
|
|
if [ "${guest_os}" != "macos" ]; then
|
|
echo " - WebDAV: ${HOME} will be exported to ${VMNAME} via dav://localhost:9843/"
|
|
fi
|
|
fi
|
|
|
|
enable_usb_passthrough
|
|
|
|
# Build the VM configuration
|
|
local DISKS=""
|
|
local NET_DEVICE=""
|
|
local SMARTCARD=""
|
|
local USB_HOSTPASS=""
|
|
local USB_SPICEPASS=""
|
|
|
|
if [ "${guest_os}" == "macos" ]; then
|
|
DISKS="-drive if=pflash,format=raw,readonly=on,file=${EFI_CODE}
|
|
-drive if=pflash,format=raw,file=${EFI_VARS}
|
|
-drive id=ESP,cache=directsync,aio=native,if=none,format=qcow2,file=${VMDIR}/ESP.qcow2"
|
|
if [ -n "${img}" ]; then
|
|
DISKS="${DISKS}
|
|
-drive id=InstallMedia,cache=directsync,aio=native,if=none,format=raw,readonly=on,file=${img}
|
|
-device virtio-blk-pci,drive=InstallMedia,scsi=off"
|
|
fi
|
|
DISKS="${DISKS}
|
|
-device virtio-blk-pci,drive=ESP,scsi=off
|
|
-drive id=SystemDisk,cache=directsync,aio=native,if=none,format=qcow2,file=${disk_img} ${STATUS_QUO}
|
|
-device virtio-blk-pci,drive=SystemDisk,scsi=off"
|
|
NET_DEVICE="vmxnet3"
|
|
# UNTESTED! USB2 passthrough since USB3 isn't(?) supported in macOS VMs
|
|
# USB_SPICEPASS="
|
|
# -device usb-ehci,id=spicepass
|
|
# -device ich9-usb-uhci1,masterbus=spicepass.0,firstport=0,multifunction=on
|
|
# -device ich9-usb-uhci2,masterbus=spicepass.0,firstport=2
|
|
# -device ich9-usb-uhci3,masterbus=spicepass.0,firstport=4
|
|
# -chardev spicevmc,name=usbredir,id=usbredirchardev1
|
|
# -device usb-redir,chardev=usbredirchardev1,id=usbredirdev1
|
|
# -chardev spicevmc,name=usbredir,id=usbredirchardev2
|
|
# -device usb-redir,chardev=usbredirchardev2,id=usbredirdev2
|
|
# -chardev spicevmc,name=usbredir,id=usbredirchardev3
|
|
# -device usb-redir,chardev=usbredirchardev3,id=usbredirdev3"
|
|
USB_HOSTPASS="-device usb-ehci,id=hostpass
|
|
${USB_PASSTHROUGH}"
|
|
else
|
|
if [ "${boot}" == "efi" ] || [ "${boot}" == "uefi" ]; then
|
|
DISKS="-drive if=pflash,format=raw,readonly=on,file=${EFI_CODE}
|
|
-drive if=pflash,format=raw,file=${EFI_VARS}"
|
|
fi
|
|
DISKS="${DISKS}
|
|
-drive media=cdrom,index=0,file=${iso}
|
|
-drive media=cdrom,index=1,file=${driver_iso}
|
|
-drive if=none,id=drive0,cache=directsync,aio=native,format=qcow2,file=${disk_img}
|
|
-device virtio-blk-pci,drive=drive0,scsi=off ${STATUS_QUO}"
|
|
NET_DEVICE="virtio-net"
|
|
SMARTCARD="-device usb-ccid
|
|
-chardev spicevmc,id=ccid,name=smartcard
|
|
-device ccid-card-passthru,chardev=ccid"
|
|
USB_SPICEPASS="-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"
|
|
USB_HOSTPASS="-device qemu-xhci,id=hostpass
|
|
${USB_PASSTHROUGH}"
|
|
fi
|
|
|
|
# Boot the VM
|
|
${QEMU} \
|
|
-name ${VMNAME},process=${VMNAME} \
|
|
-enable-kvm -machine q35,vmport=off ${GUEST_TWEAKS} \
|
|
${CPU} ${SMP} \
|
|
-m ${RAM_VM} -device virtio-balloon \
|
|
${DISKS} \
|
|
${VIDEO} -display ${OUTPUT} \
|
|
-device usb-ehci,id=input \
|
|
-device usb-kbd,bus=input.0 \
|
|
-device usb-tablet,bus=input.0 \
|
|
${USB_SPICEPASS} \
|
|
${USB_HOSTPASS} \
|
|
${SMARTCARD} \
|
|
-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 \
|
|
-object rng-random,id=rng0,filename=/dev/urandom \
|
|
-device virtio-rng-pci,rng=rng0 \
|
|
-spice ${SPICE} \
|
|
-device virtio-serial-pci \
|
|
-chardev spicevmc,id=vdagent0,name=vdagent \
|
|
-device virtserialport,chardev=vdagent0,name=com.redhat.spice.0 \
|
|
-device virtio-serial-pci \
|
|
-chardev spiceport,id=webdav0,name=org.spice-space.webdav.0 \
|
|
-device virtserialport,chardev=webdav0,name=org.spice-space.webdav.0 \
|
|
-serial mon:stdio > "${VMDIR}/${VMNAME}.log" &
|
|
echo " - PID: ${!}"
|
|
|
|
# If output is 'none' then SPICE was requested.
|
|
if [ ${OUTPUT} == "none" ]; then
|
|
spicy --title "${VMNAME}" --port ${SPICE_PORT} ${FULLSPICY} --spice-shared-dir "${HOME}" >/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/${USER}/.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"
|
|
echo " --no-smb : Do not expose the home directory via SMB."
|
|
exit 1
|
|
}
|
|
|
|
# Lowercase variables are used in the VM config file only
|
|
boot="legacy"
|
|
guest_os="linux"
|
|
img=""
|
|
iso=""
|
|
driver_iso=""
|
|
disk_img=""
|
|
disk="64G"
|
|
usb_devices=()
|
|
ram=""
|
|
cpu_cores=""
|
|
|
|
FULLSCREEN=""
|
|
FULLSPICY=""
|
|
DELETE=0
|
|
OUTPUT="sdl"
|
|
SNAPSHOT_ACTION=""
|
|
SNAPSHOT_TAG=""
|
|
STATUS_QUO=""
|
|
USB_PASSTHROUGH=""
|
|
VM=""
|
|
SHORTCUT=0
|
|
SCREEN=""
|
|
|
|
readonly LAUNCHER=$(basename "${0}")
|
|
readonly DISK_MIN_SIZE=$((197632 * 8))
|
|
|
|
# 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')
|
|
if [ "${QEMU_VER}" -lt 600 ]; 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;;
|
|
-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
|
|
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
|