DistroHopper/quickemu

765 lines
24 KiB
Plaintext
Raw Normal View History

2020-03-15 23:13:25 +00:00
#!/usr/bin/env bash
export LC_ALL=C
2020-03-15 23:13:25 +00:00
2020-04-04 12:54:30 +01:00
function web_get() {
local URL="${1}"
local FILE=""
FILE="${URL##*/}"
2021-09-27 22:47:10 +01:00
mkdir -p "${VMDIR}" 2>/dev/null
if ! wget --quiet --continue --show-progress --progress=bar:force:noscroll "${URL}" -O "${VMDIR}/${FILE}"; then
echo "ERROR! Failed to download ${URL}"
exit 1
2020-04-04 12:54:30 +01:00
fi
}
function disk_delete() {
2020-03-20 18:12:13 +00:00
if [ -e "${disk_img}" ]; then
2020-03-15 23:13:25 +00:00
rm "${disk_img}"
echo "SUCCESS! Deleted ${disk_img}"
2020-03-20 18:12:13 +00:00
else
echo "NOTE! ${disk_img} not found. Doing nothing."
2020-03-15 23:13:25 +00:00
fi
local VMNAME=""
VMNAME=$(basename "${VM}" .conf)
local SHORTCUT_DIR="/home/${USER}/.local/share/applications/"
2021-09-06 15:29:37 +01:00
if [ -e "${SHORTCUT_DIR}/${VMNAME}.desktop" ]; then
rm -v "${SHORTCUT_DIR}/${VMNAME}.desktop"
2021-09-06 22:27:06 +01:00
echo "Deleted ${VM} desktop shortcut"
fi
2020-03-15 23:13:25 +00:00
}
2020-03-20 18:17:53 +00:00
function snapshot_apply() {
local TAG="${1}"
if [ -z "${TAG}" ]; then
2020-03-20 18:17:53 +00:00
echo "ERROR! No snapshot tag provided."
exit
fi
if [ -e "${disk_img}" ]; then
if ${QEMU_IMG} snapshot -q -a "${TAG}" "${disk_img}"; then
echo "SUCCESS! Applied snapshot ${TAG} to ${disk_img}"
2020-03-20 18:17:53 +00:00
else
echo "ERROR! Failed to apply snapshot ${TAG} to ${disk_img}"
2020-03-20 18:17:53 +00:00
fi
else
echo "NOTE! ${disk_img} not found. Doing nothing."
2020-03-15 23:13:25 +00:00
fi
}
2020-03-20 18:17:53 +00:00
function snapshot_create() {
local TAG="${1}"
if [ -z "${TAG}" ]; then
2020-03-20 18:17:53 +00:00
echo "ERROR! No snapshot tag provided."
exit
2020-03-15 23:13:25 +00:00
fi
2020-03-20 18:17:53 +00:00
if [ -e "${disk_img}" ]; then
if ${QEMU_IMG} snapshot -q -c "${TAG}" "${disk_img}"; then
echo "SUCCESS! Created snapshot ${TAG} of ${disk_img}"
2020-03-20 18:17:53 +00:00
else
echo "ERROR! Failed to create snapshot ${TAG} of ${disk_img}"
2020-03-20 18:17:53 +00:00
fi
2020-03-15 23:13:25 +00:00
else
2020-03-20 18:17:53 +00:00
echo "NOTE! ${disk_img} not found. Doing nothing."
fi
}
function snapshot_delete() {
local TAG="${1}"
if [ -z "${TAG}" ]; then
2020-03-20 18:17:53 +00:00
echo "ERROR! No snapshot tag provided."
exit
fi
if [ -e "${disk_img}" ]; then
if ${QEMU_IMG} snapshot -q -d "${TAG}" "${disk_img}"; then
echo "SUCCESS! Deleted snapshot ${TAG} of ${disk_img}"
2020-03-20 18:17:53 +00:00
else
echo "ERROR! Failed to delete snapshot ${TAG} of ${disk_img}"
2020-03-20 18:17:53 +00:00
fi
else
echo "NOTE! ${disk_img} not found. Doing nothing."
fi
}
function snapshot_info() {
if [ -e "${disk_img}" ]; then
${QEMU_IMG} info "${disk_img}"
2020-03-15 23:13:25 +00:00
fi
}
function get_port() {
local PORT_START=$1
local PORT_RANGE=$2
while true; do
2021-09-06 15:29:37 +01:00
local CANDIDATE=$((PORT_START + (RANDOM % PORT_RANGE)))
(echo "" >/dev/tcp/127.0.0.1/${CANDIDATE}) >/dev/null 2>&1
2021-09-25 13:42:04 +01:00
if [ ${?} -ne 0 ]; then
echo "${CANDIDATE}"
break
fi
done
}
2021-09-06 15:30:01 +01:00
function enable_usb_passthrough() {
local DEVICE=""
local USB_BUS=""
local USB_DEV=""
local USB_NAME=""
local VENDOR_ID=""
local PRODUCT_ID=""
local USB_NOT_READY=0
# Have any USB devices been requested for pass-through?
if (( ${#usb_devices[@]} )); then
echo " - USB: Host pass-through requested:"
for DEVICE in "${usb_devices[@]}"; do
VENDOR_ID=$(echo "${DEVICE}" | cut -d':' -f1)
PRODUCT_ID=$(echo "${DEVICE}" | cut -d':' -f2)
USB_BUS=$(lsusb -d "${VENDOR_ID}:${PRODUCT_ID}" | cut -d' ' -f2)
USB_DEV=$(lsusb -d "${VENDOR_ID}:${PRODUCT_ID}" | cut -d' ' -f4 | cut -d':' -f1)
USB_NAME=$(lsusb -d "${VENDOR_ID}:${PRODUCT_ID}" | cut -d' ' -f7-)
if [ -w "/dev/bus/usb/${USB_BUS}/${USB_DEV}" ]; then
echo " o ${USB_NAME} on bus ${USB_BUS} device ${USB_DEV} is accessible."
else
echo " x ${USB_NAME} on bus ${USB_BUS} device ${USB_DEV} needs permission changes:"
echo " sudo chown -v root:${USER} /dev/bus/usb/${USB_BUS}/${USB_DEV}"
USB_NOT_READY=1
fi
2021-09-28 02:14:30 +01:00
USB_PASSTHROUGH="${USB_PASSTHROUGH} -device usb-host,bus=hostpass.0,vendorid=0x${VENDOR_ID},productid=0x${PRODUCT_ID}"
done
if [ "${USB_NOT_READY}" -eq 1 ]; then
echo " ERROR! USB permission changes are required 👆"
exit 1
fi
fi
}
2020-03-15 23:13:25 +00:00
function vm_boot() {
local VMNAME=""
VMNAME=$(basename "${VM}" .conf)
local VMDIR=""
VMDIR=$(dirname "${disk_img}")
2021-09-28 15:27:02 +01:00
local BALLOON="-device virtio-balloon"
local CPU=""
local DISPLAY_DEVICE=""
local GL="on"
local GUEST_TWEAKS=""
2021-09-27 22:46:26 +01:00
local HOST_CPU=""
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)
2021-09-28 17:43:38 +01:00
echo "Quickemu ${VERSION} starting ${VM}"
echo " - QEMU: ${QEMU} v${QEMU_VER}"
# Force to lowercase.
boot=${boot,,}
2020-04-04 12:54:30 +01:00
2020-05-06 20:23:30 +01:00
# Always Boot macOS using EFI
2020-04-04 12:54:30 +01:00
if [ "${guest_os}" == "macos" ]; then
boot="efi"
fi
if [ "${boot}" == "efi" ] || [ "${boot}" == "uefi" ]; then
2021-09-06 22:21:06 +01:00
if [ -e "/usr/share/OVMF/OVMF_CODE_4M.fd" ] ; then
2020-04-04 12:54:30 +01:00
if [ "${guest_os}" == "macos" ]; then
2021-09-25 22:50:00 +01:00
web_get "https://github.com/kholia/OSX-KVM/raw/master/OpenCore-Catalina/OpenCore.qcow2"
web_get "https://github.com/kholia/OSX-KVM/raw/master/OVMF_CODE.fd"
web_get "https://github.com/kholia/OSX-KVM/raw/master/OVMF_VARS-1024x768.fd"
2020-04-04 12:54:30 +01:00
local EFI_CODE="${VMDIR}/OVMF_CODE.fd"
local EFI_VARS="${VMDIR}/OVMF_VARS-1024x768.fd"
else
2021-09-06 22:21:06 +01:00
local EFI_CODE="/usr/share/OVMF/OVMF_CODE_4M.fd"
2020-04-04 12:54:30 +01:00
local EFI_VARS="${VMDIR}/${VMNAME}-vars.fd"
if [ ! -e "${EFI_VARS}" ]; then
2021-09-06 22:21:06 +01:00
cp "/usr/share/OVMF/OVMF_VARS_4M.fd" "${EFI_VARS}"
2020-04-04 12:54:30 +01:00
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,,}
2021-09-27 22:46:26 +01:00
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"
;;
2020-04-04 12:54:30 +01:00
macos)
2021-09-28 15:27:02 +01:00
BALLOON=""
2021-09-27 22:52:56 +01:00
#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" | rot13)
2020-04-04 12:54:30 +01:00
GUEST_TWEAKS="-device isa-applesmc,osk=${OSK}"
disk="64G"
NET_DEVICE="vmxnet3"
USB_HOST_PASSTHROUGH_CONTROLLER="usb-ehci"
2020-04-04 12:54:30 +01:00
;;
windows)
2021-09-28 17:37:40 +01:00
CPU="-cpu host,kvm=on,-hypervisor,+invtsc,l3-cache=on,migratable=no,hv_vapic,hv_relaxed,hv_spinlocks=8191,hv_stimer,hv_synic,hv_time,hv_vendor_id=1234567890ab,hv_vpindex"
GUEST_TWEAKS="-no-hpet"
disk="64G"
;;
*)
CPU="-cpu host,kvm=on"
NET_DEVICE="rtl8139"
echo "WARNING! Unrecognised guest OS: ${guest_os}"
;;
esac
2020-03-21 12:00:49 +00:00
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
2020-04-04 12:54:30 +01:00
if [ -z "${iso}" ] && [ -z "${img}" ]; then
echo "ERROR! You haven't specified a .iso or .img image to boot from."
exit 1
fi
2020-04-04 12:54:30 +01:00
echo " Just created, booting from ${iso}${img}"
elif [ -e "${disk_img}" ]; then
# Check there isn't already a process attached to the disk image.
if ! ${QEMU_IMG} info "${disk_img}" >/dev/null; then
echo " Failed to get \"write\" lock. Is another process using the disk?"
exit 1
2020-03-20 15:31:24 +00:00
else
DISK_CURR_SIZE=$(stat -c%s "${disk_img}")
if [ "${DISK_CURR_SIZE}" -le "${DISK_MIN_SIZE}" ]; then
2020-04-04 12:54:30 +01:00
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
2020-04-04 12:54:30 +01:00
# then do not boot from the iso/img
iso=""
2020-04-04 12:54:30 +01:00
img=""
fi
2020-03-20 15:31:24 +00:00
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
2021-09-06 15:30:41 +01:00
CORES_VM="16"
elif [ "${CORES_HOST}" -ge 16 ]; then
2021-09-06 15:30:41 +01:00
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
2020-04-04 12:54:30 +01:00
local SMP="-smp ${CORES_VM},sockets=1,cores=${CORES_VM},threads=1"
echo " - CPU: ${CORES_VM} Core(s)"
2020-03-21 10:49:42 +00:00
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
2021-09-06 15:31:07 +01:00
RAM_VM="32G"
elif [ "${RAM_HOST}" -ge 128 ]; then
2021-09-06 15:31:07 +01:00
RAM_VM="16G"
elif [ "${RAM_HOST}" -ge 64 ]; then
2021-09-06 15:31:07 +01:00
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
2020-03-21 10:49:42 +00:00
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
2021-09-28 17:38:06 +01:00
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"
2021-09-25 13:08:56 +01:00
# GL is not working with GTK and virtio-vga
if [ "${DISPLAY_DEVICE}" == "virtio-vga" ]; then
GL="off"
fi
fi
2020-03-20 20:35:11 +00:00
if [ "${OUTPUT}" != "none" ]; then
OUTPUT="${OUTPUT},gl=${GL}"
fi
echo " - GL: ${GL^^}"
if [ "${GL}" == "on" ] && [[ "${DISPLAY_DEVICE}" == *"virtio"* ]]; then
2021-09-25 13:08:56 +01:00
DISPLAY_DEVICE="${DISPLAY_DEVICE},virgl=on"
echo " - Virgil3D: ON"
else
2021-09-25 13:08:56 +01:00
echo " - Virgil3D: OFF"
fi
# Set the hostname of the VM
local NET="user,hostname=${VMNAME}"
2020-03-19 17:29:01 +00:00
# Find a free port to expose ssh to the guest
2021-09-25 13:14:32 +01:00
local SSH_PORT=""
SSH_PORT=$(get_port 22220 9)
if [ -n "${SSH_PORT}" ]; then
NET="${NET},hostfwd=tcp::${SSH_PORT}-:22"
echo " - ssh: ${SSH_PORT}/tcp is connected. Login via 'ssh user@localhost -p ${SSH_PORT}'"
2020-03-19 17:29:01 +00:00
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
# Boot the VM
local args=()
2021-09-28 17:44:02 +01:00
# shellcheck disable=SC2054,SC2206,SC2140
args+=(-name ${VMNAME},process=${VMNAME}
-enable-kvm -machine q35,vmport=off ${GUEST_TWEAKS}
${CPU} ${SMP}
2021-09-28 15:48:18 +01:00
-m ${RAM_VM} ${BALLOON}
2021-09-28 17:44:02 +01:00
-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
-spice ${SPICE}
-device virtio-serial-pci
-chardev spicevmc,id=vdagent0,name=vdagent
-device virtserialport,chardev=vdagent0,name=com.redhat.spice.0
2021-09-28 15:48:18 +01:00
-monitor none
-serial mon:stdio)
# Add the disks
if [ "${boot}" == "efi" ] || [ "${boot}" == "uefi" ]; then
# shellcheck disable=SC2054
args+=(-drive if=pflash,format=raw,readonly=on,file="${EFI_CODE}"
-drive if=pflash,format=raw,file="${EFI_VARS}")
fi
if [ "${guest_os}" == "macos" ]; then
# shellcheck disable=SC2054
args+=(-device ahci,id=ahci
2021-09-28 15:50:24 +01:00
-device ide-hd,bus=ahci.0,drive=OpenCore
-drive id=OpenCore,if=none,format=qcow2,file="${VMDIR}/OpenCore.qcow2")
if [ -n "${img}" ]; then
# shellcheck disable=SC2054
2021-09-28 15:50:24 +01:00
args+=(-device ide-hd,bus=ahci.1,drive=InstallMedia
-drive id=InstallMedia,if=none,format=raw,file="${img}")
2021-09-06 22:26:48 +01:00
fi
if [ "${virtio_blk}" == "on" ]; then
# shellcheck disable=SC2054
args+=(-device virtio-blk-pci,drive=SystemDisk,scsi=off
-drive id=SystemDisk,if=none,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,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
2021-09-28 15:50:24 +01:00
args+=(-device virtio-blk-pci,drive=drive0,scsi=off
-drive id=drive0,if=none,cache=directsync,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
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"
2021-09-28 15:51:06 +01:00
${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}" "${FULLSPICY}" --spice-shared-dir "${HOME}" >/dev/null 2>&1
2021-09-06 22:26:48 +01:00
fi
2020-03-15 23:13:25 +00:00
}
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}
2021-09-06 22:21:06 +01:00
Icon=/usr/share/icons/hicolor/scalable/apps/qemu.svg
EOF
echo "Created ${VMNAME}.desktop file"
}
2020-03-15 23:13:25 +00:00
function usage() {
echo
echo "Usage"
echo " ${LAUNCHER} --vm ubuntu.conf"
echo
echo "You can also pass optional parameters"
2020-03-20 18:22:03 +00:00
echo " --delete : Delete the disk image."
echo " --display : Select display backend. 'sdl' (default), 'gtk' or 'spice'"
echo " --shortcut : Create a desktop shortcut"
2020-03-20 18:22:03 +00:00
echo " --snapshot apply <tag> : Apply/restore a snapshot."
echo " --snapshot create <tag> : Create a snapshot."
echo " --snapshot delete <tag> : Delete a snapshot."
echo " --snapshot info : Show disk/snapshot info."
echo " --status-quo : Do not commit any changes to disk/snapshot."
2021-09-25 13:40:52 +01:00
echo " --fullscreen : Starts VM in full screen mode (Ctl+Alt+f to exit)"
echo " --screen <screen> : Use specified screen to determine the window size."
2021-09-28 17:43:38 +01:00
echo " --version : Print version"
2020-03-15 23:13:25 +00:00
exit 1
}
# Lowercase variables are used in the VM config file only
2021-09-25 23:25:18 +01:00
boot="efi"
2021-09-25 13:57:22 +01:00
cpu_cores=""
disk_img=""
disk="64G"
fixed_iso=""
guest_os="linux"
2020-04-04 12:54:30 +01:00
img=""
iso=""
2021-09-25 13:42:04 +01:00
port_forwards=()
ram=""
2021-09-25 13:57:22 +01:00
usb_devices=()
virtio_blk="on"
2021-09-25 13:57:22 +01:00
DELETE=0
FULLSCREEN=""
FULLSPICY=""
OUTPUT="sdl"
2021-09-25 13:57:22 +01:00
SCREEN=""
SHORTCUT=0
2020-03-20 18:17:53 +00:00
SNAPSHOT_ACTION=""
SNAPSHOT_TAG=""
STATUS_QUO=""
2021-09-06 15:27:49 +01:00
USB_PASSTHROUGH=""
2020-03-15 23:13:25 +00:00
VM=""
readonly LAUNCHER=$(basename "${0}")
readonly DISK_MIN_SIZE=$((197632 * 8))
2021-09-28 17:43:38 +01:00
readonly VERSION="2.1.0"
2021-09-06 22:21:06 +01:00
# 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
2020-03-15 23:13:25 +00:00
QEMU_VER=$(${QEMU} -version | head -n1 | cut -d' ' -f4 | cut -d'(' -f1 | sed 's/\.//g' | cut -c1-2)
if [ "${QEMU_VER}" -lt 60 ]; then
2021-09-10 13:25:37 +01:00
echo "ERROR! Qemu 6.0.0 or newer is required, detected $(${QEMU} -version | head -n1 | cut -d' ' -f4 | cut -d'(' -f1)."
exit 1
fi
2020-08-10 13:07:45 +01:00
# Take command line arguments
if [ $# -lt 1 ]; then
usage
exit 0
else
while [ $# -gt 0 ]; do
case "${1}" in
-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;;
2020-08-10 13:07:45 +01:00
-snapshot|--snapshot)
SNAPSHOT_ACTION="${2}"
if [ -z "${SNAPSHOT_ACTION}" ]; then
echo "ERROR! No snapshot action provided."
exit 1
fi
shift
SNAPSHOT_TAG="${2}"
if [ -z "${SNAPSHOT_TAG}" ] && [ "${SNAPSHOT_ACTION}" != "info" ]; then
echo "ERROR! No snapshot tag provided."
exit 1
fi
shift
shift;;
-status-quo|--status-quo)
STATUS_QUO="-snapshot"
shift;;
-fullscreen|--fullscreen|-full-screen|--full-screen)
FULLSCREEN="-full-screen"
FULLSPICY="--full-screen"
2020-08-10 13:07:45 +01:00
shift;;
-vm|--vm)
VM="${2}"
shift
shift;;
-screen|--screen)
SCREEN="${2}"
shift
shift;;
2020-08-10 13:07:45 +01:00
-shortcut|--shortcut)
SHORTCUT=1
shift;;
2021-09-28 17:43:38 +01:00
-version|--version)
echo "${VERSION}"
exit;;
2020-08-10 13:07:45 +01:00
-h|--h|-help|--help)
usage;;
*)
echo "ERROR! \"${1}\" is not a supported parameter."
usage;;
esac
done
fi
2020-03-15 23:13:25 +00:00
if [ -n "${VM}" ] && [ -e "${VM}" ]; then
# shellcheck source=/dev/null
source "${VM}"
2020-03-20 18:16:08 +00:00
if [ -z "${disk_img}" ]; then
echo "ERROR! No disk_img defined."
exit 1
fi
# 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
2020-03-15 23:13:25 +00:00
fi
if [ ${DELETE} -eq 1 ]; then
disk_delete
2020-03-20 18:17:53 +00:00
exit
2020-03-15 23:13:25 +00:00
fi
2020-03-20 18:17:53 +00:00
if [ -n "${SNAPSHOT_ACTION}" ]; then
case ${SNAPSHOT_ACTION} in
apply)
snapshot_apply "${SNAPSHOT_TAG}"
snapshot_info
exit;;
create)
snapshot_create "${SNAPSHOT_TAG}"
snapshot_info
exit;;
delete)
snapshot_delete "${SNAPSHOT_TAG}"
snapshot_info
exit;;
info)
snapshot_info
exit;;
*)
echo "ERROR! \"${SNAPSHOT_ACTION}\" is not a supported snapshot action."
usage;;
esac
2020-03-15 23:13:25 +00:00
fi
if [ ${SHORTCUT} -eq 1 ]; then
shortcut_create
exit
fi
vm_boot