control-vm.sh: Refactor logic and provide a help output
This commit is contained in:
parent
72013bf33d
commit
bceb9a9710
318
control-vm.sh
318
control-vm.sh
@ -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 ===
|
|
||||||
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
|
|
||||||
|
|
||||||
|
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. ===
|
# === 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
1
todo
@ -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 :)
|
|
||||||
|
Loading…
Reference in New Issue
Block a user