0
0

control-vm.sh: Refactor logic and provide a help output

This commit is contained in:
Nils Freydank 2024-06-23 21:46:44 +02:00
parent 72013bf33d
commit bceb9a9710
Signed by: nfr
GPG Key ID: 0F1DEAB2D36AD112
2 changed files with 173 additions and 146 deletions

View File

@ -8,7 +8,7 @@
# https://git.holgersson.xyz/nfr/control-vm # https://git.holgersson.xyz/nfr/control-vm
# If that link does not work, feel free to drop me an email. # If that link does not work, feel free to drop me an email.
# Version: 2024-05-23 # For version see variable below comment section.
# Author: Nils Freydank <nils.freydank@datenschutz-ist-voll-doof.de> # Author: Nils Freydank <nils.freydank@datenschutz-ist-voll-doof.de>
# License: MIT # License: MIT
@ -65,133 +65,133 @@
# CLIPBOARD="True" # CLIPBOARD="True"
# _EOF # _EOF
# Provide a sanity check first. __VERSION__="0.0.0"
[[ -z "${1}" ]] && echo "Please provide a guest name!" && exit 1
# === Define default values === set_params(){
CLIPBOARD="${CLIPBOARD:-}" # === Define default values ===
CPU_CORES="${CPU_CORES:-8}" CLIPBOARD="${CLIPBOARD:-}"
EFI="${EFI:-True}" CPU_CORES="${CPU_CORES:-8}"
GUEST_NAME="${1}" EFI="${EFI:-True}"
LIVE_ONLY="${LIVE_ONLY:-}" GUEST_NAME="${1}"
# Keep low default aligned with oldest tested version. LIVE_ONLY="${LIVE_ONLY:-}"
# Feel free to bump to latest version that you QEMU supports. # Keep low default aligned with oldest tested version.
MACHINE="${MACHINE:-pc-q35-6.0}" # Feel free to bump to latest version that you QEMU supports.
MEMORY="${MEMORY:-16384}" MACHINE="${MACHINE:-pc-q35-6.0}"
NET="${NET:-}" MEMORY="${MEMORY:-16384}"
NETDEV_NAME="vmnic-${GUEST_NAME}" NET="${NET:-}"
SOUND="${SOUND:-}" NETDEV_NAME="vmnic-${GUEST_NAME}"
SSH_PORT="${SSH_PORT:-}" SOUND="${SOUND:-}"
UI="${UI:-}" SSH_PORT="${SSH_PORT:-}"
# Define paths. UI="${UI:-}"
# Note: Sort in alphabetical order as long as inheritance is respected. # Define paths.
BASE_PATH="$(pwd)/${GUEST_NAME}" # Note: Sort in alphabetical order as long as inheritance is respected.
GUEST_CONFIG_FILE="${GUEST_CONFIG_FILE:-${BASE_PATH}/virtual-machine.bash}" BASE_PATH="$(pwd)/${GUEST_NAME}"
IMAGE_PATH="${IMAGE_PATH:-${BASE_PATH}/virtual-machine.img}" GUEST_CONFIG_FILE="${GUEST_CONFIG_FILE:-${BASE_PATH}/virtual-machine.bash}"
ISO_PATH="${ISO_PATH:-${BASE_PATH}/../installer/${GUEST_NAME}.iso}" IMAGE_PATH="${IMAGE_PATH:-${BASE_PATH}/virtual-machine.img}"
MONITOR_SOCKET="${MONITOR_SOCKET:-${BASE_PATH}/monitor.socket}" ISO_PATH="${ISO_PATH:-${BASE_PATH}/../installer/${GUEST_NAME}.iso}"
PID_FILE="${PID_FILE:-${BASE_PATH}/qemu.pid}" MONITOR_SOCKET="${MONITOR_SOCKET:-${BASE_PATH}/monitor.socket}"
QMP_SOCKET="${QMP_SOCKET:-${BASE_PATH}/qmp.socket}" PID_FILE="${PID_FILE:-${BASE_PATH}/qemu.pid}"
SNAPSHOT_INFO_FILE="${SNAPSHOT_INFO_FILE:-${BASE_PATH}/last-snapshot-info}" QMP_SOCKET="${QMP_SOCKET:-${BASE_PATH}/qmp.socket}"
SPICE_SOCKET="${SPICE_SOCKET:-${BASE_PATH}/spice.socket}" SNAPSHOT_INFO_FILE="${SNAPSHOT_INFO_FILE:-${BASE_PATH}/last-snapshot-info}"
SPICE_SOCKET="${SPICE_SOCKET:-${BASE_PATH}/spice.socket}"
# === Source the guest configuration to overwrite defaults if necessary. === # === Source the guest configuration to overwrite defaults if necessary. ===
if [[ -f "${GUEST_CONFIG_FILE}" ]]; then if [[ -f "${GUEST_CONFIG_FILE}" ]]; then
source "${GUEST_CONFIG_FILE}" source "${GUEST_CONFIG_FILE}"
else else
echo "No configuration found. Aborting." echo " error: No configuration found. Aborting."
exit 2 exit 3
fi fi
# === Define a base and glue everything together. === # === Define a base and glue everything together. ===
QEMU_COMMON_ARGS=( QEMU_COMMON_ARGS=(
# Provide a pretty name for the guest. # Provide a pretty name for the guest.
-name "${GUEST_NAME}" -name "${GUEST_NAME}"
# Disable some less document user configuration loading. # Disable some less document user configuration loading.
-no-user-config -no-user-config
# Configure some seccomp mode 2 filters. # Configure some seccomp mode 2 filters.
-sandbox on,obsolete=deny,elevateprivileges=allow,spawn=allow,resourcecontrol=allow -sandbox on,obsolete=deny,elevateprivileges=allow,spawn=allow,resourcecontrol=allow
# Enable KVM and hardware acceleration. # Enable KVM and hardware acceleration.
-enable-kvm -enable-kvm
# Use the same CPU as the host has for maximum performoance and configure # Use the same CPU as the host has for maximum performoance and configure
# the amount of memory and CPU cores as configured per-host. # the amount of memory and CPU cores as configured per-host.
-cpu host -cpu host
-smp cores="${CPU_CORES}" -smp cores="${CPU_CORES}"
-m "${MEMORY}" -m "${MEMORY}"
# Detach from the current shell and monitor by pid file and a unix socket. # Detach from the current shell and monitor by pid file and a unix socket.
-daemonize -daemonize
-pidfile "${PID_FILE}" -pidfile "${PID_FILE}"
-monitor unix:"${MONITOR_SOCKET}",server,nowait -monitor unix:"${MONITOR_SOCKET}",server,nowait
-qmp-pretty unix:"${QMP_SOCKET}",server,nowait -qmp-pretty unix:"${QMP_SOCKET}",server,nowait
) )
QEMU_DISK_ARGS=( -drive id=hd0,media=disk,if=virtio,file="${IMAGE_PATH}",aio=io_uring,cache=none,cache-size=16M,discard=on ) QEMU_DISK_ARGS=( -drive id=hd0,media=disk,if=virtio,file="${IMAGE_PATH}",aio=io_uring,cache=none,cache-size=16M,discard=on )
QEMU_DISPLAY_NONE_ARGS=( -vga none -display none ) QEMU_DISPLAY_NONE_ARGS=( -vga none -display none )
QEMU_DISPLAY_VIRTIO_ARGS=( -vga virtio ) QEMU_DISPLAY_VIRTIO_ARGS=( -vga virtio )
QEMU_DISPLAY_STD_ARGS=( -vga std ) QEMU_DISPLAY_STD_ARGS=( -vga std )
QEMU_DISPLAY_SPICE_ARGS=( -spice unix=on,addr="${SPICE_SOCKET}",disable-ticketing=on,gl=off ) QEMU_DISPLAY_SPICE_ARGS=( -spice unix=on,addr="${SPICE_SOCKET}",disable-ticketing=on,gl=off )
QEMU_CLIPBOARD_SPICE_ARGS=( QEMU_CLIPBOARD_SPICE_ARGS=(
-device virtio-serial-pci -device virtio-serial-pci
-device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0 -device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0
-chardev spicevmc,id=spicechannel0,name=vdagent -chardev spicevmc,id=spicechannel0,name=vdagent
) )
QEMU_EFI_ARGS=( QEMU_EFI_ARGS=(
-M "${MACHINE}",accel=kvm -M "${MACHINE}",accel=kvm
-drive file=/usr/share/edk2-ovmf/OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on -drive file=/usr/share/edk2-ovmf/OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on
) )
QEMU_BIOS_ARGS=( QEMU_BIOS_ARGS=(
-M "${MACHINE}",accel=kvm -M "${MACHINE}",accel=kvm
) )
QEMU_ISO_ARGS=( -cdrom "${ISO_PATH}" ) QEMU_ISO_ARGS=( -cdrom "${ISO_PATH}" )
QEMU_NET_ARGS=( QEMU_NET_ARGS=(
-device virtio-net,netdev="${NETDEV_NAME}" -device virtio-net,netdev="${NETDEV_NAME}"
-netdev user,id="${NETDEV_NAME}" -netdev user,id="${NETDEV_NAME}"
) )
QEMU_SOUND_ARGS=( -device ich9-intel-hda -device hda-duplex ) QEMU_SOUND_ARGS=( -device ich9-intel-hda -device hda-duplex )
QEMU_SSH_ARGS=( QEMU_SSH_ARGS=(
-device virtio-net,netdev="${NETDEV_NAME}" -device virtio-net,netdev="${NETDEV_NAME}"
-netdev user,id="${NETDEV_NAME}",hostfwd=tcp:127.0.0.1:"${SSH_PORT}"-:22 -netdev user,id="${NETDEV_NAME}",hostfwd=tcp:127.0.0.1:"${SSH_PORT}"-:22
) )
# === Start the final QEMU parameter array. ===
QEMU_ARGS=( "${QEMU_COMMON_ARGS[@]}" )
if [[ "${EFI}" ]]; then
QEMU_ARGS+=( "${QEMU_EFI_ARGS[@]}" )
else
QEMU_ARGS+=( "${QEMU_BIOS_ARGS[@]}" )
fi
# The following block checks only agains live vs non-live.
if [[ "${LIVE_ONLY}" ]]; then
QEMU_ARGS+=(
"${QEMU_ISO_ARGS[@]}"
# d: CD-ROM
-boot d
)
else
QEMU_ARGS+=(
"${QEMU_DISK_ARGS[@]}"
# c: hard disk
-boot c
)
fi
#
[[ -n "${NET}" && -z "${SSH_PORT}" ]] && QEMU_ARGS+=( "${QEMU_NET_ARGS[@]}" )
[[ -n "${SOUND}" ]] && QEMU_ARGS+=( "${QEMU_SOUND_ARGS[@]}" )
[[ -n "${SSH_PORT}" ]] && QEMU_ARGS+=( "${QEMU_SSH_ARGS[@]}" )
if [[ -n "${UI}" ]]; then
case "${UI}" in
std|stdvga ) QEMU_ARGS+=( "${QEMU_DISPLAY_STD_ARGS[@]}" );;
*) QEMU_ARGS+=( "${QEMU_DISPLAY_VIRTIO_ARGS[@]}" );;
esac
QEMU_ARGS+=( "${QEMU_DISPLAY_SPICE_ARGS[@]}" )
# Only check for clipboard support here as clipoard w/o UI doesn't make
# sense in this context.
if [[ -n "${CLIPBOARD}" ]]; then
QEMU_ARGS+=( "${QEMU_CLIPBOARD_SPICE_ARGS[@]}" )
fi
else
QEMU_ARGS+=( "${QEMU_DISPLAY_NONE_ARGS[@]}" )
fi
# === Start the final QEMU parameter array. ===
QEMU_ARGS=( "${QEMU_COMMON_ARGS[@]}" )
if [[ "${EFI}" ]]; then
QEMU_ARGS+=( "${QEMU_EFI_ARGS[@]}" )
else
QEMU_ARGS+=( "${QEMU_BIOS_ARGS[@]}" )
fi
# The following block checks only agains live vs non-live.
if [[ "${LIVE_ONLY}" ]]; then
QEMU_ARGS+=(
"${QEMU_ISO_ARGS[@]}"
# d: CD-ROM
-boot d
)
else
QEMU_ARGS+=(
"${QEMU_DISK_ARGS[@]}"
# c: hard disk
-boot c
)
fi
#
[[ -n "${NET}" && -z "${SSH_PORT}" ]] && QEMU_ARGS+=( "${QEMU_NET_ARGS[@]}" )
[[ -n "${SOUND}" ]] && QEMU_ARGS+=( "${QEMU_SOUND_ARGS[@]}" )
[[ -n "${SSH_PORT}" ]] && QEMU_ARGS+=( "${QEMU_SSH_ARGS[@]}" )
if [[ -n "${UI}" ]]; then
case "${UI}" in
std|stdvga ) QEMU_ARGS+=( "${QEMU_DISPLAY_STD_ARGS[@]}" );;
*) QEMU_ARGS+=( "${QEMU_DISPLAY_VIRTIO_ARGS[@]}" );;
esac
QEMU_ARGS+=( "${QEMU_DISPLAY_SPICE_ARGS[@]}" )
# Only check for clipboard support here as clipoard w/o UI doesn't make
# sense in this context.
if [[ -n "${CLIPBOARD}" ]]; then
QEMU_ARGS+=( "${QEMU_CLIPBOARD_SPICE_ARGS[@]}" )
fi
else
QEMU_ARGS+=( "${QEMU_DISPLAY_NONE_ARGS[@]}" )
fi
}
# === Define functions for the actual QEMU controlling. === # === Define functions for the actual QEMU controlling. ===
start_qemu(){ start_qemu(){
if [[ ! -f "${PID_FILE}" ]]; then if [[ ! -f "${PID_FILE}" ]]; then
@ -284,24 +284,52 @@ resume_qemu(){
echo "Guest ${GUEST_NAME} is not running, nothing to resume." echo "Guest ${GUEST_NAME} is not running, nothing to resume."
fi fi
} }
print_help(){
echo " ======================================================================="
echo " control-vm.sh - simple QEMU/KVM vm manager"
echo " version: ${__VERSION__}"
echo " author Nils Freydank <nils.freydank@datenschutz-ist-voll-doof.de>"
echo " license: MIT"
echo " url: https://git.holgersson.xyz/nfr/control-vm"
echo " ======================================================================="
echo ""
echo " Use this tool in the following way:"
echo " ./control-vm.sh <name of your virtual machine> <operation>"
echo ""
echo " with operation as one of:"
echo " start | shutdown | stop | connect | save | restore | pause | resume"
echo " or as one of the combinations:"
echo " start+connect | stop+restore | restore+restart"
echo ""
echo " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
echo " ! Note that stop means a forced stop - which can lead to data loss. !"
echo " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
}
# === Parse the user input. === # === Parse the user input. ===
# "$1 != nothing" is already checked earlier. # Provide a sanity check first.
case "${2}" in if [[ -z "${1}" ]]; then
start+connect) start_qemu && connect_to_vm;; echo " error: Please provide a guest name!"
stop+restore) stop_qemu && restore_qemu;; print_help
restore+restart) stop_qemu && restore_qemu && start_qemu && connect_to_vm;; exit 1
start) start_qemu;; else
shutdown) shutdown_qemu;; set_params "${1}"
stop) stop_qemu;; case "${2}" in
connect) connect_to_vm;; start+connect) start_qemu && connect_to_vm;;
save) save_qemu;; stop+restore) stop_qemu && restore_qemu;;
restore) restore_qemu;; restore+restart) stop_qemu && restore_qemu && start_qemu && connect_to_vm;;
pause) pause_qemu;; start) start_qemu;;
resume) resume_qemu;; shutdown) shutdown_qemu;;
*) echo "Unknown Operation!" stop) stop_qemu;;
echo "Use one of: start | shutdown | stop | connect | save | restore | pause | resume" connect) connect_to_vm;;
echo "or combinations: start+connect | stop+restore | restore+restart" save) save_qemu;;
echo "Note that stop means a forced stop.";; restore) restore_qemu;;
esac pause) pause_qemu;;
resume) resume_qemu;;
*) echo " error: Unknown Operation!"
print_help
exit 2
;;
esac
fi
# vim:fileencoding=utf-8:ts=4:syntax=bash:expandtab # vim:fileencoding=utf-8:ts=4:syntax=bash:expandtab

1
todo
View File

@ -7,4 +7,3 @@
# 7. shared dir without smb, but via FUSE # 7. shared dir without smb, but via FUSE
# 8. bash and zsh-completion # 8. bash and zsh-completion
# 9. don't rely on the PID to check if the VM is running, query socket? # 9. don't rely on the PID to check if the VM is running, query socket?
# 11. print a pretty help message and share on mastodon :)