#!/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 snapshot_tag="${1}" if [ -z "${snapshot_tag}" ]; then echo "ERROR! No snapshot tag provided." exit fi if [ -e "${disk_img}" ]; then ${QEMU_IMG} snapshot -q -a "${snapshot_tag}" "${disk_img}" if [ $? -eq 0 ]; then echo "SUCCESS! Applied snapshot ${snapshot_tag} to ${disk_img}" else echo "ERROR! Failed to apply snapshot ${snapshot_id} to ${disk_img}" fi else echo "NOTE! ${disk_img} not found. Doing nothing." fi } function snapshot_create() { local snapshot_tag="${1}" if [ -z "${snapshot_tag}" ]; then echo "ERROR! No snapshot tag provided." exit fi if [ -e "${disk_img}" ]; then ${QEMU_IMG} snapshot -q -c "${snapshot_tag}" "${disk_img}" if [ $? -eq 0 ]; then echo "SUCCESS! Created snapshot ${snapshot_tag} of ${disk_img}" else echo "ERROR! Failed to create snapshot ${snapshot_tag} of ${disk_img}" fi else echo "NOTE! ${disk_img} not found. Doing nothing." fi } function snapshot_delete() { local snapshot_tag="${1}" if [ -z "${snapshot_tag}" ]; then echo "ERROR! No snapshot tag provided." exit fi if [ -e "${disk_img}" ]; then ${QEMU_IMG} snapshot -q -d "${snapshot_tag}" "${disk_img}" if [ $? -eq 0 ]; then echo "SUCCESS! Deleted snapshot ${snapshot_tag} of ${disk_img}" else echo "ERROR! Failed to delete snapshot ${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 GL="on" local VIRGL="on" local UI="sdl" local QEMU_VER=$(${QEMU} -version | head -n1 | cut -d' ' -f4 | cut -d'(' -f1) echo "Starting ${VM}" echo " - QEMU: ${QEMU} v${QEMU_VER}" readonly disk_min_size=$((197632 * 8)) if [ -z "${disk}" ]; then disk="64G" fi if [ ${ENABLE_EFI} -eq 1 ]; then if [ -e /snap/qemu-virgil/current/usr/share/qemu/edk2-x86_64-code.fd ] ; then if [ ! -e ${VMDIR}/${VMNAME}-vars.fd ]; then cp /snap/qemu-virgil/current/usr/share/qemu/edk2-i386-vars.fd ${VMDIR}/${VMNAME}-vars.fd fi BIOS="-drive if=pflash,format=raw,readonly,file=/snap/qemu-virgil/current/usr/share/qemu/edk2-x86_64-code.fd -drive if=pflash,format=raw,file=${VMDIR}/${VMNAME}-vars.fd" else echo " - EFI: Booting requested but no EFI firmware found." echo " Booting from Legacy BIOS." fi echo " - BIOS: EFI" else echo " - BIOS: Legacy" fi echo " - Disk: ${disk_img} (${disk})" if [ ! -f "${disk_img}" ]; then # If there is no disk image, create a new image. ${QEMU_IMG} create -q -f qcow2 "${disk_img}" "${disk}" 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}" 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 [ "${STATUSQUO}" == "-snapshot" ] && [ -z "${iso}" ]; then echo " Existing disk state will be preserved, no writes will be committed." fi if [ -n "${iso}" ] && [ -e "${iso}" ]; then local ISO_BOOT="-drive media=cdrom,file="${iso}",index=2" echo " - Boot: ${iso}" fi if [ -n "${driver_iso}" ] && [ -e "${driver_iso}" ]; then local ISO_DRIVER="-drive media=cdrom,file="${driver_iso}",index=3" echo " - Drivers: ${driver_iso}" fi local cores="1" local allcores=$(nproc --all) if [ ${allcores} -ge 8 ]; then cores="4" elif [ ${allcores} -ge 4 ]; then cores="2" fi echo " - CPU: ${cores} Core(s)" local ram="2G" local allram=$(free --mega -h | grep Mem | cut -d':' -f2 | cut -d'G' -f1 | sed 's/ //g') if [ ${allram} -ge 64 ]; then ram="4G" elif [ ${allram} -ge 16 ]; then ram="3G" fi echo " - RAM: ${ram}" local xres=1152 local yres=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 xres=3200 yres=1800 elif [ ${LOWEST_WIDTH} -ge 2560 ]; then xres=2048 yres=1152 elif [ ${LOWEST_WIDTH} -ge 1920 ]; then xres=1664 yres=936 elif [ ${LOWEST_WIDTH} -ge 1280 ]; then xres=1152 yres=648 fi fi # GL is not working with GTK currently if [ "${UI}" == "gtk" ]; then GL="off" else echo " - Display: ${xres}x${yres}" fi echo " - GL: ${GL}" echo " - Virgil3D: ${VIRGL}" echo " - Output: ${UI^^}" # Set the hostname of the VM local NET="user,hostname=${VMNAME}" # If smbd is available, export $HOME to the guest via samba if [ -e /snap/qemu-virgil/current/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 ${QEMU} -name ${VMNAME},process=${VMNAME} \ ${BIOS} \ -drive id=disk,file=${disk_img},format=qcow2,if=virtio,aio=native,cache.direct=on \ ${ISO_BOOT} ${ISO_DRIVER} \ -enable-kvm \ -machine q35,accel=kvm \ -cpu host,kvm=on \ -m ${ram} \ -smp ${cores} \ -netdev ${NET},id=nic \ -device virtio-net,netdev=nic \ -rtc base=localtime,clock=host \ -serial mon:stdio \ -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 \ -device qemu-xhci,id=xhci,p2=8,p3=8 -device usb-kbd -device usb-tablet \ -object rng-random,id=rng0,filename=/dev/urandom \ -device virtio-rng-pci,rng=rng0 \ -device virtio-vga,virgl=${VIRGL},xres=${xres},yres=${yres} \ -display ${UI},gl=${GL} ${STATUSQUO} \ "$@" } 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 " --efi : Enable EFI BIOS." echo " --snapshot apply : Apply/restore a snapshot." echo " --snapshot create : Create a snapshot." echo " --snapshot delete : Delete a snapshot." echo " --snapshot info : Show disk/snapshot info." echo " --status-quo : Do not commit any changes to disk/snapshot." exit 1 } DELETE=0 ENABLE_EFI=0 readonly QEMU="/snap/bin/qemu-virgil" readonly QEMU_IMG="/snap/bin/qemu-virgil.qemu-img" readonly LAUNCHER=$(basename $0) SNAPSHOT_ACTION="" SNAPSHOT_TAG="" STATUSQUO="" VM="" while [ $# -gt 0 ]; do case "${1}" in -efi|--efi) ENABLE_EFI=1 shift;; -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) STATUSQUO="-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