From bceb9a97100605a6d758dc8131adec02fd39fe96 Mon Sep 17 00:00:00 2001 From: Nils Freydank Date: Sun, 23 Jun 2024 21:46:44 +0200 Subject: [PATCH] control-vm.sh: Refactor logic and provide a help output --- control-vm.sh | 318 +++++++++++++++++++++++++++----------------------- todo | 1 - 2 files changed, 173 insertions(+), 146 deletions(-) diff --git a/control-vm.sh b/control-vm.sh index 5efe756..6f71e6d 100755 --- a/control-vm.sh +++ b/control-vm.sh @@ -8,7 +8,7 @@ # https://git.holgersson.xyz/nfr/control-vm # 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 # License: MIT @@ -65,133 +65,133 @@ # CLIPBOARD="True" # _EOF -# Provide a sanity check first. -[[ -z "${1}" ]] && echo "Please provide a guest name!" && exit 1 - -# === Define default values === -CLIPBOARD="${CLIPBOARD:-}" -CPU_CORES="${CPU_CORES:-8}" -EFI="${EFI:-True}" -GUEST_NAME="${1}" -LIVE_ONLY="${LIVE_ONLY:-}" -# Keep low default aligned with oldest tested version. -# Feel free to bump to latest version that you QEMU supports. -MACHINE="${MACHINE:-pc-q35-6.0}" -MEMORY="${MEMORY:-16384}" -NET="${NET:-}" -NETDEV_NAME="vmnic-${GUEST_NAME}" -SOUND="${SOUND:-}" -SSH_PORT="${SSH_PORT:-}" -UI="${UI:-}" -# Define paths. -# Note: Sort in alphabetical order as long as inheritance is respected. -BASE_PATH="$(pwd)/${GUEST_NAME}" -GUEST_CONFIG_FILE="${GUEST_CONFIG_FILE:-${BASE_PATH}/virtual-machine.bash}" -IMAGE_PATH="${IMAGE_PATH:-${BASE_PATH}/virtual-machine.img}" -ISO_PATH="${ISO_PATH:-${BASE_PATH}/../installer/${GUEST_NAME}.iso}" -MONITOR_SOCKET="${MONITOR_SOCKET:-${BASE_PATH}/monitor.socket}" -PID_FILE="${PID_FILE:-${BASE_PATH}/qemu.pid}" -QMP_SOCKET="${QMP_SOCKET:-${BASE_PATH}/qmp.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. === -if [[ -f "${GUEST_CONFIG_FILE}" ]]; then - source "${GUEST_CONFIG_FILE}" -else - echo "No configuration found. Aborting." - exit 2 -fi - -# === Define a base and glue everything together. === -QEMU_COMMON_ARGS=( - # Provide a pretty name for the guest. - -name "${GUEST_NAME}" - # Disable some less document user configuration loading. - -no-user-config - # Configure some seccomp mode 2 filters. - -sandbox on,obsolete=deny,elevateprivileges=allow,spawn=allow,resourcecontrol=allow - # Enable KVM and hardware acceleration. - -enable-kvm - # Use the same CPU as the host has for maximum performoance and configure - # the amount of memory and CPU cores as configured per-host. - -cpu host - -smp cores="${CPU_CORES}" - -m "${MEMORY}" - # Detach from the current shell and monitor by pid file and a unix socket. - -daemonize - -pidfile "${PID_FILE}" - -monitor unix:"${MONITOR_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_DISPLAY_NONE_ARGS=( -vga none -display none ) -QEMU_DISPLAY_VIRTIO_ARGS=( -vga virtio ) -QEMU_DISPLAY_STD_ARGS=( -vga std ) -QEMU_DISPLAY_SPICE_ARGS=( -spice unix=on,addr="${SPICE_SOCKET}",disable-ticketing=on,gl=off ) -QEMU_CLIPBOARD_SPICE_ARGS=( - -device virtio-serial-pci - -device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0 - -chardev spicevmc,id=spicechannel0,name=vdagent -) -QEMU_EFI_ARGS=( - -M "${MACHINE}",accel=kvm - -drive file=/usr/share/edk2-ovmf/OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on -) -QEMU_BIOS_ARGS=( - -M "${MACHINE}",accel=kvm -) -QEMU_ISO_ARGS=( -cdrom "${ISO_PATH}" ) -QEMU_NET_ARGS=( - -device virtio-net,netdev="${NETDEV_NAME}" - -netdev user,id="${NETDEV_NAME}" -) -QEMU_SOUND_ARGS=( -device ich9-intel-hda -device hda-duplex ) -QEMU_SSH_ARGS=( - -device virtio-net,netdev="${NETDEV_NAME}" - -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 +__VERSION__="0.0.0" +set_params(){ + # === Define default values === + CLIPBOARD="${CLIPBOARD:-}" + CPU_CORES="${CPU_CORES:-8}" + EFI="${EFI:-True}" + GUEST_NAME="${1}" + LIVE_ONLY="${LIVE_ONLY:-}" + # Keep low default aligned with oldest tested version. + # Feel free to bump to latest version that you QEMU supports. + MACHINE="${MACHINE:-pc-q35-6.0}" + MEMORY="${MEMORY:-16384}" + NET="${NET:-}" + NETDEV_NAME="vmnic-${GUEST_NAME}" + SOUND="${SOUND:-}" + SSH_PORT="${SSH_PORT:-}" + UI="${UI:-}" + # Define paths. + # Note: Sort in alphabetical order as long as inheritance is respected. + BASE_PATH="$(pwd)/${GUEST_NAME}" + GUEST_CONFIG_FILE="${GUEST_CONFIG_FILE:-${BASE_PATH}/virtual-machine.bash}" + IMAGE_PATH="${IMAGE_PATH:-${BASE_PATH}/virtual-machine.img}" + ISO_PATH="${ISO_PATH:-${BASE_PATH}/../installer/${GUEST_NAME}.iso}" + MONITOR_SOCKET="${MONITOR_SOCKET:-${BASE_PATH}/monitor.socket}" + PID_FILE="${PID_FILE:-${BASE_PATH}/qemu.pid}" + QMP_SOCKET="${QMP_SOCKET:-${BASE_PATH}/qmp.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. === + if [[ -f "${GUEST_CONFIG_FILE}" ]]; then + source "${GUEST_CONFIG_FILE}" + else + echo " error: No configuration found. Aborting." + exit 3 + fi + + # === Define a base and glue everything together. === + QEMU_COMMON_ARGS=( + # Provide a pretty name for the guest. + -name "${GUEST_NAME}" + # Disable some less document user configuration loading. + -no-user-config + # Configure some seccomp mode 2 filters. + -sandbox on,obsolete=deny,elevateprivileges=allow,spawn=allow,resourcecontrol=allow + # Enable KVM and hardware acceleration. + -enable-kvm + # Use the same CPU as the host has for maximum performoance and configure + # the amount of memory and CPU cores as configured per-host. + -cpu host + -smp cores="${CPU_CORES}" + -m "${MEMORY}" + # Detach from the current shell and monitor by pid file and a unix socket. + -daemonize + -pidfile "${PID_FILE}" + -monitor unix:"${MONITOR_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_DISPLAY_NONE_ARGS=( -vga none -display none ) + QEMU_DISPLAY_VIRTIO_ARGS=( -vga virtio ) + QEMU_DISPLAY_STD_ARGS=( -vga std ) + QEMU_DISPLAY_SPICE_ARGS=( -spice unix=on,addr="${SPICE_SOCKET}",disable-ticketing=on,gl=off ) + QEMU_CLIPBOARD_SPICE_ARGS=( + -device virtio-serial-pci + -device virtserialport,chardev=spicechannel0,name=com.redhat.spice.0 + -chardev spicevmc,id=spicechannel0,name=vdagent + ) + QEMU_EFI_ARGS=( + -M "${MACHINE}",accel=kvm + -drive file=/usr/share/edk2-ovmf/OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on + ) + QEMU_BIOS_ARGS=( + -M "${MACHINE}",accel=kvm + ) + QEMU_ISO_ARGS=( -cdrom "${ISO_PATH}" ) + QEMU_NET_ARGS=( + -device virtio-net,netdev="${NETDEV_NAME}" + -netdev user,id="${NETDEV_NAME}" + ) + QEMU_SOUND_ARGS=( -device ich9-intel-hda -device hda-duplex ) + QEMU_SSH_ARGS=( + -device virtio-net,netdev="${NETDEV_NAME}" + -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 +} # === Define functions for the actual QEMU controlling. === start_qemu(){ if [[ ! -f "${PID_FILE}" ]]; then @@ -284,24 +284,52 @@ resume_qemu(){ echo "Guest ${GUEST_NAME} is not running, nothing to resume." fi } +print_help(){ + echo " =======================================================================" + echo " control-vm.sh - simple QEMU/KVM vm manager" + echo " version: ${__VERSION__}" + echo " author Nils Freydank " + 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 " + 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. === -# "$1 != nothing" is already checked earlier. -case "${2}" in - start+connect) start_qemu && connect_to_vm;; - stop+restore) stop_qemu && restore_qemu;; - restore+restart) stop_qemu && restore_qemu && start_qemu && connect_to_vm;; - start) start_qemu;; - shutdown) shutdown_qemu;; - stop) stop_qemu;; - connect) connect_to_vm;; - save) save_qemu;; - restore) restore_qemu;; - pause) pause_qemu;; - resume) resume_qemu;; - *) echo "Unknown Operation!" - echo "Use one of: start | shutdown | stop | connect | save | restore | pause | resume" - echo "or combinations: start+connect | stop+restore | restore+restart" - echo "Note that stop means a forced stop.";; -esac +# Provide a sanity check first. +if [[ -z "${1}" ]]; then + echo " error: Please provide a guest name!" + print_help + exit 1 +else + set_params "${1}" + case "${2}" in + start+connect) start_qemu && connect_to_vm;; + stop+restore) stop_qemu && restore_qemu;; + restore+restart) stop_qemu && restore_qemu && start_qemu && connect_to_vm;; + start) start_qemu;; + shutdown) shutdown_qemu;; + stop) stop_qemu;; + connect) connect_to_vm;; + save) save_qemu;; + restore) restore_qemu;; + 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 diff --git a/todo b/todo index 052ba3a..7688788 100644 --- a/todo +++ b/todo @@ -7,4 +7,3 @@ # 7. shared dir without smb, but via FUSE # 8. bash and zsh-completion # 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 :)