mirror of
https://github.com/oSoWoSo/DistroHopper.git
synced 2024-08-14 22:46:53 +00:00
735e3a4ee8
File handling is more robust and will support filename and directories with spaces in them.
448 lines
No EOL
13 KiB
Bash
Executable file
448 lines
No EOL
13 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
|
|
}
|
|
|
|
function snapshot_apply() {
|
|
local TAG="${1}"
|
|
if [ -z "${TAG}" ]; then
|
|
echo "ERROR! No snapshot tag provided."
|
|
exit
|
|
fi
|
|
|
|
if [ -e "${disk_img}" ]; then
|
|
${QEMU_IMG} snapshot -q -a "${TAG}" "${disk_img}"
|
|
if [ $? -eq 0 ]; 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
|
|
${QEMU_IMG} snapshot -q -c "${TAG}" "${disk_img}"
|
|
if [ $? -eq 0 ]; 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
|
|
${QEMU_IMG} snapshot -q -d "${TAG}" "${disk_img}"
|
|
if [ $? -eq 0 ]; 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=22220
|
|
local PORT_RANGE=9
|
|
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 vm_boot() {
|
|
local VMNAME=$(basename "${VM}" .conf)
|
|
local VMDIR=$(dirname "${disk_img}")
|
|
local BIOS=""
|
|
local CPU="-cpu host,kvm=on"
|
|
local GUEST_TWEAKS=""
|
|
local DISPLAY_DEVICE=""
|
|
local VIDEO=""
|
|
local GL="on"
|
|
local VIRGL="on"
|
|
local OUTPUT="sdl"
|
|
local OUTPUT_EXTRA=""
|
|
local 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=$(echo ${boot,,})
|
|
if [ "${boot}" == "efi" ] || [ "${boot}" == "uefi" ]; then
|
|
if [ -e "${VIRGIL_PATH}/usr/share/qemu/edk2-x86_64-code.fd" ] ; then
|
|
local EFI_CODE="${VIRGIL_PATH}/usr/share/qemu/edk2-x86_64-code.fd"
|
|
local EFI_VARS="${VMDIR}/${VMNAME}-vars.fd"
|
|
if [ ! -e "${EFI_VARS}" ]; then
|
|
cp "${VIRGIL_PATH}/usr/share/qemu/edk2-i386-vars.fd" "${EFI_VARS}"
|
|
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=$(echo ${guest_os,,})
|
|
# Make any OS specific adjustments
|
|
case ${guest_os} in
|
|
linux)
|
|
DISPLAY_DEVICE="virtio-vga"
|
|
;;
|
|
windows)
|
|
CPU="${CPU},hv_time"
|
|
GUEST_TWEAKS="-no-hpet"
|
|
DISPLAY_DEVICE="qxl-vga"
|
|
;;
|
|
*)
|
|
echo "ERROR! Unrecognised guest OS: ${guest_os}"
|
|
exit
|
|
;;
|
|
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
|
|
${QEMU_IMG} create -q -f qcow2 "${disk_img}" "${disk}"
|
|
if [ $? -ne 0 ]; then
|
|
echo "ERROR! Failed to create ${disk_img}"
|
|
exit 1
|
|
fi
|
|
if [ -z "${iso}" ]; then
|
|
echo "ERROR! You haven't specified a .iso image to boot from."
|
|
exit 1
|
|
fi
|
|
echo " Just created, booting from ${iso}"
|
|
if [ $? -ne 0 ]; then
|
|
echo "ERROR! Failed to create ${disk_img} of ${disk}. Stopping here."
|
|
exit 1
|
|
fi
|
|
elif [ -e "${disk_img}" ]; then
|
|
# Check there isn't already a process attached to the disk image.
|
|
QEMU_LOCK_TEST=$(${QEMU_IMG} info "${disk_img}" 2>/dev/null)
|
|
if [ $? -ne 0 ]; 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}"
|
|
if [ -z "${iso}" ]; then
|
|
echo "ERROR! You haven't specified a .iso 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
|
|
iso=""
|
|
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"
|
|
local CORES_HOST=$(nproc --all)
|
|
if [ ${CORES_HOST} -ge 8 ]; then
|
|
CORES_VM="4"
|
|
elif [ ${CORES_HOST} -ge 4 ]; then
|
|
CORES_VM="2"
|
|
fi
|
|
echo " - CPU: ${CORES_VM} Core(s)"
|
|
|
|
local RAM_VM="2G"
|
|
local 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 64 ]; then
|
|
RAM_VM="4G"
|
|
elif [ ${RAM_HOST} -ge 16 ]; then
|
|
RAM_VM="3G"
|
|
fi
|
|
echo " - RAM: ${RAM_VM}"
|
|
|
|
local X_RES=1152
|
|
local Y_RES=648
|
|
if [ "${XDG_SESSION_TYPE}" == "x11" ]; then
|
|
local LOWEST_WIDTH=$(xrandr --listmonitors | grep -v Monitors | cut -d' ' -f4 | cut -d'/' -f1 | sort | head -n1)
|
|
if [ ${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
|
|
|
|
# GL is not working with GTK currently
|
|
if [ "${OUTPUT}" == "gtk" ]; then
|
|
GL="es"
|
|
OUTPUT_EXTRA=",grab-on-hover=on,zoom-to-fit=on"
|
|
else
|
|
echo " - Screen: ${X_RES}x${Y_RES}"
|
|
fi
|
|
|
|
if [ "${DISPLAY_DEVICE}" == "qxl-vga" ]; then
|
|
VIDEO="-device ${DISPLAY_DEVICE},xres=${X_RES},yres=${Y_RES}"
|
|
elif [ "${DISPLAY_DEVICE}" == "virtio-vga" ]; then
|
|
VIDEO="-device ${DISPLAY_DEVICE},virgl=${VIRGL},xres=${X_RES},yres=${Y_RES}"
|
|
else
|
|
VIDEO=" -device VGA,vgamem_mb=32,xres=${X_RES},yres=${Y_RES}"
|
|
fi
|
|
|
|
echo " - Video: ${DISPLAY_DEVICE}"
|
|
echo " - GL: ${GL^^}"
|
|
echo " - Virgil3D: ${VIRGL^^}"
|
|
echo " - Display: ${OUTPUT^^}"
|
|
|
|
# Set the hostname of the VM
|
|
local NET="user,hostname=${VMNAME}"
|
|
|
|
# If smbd is available, export $HOME to the guest via samba
|
|
if [ -e "${VIRGIL_PATH}/usr/sbin/smbd" ]; then
|
|
NET="${NET},smb=${HOME}"
|
|
fi
|
|
|
|
if [[ ${NET} == *"smb"* ]]; then
|
|
echo " - smbd: ${HOME} will be exported to the guest via smb://10.0.2.4/qemu"
|
|
else
|
|
echo " - smbd: ${HOME} will not be exported to the guest. 'smbd' not found."
|
|
fi
|
|
|
|
# Find a free port to expose ssh to the guest
|
|
local PORT=$(get_port)
|
|
if [ -n "${PORT}" ]; then
|
|
NET="${NET},hostfwd=tcp::${PORT}-:22"
|
|
echo " - ssh: ${PORT}/tcp is connected. Login via 'ssh user@localhost -p ${PORT}'"
|
|
else
|
|
echo " - ssh: All ports for exposing ssh have been exhausted."
|
|
fi
|
|
|
|
# Boot the iso image
|
|
if [ "${boot}" == "efi" ] || [ "${boot}" == "uefi" ]; then
|
|
${QEMU} \
|
|
-name ${VMNAME},process=${VMNAME} \
|
|
-enable-kvm -machine q35 ${GUEST_TWEAKS} \
|
|
${CPU} -smp ${CORES_VM} \
|
|
-m ${RAM_VM} -device virtio-balloon \
|
|
-drive if=pflash,format=raw,readonly,file="${EFI_CODE}" \
|
|
-drive if=pflash,format=raw,file="${EFI_VARS}" \
|
|
-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} \
|
|
${VIDEO} -display ${OUTPUT},gl=${GL}${OUTPUT_EXTRA} \
|
|
-device qemu-xhci,id=xhci,p2=8,p3=8 -device usb-kbd,bus=xhci.0 -device usb-tablet,bus=xhci.0 ${USB_PASSTHROUGH} \
|
|
-device virtio-net,netdev=nic -netdev ${NET},id=nic \
|
|
-audiodev pa,id=pa,server=unix:${XDG_RUNTIME_DIR}/pulse/native,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 port=5930,disable-ticketing \
|
|
-device virtio-serial-pci \
|
|
-device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0 \
|
|
-chardev spicevmc,id=spicechannel0,name=vdagent \
|
|
-serial mon:stdio \
|
|
"${@}"
|
|
else
|
|
${QEMU} \
|
|
-name ${VMNAME},process=${VMNAME} \
|
|
-enable-kvm -machine q35 ${GUEST_TWEAKS} \
|
|
${CPU} -smp ${CORES_VM} \
|
|
-m ${RAM_VM} -device virtio-balloon \
|
|
-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} \
|
|
${VIDEO} -display ${OUTPUT},gl=${GL}${OUTPUT_EXTRA} \
|
|
-device qemu-xhci,id=xhci,p2=8,p3=8 -device usb-kbd,bus=xhci.0 -device usb-tablet,bus=xhci.0 ${USB_PASSTHROUGH} \
|
|
-device virtio-net,netdev=nic -netdev ${NET},id=nic \
|
|
-audiodev pa,id=pa,server=unix:${XDG_RUNTIME_DIR}/pulse/native,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 port=5930,disable-ticketing \
|
|
-device virtio-serial-pci \
|
|
-device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0 \
|
|
-chardev spicevmc,id=spicechannel0,name=vdagent \
|
|
-serial mon:stdio \
|
|
"${@}"
|
|
fi
|
|
}
|
|
|
|
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 " --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."
|
|
exit 1
|
|
}
|
|
|
|
# Lowercase variables are used in the VM config file only
|
|
boot="legacy"
|
|
guest_os="linux"
|
|
iso=""
|
|
driver_iso=""
|
|
disk_img=""
|
|
disk="64G"
|
|
|
|
DELETE=0
|
|
ENABLE_EFI=0
|
|
SNAPSHOT_ACTION=""
|
|
SNAPSHOT_TAG=""
|
|
STATUS_QUO=""
|
|
VM=""
|
|
|
|
readonly LAUNCHER=$(basename "${0}")
|
|
readonly DISK_MIN_SIZE=$((197632 * 8))
|
|
readonly QEMU="/snap/bin/qemu-virgil"
|
|
readonly QEMU_IMG="/snap/bin/qemu-virgil.qemu-img"
|
|
readonly VIRGIL_PATH="/snap/qemu-virgil/current"
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "${1}" in
|
|
-delete|--delete)
|
|
DELETE=1
|
|
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;;
|
|
-vm|--vm)
|
|
VM="${2}"
|
|
shift
|
|
shift;;
|
|
-h|--h|-help|--help)
|
|
usage;;
|
|
*)
|
|
echo "ERROR! \"${1}\" is not a supported parameter."
|
|
usage;;
|
|
esac
|
|
done
|
|
|
|
# Check we have qemu-virgil available
|
|
if [ ! -e "${QEMU}" ] && [ ! -e "${QEMU_IMG}" ]; then
|
|
echo "ERROR! qemu-virgil not found. Please install the qemu-virgil snap."
|
|
echo " https://snapcraft.io/qemu-virgil"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -n "${VM}" ] && [ -e "${VM}" ]; then
|
|
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
|
|
|
|
vm_boot |