DistroHopper/quickemu-tools

854 lines
20 KiB
Bash
Executable File

#! /bin/bash
## Copyright (c) Alex Genovese https://github.com/TuxVinyards
# licence: GPL3 https://www.gnu.org/licenses
## Provides a set of qcow snapshot & msr tools to work with quickemu
# https://github.com/quickemu-project/quickemu https://gitlab.com/qemu-project/qemu
# Users should install 'quickemu' and may set up Virtual Machines as normal.
## Install by placing script in /usr/bin (ensure chmod +x)
# Run by opening a terminal in the VM folder and typing:
# quickemu-tools --vm "file.conf"
## Based on 'quickemu-mod' https://github.com/TuxVinyards/quickemu-mod
# but, as having run time mods & hypervisor recipes removed, this 'tools' version
# now disengages it from having to keep track of the main project ...
# In theory, further disengagement could be achieved by routing straight 'qemu-img'
# But as this should work with any version of the original 'quickemu'
# the quickemu methods have been left, so any future add-ons might be possible. REVIEW
## Any snippets re-used from https://github.com/quickemu-project/quickemu
# are used to make the two projects work together easily & are used mainly
# for the benefit of the original project.
# Original snippets may be subject the original MIT licence.
# The 'tools' project, as separate script, is covered by GPL3
# even it it becomes adopted by the original project.
# IF ANY 'MODDED' CODE BECOMES USED IN THE ORIGINAL QUICKEMU SCRIPTS,
# OR ANY OTHER SIMILAR PROJECT, YOU SHOULD SHOW BOTH OF THE LICENCES
# & SHOW CLEAR ATTRIBUTIONS TO THE CODE SECTIONS USED.
QtoolsVersion="2023.03.28"
## API --vm "file.conf" [ --path "path/folder" ]
# where path to be used if .conf file not in current folder / present working directory
## SETTINGS
# General color & theming
X_Shade="3"
# Yellow 3 (recommended), Blue 4, Cyan 6 (brighter blue), Red 1
# https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
# File for default KVM behaviour for unhandled machine-specific registers. ( REVIEW )
# Edit here if your OS locates 'modprobe.d' differently. Default is "/etc/modprobe.d/kvm-quickemu.conf"
KVM_MSR_ModProbeFile="/etc/modprobe.d/kvm-quickemu.conf"
CurrentFolder="$(pwd)"
## Make sure shell is set during session to decimal separator of dot
# LC_ALL=C changes too much, just set the numeric.
# See locale setting discussion: https://unix.stackexchange.com/a/149129
# Also https://unix.stackexchange.com/questions/62316/why-is-there-no-euro-english-locale?rq=1
# & http://www.unicode.org/L2/L2001/01102-POSIX15897.htm
export "LC_NUMERIC=C"
export "LC_COLLATE=C"
## MOD Standard Quickemu checks for '< 4' which at 2022/3 now needs a bump.
# Some ver 5 script is now present in the standard release too ...
# More Version 5 style scripting should be used:
# See http://mywiki.wooledge.org/BashGuide/Practices#Choose_Your_Shell
if ((BASH_VERSINFO[0] < 5)); then
echo "Sorry, you need Bash 5.0 or newer to run this script."
exit 1
fi
LAUNCHER="$(basename "$0")"
if [[ ! $(type -p quickemu) ]]; then
echo "ERROR! QuickEmu not found. Please install."
exit 1
fi
# TODO add 'guestfs-tools' & an interface for 'virt-resize'
printColor () {
tput setaf "$X_Shade"
# shellcheck disable=SC2059
printf "$@"
tput sgr0
}
exit () {
# trap to keep terminal open if started by mouse click -t secs
printf "\n\n"
if [[ ! $CLI ]] && [[ $1 ]]; then
printColor " ERROR : [Enter] to quit or [h] to hold terminal open \n\n"
read -rp " > " -t 30 ExitTrap
fi
[[ ! $CLI ]] && [[ $ExitTrap == "h" ]] && printf "\n\n Holding terminal open [Enter] to quit \n\n" && read -rp " > "
tput cnorm
command exit "$@"
}
function_find_kvm_msr_default_and_status () {
# outputs boths vars 'KVM_MSR_DefaultConf' & 'KVM_MSR_status' with value Y or N
# finds and flags if MSRS has a config conflict
KVM_MSR_status="$(cat /sys/module/kvm/parameters/ignore_msrs)"
[[ ! $KVM_MSR_ModProbeFile ]] && KVM_MSR_ModProbeFile="/etc/modprobe.d/kvm-quickemu.conf"
KVM_MSR_DefaultConf="$(cat "$KVM_MSR_ModProbeFile" 2> /dev/null)"
[[ "$KVM_MSR_DefaultConf" == *'=Y' ]] && KVM_MSR_default="Y"
[[ "$KVM_MSR_DefaultConf" == *'=N' ]] || [[ ! -e "$KVM_MSR_ModProbeFile" ]] && KVM_MSR_default="N"
if [[ $VM_InstanceName ]]; then
if [[ "$VM_InstanceName" == *windows* ]] || [[ "$VM_InstanceName" == *macos* ]] ; then
if [[ $KVM_MSR_status == "N" ]]; then KVM_MSR_Error=1 ; else KVM_MSR_Error= ; fi
elif [[ "$VM_InstanceName" != *windows* ]] && [[ "$VM_InstanceName" != *macos* ]] ; then
if [[ $KVM_MSR_status == "Y" ]]; then KVM_MSR_Error=1 ; else KVM_MSR_Error= ; fi
else
KVM_MSR_Error=
fi
fi
}
print_kvm_status () {
function_find_kvm_msr_default_and_status
if [[ $KVM_MSR_status == "Y" ]] ; then
printf "\n\n KVM: /sys/module/kvm/parameters/ignore_msrs = Y"
[[ $VM_InstanceName ]] && [[ $KVM_MSR_Error ]] && printColor " ERROR "
printf "\n"
else
printf "\n\n KVM: /sys/module/kvm/parameters/ignore_msrs = N"
[[ $VM_InstanceName ]] && [[ $KVM_MSR_Error ]] && printColor " ERROR "
printf "\n"
fi
}
toggle_msr_defaults () {
# Modded & now reversible rewrite of original quickemu's function 'ignore_msrs_always'
# https://www.linux-kvm.org/page/Category:Docs
if [[ ! -d /etc/modprobe.d ]]; then
printf "\n ERROR! /etc/modprobe.d was not found. \n\n See notes, it may be possible to manually create modprobe.d/kvm-quickemu.conf \n\n"
else
printColor "\n\n Configure default, boot-up, KVM behaviour "
printf "for unhandled machine-specific registers"
printf "\n\n Normal setting is N (don't ignore) but Windows and MacOS require Y (true) 'ignore' "
function_find_kvm_msr_default_and_status
printColor "\n\n Status: /sys/module/kvm/parameters/ignore_msrs = %s Current Default = %s" "$KVM_MSR_status" "$KVM_MSR_default"
[[ ! $KVM_MSR_ModProbeFile ]] && KVM_MSR_ModProbeFile="/etc/modprobe.d/kvm-quickemu.conf"
if [[ ! -e "$KVM_MSR_ModProbeFile" ]]; then
printf "\n\n \'%s\' needs to be created " "$KVM_MSR_ModProbeFile"
fi
printf "\n\n [y] to set Y [n] to set N [b] to go back \n\n"
read -rp " > " Set_MSR_defaults
# set .conf file content & update initramfs in all kernels (y/n or none)
if [[ $Set_MSR_defaults == "y" ]]; then
printf "\n\n Updating 'initramfs' may take a moment or two ... \n\n"
# As per Martin's solution in original quickemu, needs 'tee' to get this to work,
# but route tee's stdout to null to tidy the screen
echo "options kvm ignore_msrs=Y" | sudo tee "$KVM_MSR_ModProbeFile" 1> /dev/null
sudo update-initramfs -k all -u
elif [[ $Set_MSR_defaults == "n" ]]; then
printf "\n\n Updating 'initramfs' may take a moment or two ... \n\n"
echo "options kvm ignore_msrs=N" | sudo tee "$KVM_MSR_ModProbeFile" 1> /dev/null
sudo update-initramfs -k all -u
fi
fi
[[ $CLI ]] && exit 1
}
show_kvm_sudo_security_note () {
printColor "\n QuickEmu-Tools require 'sudo' permissions to echo true or false to 'ignore_msrs'"
printf "\n\n This allows you to create a temporary MSRS status that may be changed at any time,"
printf "\n\n allowing you to match the selected guest VM that you want to run."
printColor "\n\n\n If you have concerns about this script, or about giving elevated permissions, "
printf "\n\n then the script should be checked or you should issue these commands manually:"
printf "\n\n Open a side terminal, use shift-crtl-c to copy the displayed command & shift-crtl-v to paste it. "
printf "\n\n Elevated permissions will then exist only in the side terminal & cease once it is closed. "
printf "\n\n Return to q-tools & select 'leave as'. Q-Tools will re-read msrs settings & auto-update. "
printColor "\n\n\n If you mainly use Windows or Mac VM's then a file '.../modprobe.d/kvm-quickemu.conf' "
printf "\n\n can be created to modify the load up settings. Quickemu-Tools has a new built in function"
printf "\n\n that can set this up & also allows future adjustments may be made."
printf "\n\n Or it may be carried out manually... See settings, script & further notes for details."
printColor "\n\n\n Status: /sys/module/kvm/parameters/ignore_msrs = %s Current Default = %s" "$KVM_MSR_status" "$KVM_MSR_default"
printf "\n\n Windows or MacOS should be set to 'Y' "
printf "\n"
}
select_msr_config () {
# MSR_offer normally present if MSRS/OS conflict previously detected,
# however, presume selector is being used to change current status REVIEW
print_kvm_status
if [[ $KVM_MSR_status == "Y" ]]; then MSR_offer="N" ; else MSR_offer="Y" ; fi
KVM_MSR_selector=
[[ $KVM_MSR_selector_LoadHelp ]] && show_kvm_sudo_security_note
while true ; do
if [[ $MSR_offer == "Y" ]]; then
printf "\n\n Set Y : echo 1 | sudo tee /sys/module/kvm/parameters/ignore_msrs ? "
printColor "\n\n [y] to set Y "
printf " [enter] leave as N"
else
printf "\n\n Set N : echo 0 | sudo tee /sys/module/kvm/parameters/ignore_msrs ? "
printColor "\n\n [n] to set N "
printf " [enter] leave as Y"
fi
printf " [d] to set the boot defaults"
[[ $KVM_MSR_selector != "h" ]] || [[ $KVM_MSR_selector_LoadHelp ]] && printf " [h] see help "
printf "\n\n"
read -rp " > " KVM_MSR_selector
printf "\n"
[[ $KVM_MSR_selector == "h" ]] && show_kvm_sudo_security_note
[[ ! $KVM_MSR_selector ]] && break
[[ $KVM_MSR_selector == "y" && $MSR_offer == "N" ]] || [[ $KVM_MSR_selector == "n" && $MSR_offer == "Y" ]] && break
if [[ $KVM_MSR_selector == "y" ]]|| [[ $KVM_MSR_selector == "n" ]]; then
# As per Martin's solution in original quickemu, needs 'tee' to get this to work,
# but route tee's stdout to null to tidy the screen
[[ $KVM_MSR_selector == "y" ]] && echo 1 | sudo tee /sys/module/kvm/parameters/ignore_msrs 1> /dev/null
[[ $KVM_MSR_selector == "n" ]] && echo 0 | sudo tee /sys/module/kvm/parameters/ignore_msrs 1> /dev/null
print_kvm_status
printColor "\n\n [enter] to return \n\n"
read -rp " > "
break
fi
if [[ $KVM_MSR_selector == "d" ]]; then
toggle_msr_defaults
if [[ $Set_MSR_defaults == "b" ]]; then
Set_MSR_defaults=
print_kvm_status
printColor "\n\n Make TEMPORARY setting adjustments to MSRS ?"
else
function_find_kvm_msr_default_and_status
break
fi
fi
done
KVM_MSR_selector=
KVM_MSR_selector_LoadHelp=
}
msrs_conflict_check_resolver() {
# Do a check ...
function_find_kvm_msr_default_and_status
# Display & Offer config settings if MSRS/OS CONFLICT exists
if [[ $KVM_MSR_status == "N" ]] ; then
# usual system default = N
if [[ "$VM_InstanceName" == *windows* ]] || [[ "$VM_InstanceName" == *macos* ]] ; then
printColor "\n\n Selected: %s " "$VM_InstanceName"
printf " 'ignore_msrs' is recommended for Windows and Mac"
#printf "\n\n Status: /sys/module/kvm/parameters/ignore_msrs = N Default = %s" "$KVM_MSR_default"
MSR_offer="Y"
select_msr_config
function_find_kvm_msr_default_and_status
if [[ $KVM_MSR_status == "N" ]]; then KVM_MSR_Error=1 ; else KVM_MSR_Error= ; fi
fi
else
# Status = Y & which is only recommended for Windows & Mac
if [[ "$VM_InstanceName" != *windows* ]] && [[ "$VM_InstanceName" != *macos* ]] ; then
printColor "\n\n Selected: %s " "$VM_InstanceName"
printf " 'ignore_msrs' is only recommended for Windows and Mac"
#printf "\n\n Status: /sys/module/kvm/parameters/ignore_msrs = Y Default = %s" "$KVM_MSR_default"
MSR_offer="N"
select_msr_config
function_find_kvm_msr_default_and_status
if [[ $KVM_MSR_status == "Y" ]]; then KVM_MSR_Error=1 ; else KVM_MSR_Error= ; fi
fi
fi
}
function_conf_error () {
printf "\n\n ERROR Quickemu-Tools Settings, VM folder & conf file(s)"
if [[ $1 ]] ; then printf "\n\n Please check %s settings, location & content ... \n\n" "$1"
else printf "\n\n Please check the settings and re-run this script ... \n\n" ; fi
printColor "\n\n [Enter] for help [q] to quit \n\n"
read -rp " > "
[[ $REPLY == "q" ]] && printf "\n\n" && command exit
show_quickemu_tools_help
command exit
}
function_snapshot_list() {
printf "\n\n"
quickemu -vm "$VM_Conf_File" --snapshot info
}
function show_quickemu_tools_help {
printColor "\n\n QuickEmu-Tools version %s " "$QtoolsVersion"
printColor "\n\n Easy snapshot & MSRS tools for the QuickEmu Project"
printf "\n\n Manage multiple snapshots. Recover Disk Space. "
printf "\n\n Toggle boot-up & temporary MSRS's "
printf "\n\n\n For code contributions, add-ons, info & updates see:"
printf "\n\n https://github.com/TuxVinyards/"
printf "\n\n\n From a terminal: "
printColor "%s --vm \"vm-name.conf\" [ --path \"path/folder\" ] " "$LAUNCHER"
printf "\n\n Add path if working from outside the VM's .conf folder"
printf "\n\n [enter] to return \n\n"
read -rp " > "
}
show_qmod_title() {
printColor "\n\n Quickemu Tools - Version %s" "$QtoolsVersion"
printf "\n\n A menu interfaced tool set for the quickemu project ..... \n\n"
}
function_show_main_menu_header () {
printf "\033c"
show_qmod_title
printColor " %s " "$VM_InstanceName"
printf " @ %s" "$VM_Conf_Dir"
}
## API READ ####################################################################################################
# https://unix.stackexchange.com/questions/220330/hide-and-unhide-cursor-with-tput
tput civis
if [[ ! $1 ]]; then
show_quickemu_tools_help
echo
command exit 1
else
## API --vm "file.conf" [ --path "path/folder" ]
# where path to be used if .conf file not in current folder / present working directory
while [[ $# -gt 0 ]]; do
case "$1" in
-vm|--vm)
VM_Conf_File="$2"
shift
shift ;;
--path|-path)
VM_Conf_Dir="$2"
shift
shift ;;
esac
done
[[ ! $VM_Conf_Dir ]] && VM_Conf_Dir="$CurrentFolder"
[[ ! $VM_Conf_File ]] && function_conf_error "Qtools COMMAND LINE Instruction,"
[[ ! -e "$VM_Conf_Dir/$VM_Conf_File" ]] && function_conf_error "Qtools COMMAND LINE Instruction,"
## Check file/folder exists
[[ ! -d "$VM_Conf_Dir" ]] && function_conf_error "folder"
# change directory to where the VM is
if [[ $CurrentFolder != "$VM_Conf_Dir" ]]; then
! cd "$VM_Conf_Dir" && printColor "\n\n ERROR .conf folder switching \n\n" && exit 1
fi
[[ ! -e "$VM_Conf_File" ]] && function_conf_error ".conf file"
# Quickemu sets the same name to the .conf file and to the main folder
VM_InstanceName="${VM_Conf_File/.conf}"
# VM_QCOW_Dir="$VM_Conf_Dir/$VM_InstanceName"
# check that the dir contains the right files && grep .conf for right content
[[ ! $(ls "$VM_InstanceName"/*.qcow2 2> /dev/null) ]] && function_conf_error "folder"
! grep -q 'guest_os=' "$VM_Conf_File" && function_conf_error ".conf file"
## Check KVM parameter settings & advise according to guest OS
KVM_MSR_selector=
function_find_kvm_msr_default_and_status
#msrs_conflict_check_resolver
fi
printf "\033c"
show_qmod_title
MultiInstanceError="$(pgrep -c 'quickemu-mod')"
if [[ $MultiInstanceError -gt 1 ]]; then
printColor "\n\n ERROR more than one instance of q-tools is running \n\n"
read -rp " Close the other instances, then press [enter] to continue > "
fi
check_instance_runtime() {
# https://www.qemu.org/docs/master/tools/qemu-img.html
InstancePID="$(pgrep "$VM_InstanceName")"
if [[ $InstancePID ]]; then
printColor "\n\n WARNING snapshots operations should NOT be carried out when VM running \n\n"
read -rp " Close down the VM, then press [enter] to continue > "
fi
}
check_instance_runtime
# MAIN MENU (select VM then choose actions to do)
while true ; do
MainMenuChoice=
SnapTitle=
SnapNumber=
SnapName=
function_show_main_menu_header
if [[ ! $MainMenuChoice ]]; then
print_kvm_status
printf "\n\n [m] toggle msrs "
printf "\n\n\n [sl] list [sc] create [sd] delete [sa] apply snapshots "
printf "\n\n\n [h] show help & info "
printf "\n\n [q] quit "
printf "\n\n\n"
read -rp " > " MainMenuChoice
fi
# ACTIONS:
if [[ $MainMenuChoice == "h" ]] ; then
show_quickemu_tools_help
elif [[ $MainMenuChoice == "m" ]] ; then
#KVM_MSR_selector_LoadHelp=1
select_msr_config
#msrs_conflict_check_resolver
elif [[ $MainMenuChoice == "q" ]] ; then
printf "\n\n"
MainMenuChoice=
break
exit
elif [[ $MainMenuChoice == "sl" ]] ; then
check_instance_runtime
function_snapshot_list
printf "\n\n [enter] to return to menu \n\n "
read -rp " > "
elif [[ $MainMenuChoice == "sc" ]] ; then
check_instance_runtime
function_snapshot_list
printColor "\n\n Give [title] or [enter] for date.time [b] back to menu "
SnapTitle=
printf "\n\n"
read -rp " > " SnapTitle
printf "\n\n"
[[ ! $SnapTitle ]] && SnapTitle="$(date +%b%d.%R)"
[[ $SnapTitle != "b" ]] && quickemu -vm "$VM_Conf_File" --snapshot create "$SnapTitle"
printf "\n\n [enter] to return to menu \n\n "
read -rp " > "
elif [[ $MainMenuChoice == "sd" ]] ; then
printColor "\n\n Quickemu-Tools Snapshot Deletion:"
check_instance_runtime
function_snapshot_list
# Create range-selectable array
SnapListString="$(function_snapshot_list | grep '[0-9][0-9]:')"
mapfile -t SnapListArrRaw <<< "$SnapListString"
i=0
printColor "\n\n ID Array Name \n\n"
while [[ "${SnapListArrRaw[$i]}" ]]; do
IFS=' ' read -ra SnapListArrSeparated <<< "${SnapListArrRaw[$i]}"
printf "%2d %2d %s \n" "${SnapListArrSeparated[0]}" "$i" "${SnapListArrSeparated[1]}"
((i+=1))
done
SnapListArrTotal=$((i-1))
printColor "\n\n Give ARRAY number 0 to %s of snapshot or start of snapshot range to delete" "$SnapListArrTotal"
printf "\n\n [enter] to return to main menu "
SnapName=
SnapDeleteStart=
SnapDeleteEnd=
SnapDeleteConfirm=
printf "\n\n"
read -rp " > " SnapDeleteStart
if [[ $SnapDeleteStart ]]; then
printColor "\n\n [enter] for individual snapshot or ARRAY [number] for end of range (inclusive) \n\n"
read -rp " > " SnapDeleteEnd
if [[ $SnapDeleteEnd ]]; then
printf "\n Array Range = %s to %s " "$SnapDeleteStart" "$SnapDeleteEnd"
else
printf "\n Delete = Array entry %s " "$SnapDeleteStart"
SnapDeleteEnd="$SnapDeleteStart"
fi
printColor "\n\n [enter] to continue [b] back to main menu \n\n"
read -rp " > " SnapDeleteConfirm
if [[ $SnapDeleteConfirm == "b" ]]; then
printf "\n\n Deletion schedule has been CANCELLED"
else
SnapDeleteRangeCounter=$SnapDeleteStart
while [[ $SnapDeleteRangeCounter -le $SnapDeleteEnd ]]; do
IFS=' ' read -ra SnapListArrSeparated <<< "${SnapListArrRaw[$SnapDeleteRangeCounter]}"
SnapName="${SnapListArrSeparated[1]}"
if [[ ! $SnapName ]]; then
printColor "\n\n ERROR with SnapShot Array List \n\n"
exit 1
else
printColor "\n\n Deleting SnapShot %2d %2d %s \n\n" "${SnapListArrSeparated[0]}" "$SnapDeleteRangeCounter" "${SnapListArrSeparated[1]}"
quickemu -vm "$VM_Conf_File" --snapshot delete "$SnapName"
fi
((SnapDeleteRangeCounter+=1))
done
fi
printf "\n\n [enter] to return to menu \n\n "
read -rp " > "
fi
elif [[ $MainMenuChoice == "sa" ]] ; then
check_instance_runtime
function_snapshot_list
printColor "\n\n Give number of snapshot to use [enter] to return to menu "
SnapNumber=
printf "\n\n"
read -rp " > " SnapNumber
printf "\n\n"
if [[ $SnapNumber ]]; then
quickemu -vm "$VM_Conf_File" --snapshot apply "$SnapNumber"
printf "\n\n May take a moment .... \n\n"
printColor "\n\n Snapshot %s has been applied. \n\n" "$SnapNumber "
fi
fi
done
# vim:tabstop=2:shiftwidth=2:expandtab
##