#!/usr/bin/env bash # version=0.8.0 # Re-exec the script as qemu via sudo (only if needed) [ "$(whoami)" != qemu ] && exec sudo -E -H -u qemu -- "$0" "$@" # Environment PATH="${HOME}/launchers:${HOME}/bin:${PATH}" # Aliases alias ls='ls --color=auto' alias exec='exec ' shopt -s expand_aliases # Set a restrictive umask to make sure qemu user files are private umask 7027 # Function to print a colored error perror() { >&2 printf '\033[1;31mKO:\033[0m \033[1m%s\033[0m\n' "$*" } # Function to show the usage public_help() { name=$(basename "$0") exec cat << EOF ${name}: usage: ${name} running - (default behaviour) list running VMs ${name} ls - list available VMs ${name} add [] - add a launching script ${name} edit - edit VM launching script ${name} start - start a VM ${name} attach - attach to the VM monitor socket ${name} rm - delete launch script ${name} diskls - list available disk images ${name} diskadd - create a disk image ${name} diskrm - delete disk image ${name} shell - start a shell as user qemu ${name} do - run shell input as user qemu ${name} spice [] - expose a SPICE socket to TCP ${name} help - show this help EOF } # Function to throw an invalid usage error (skill issue) error_usage() { perror "invalid usage" >&2 public_help return 1 } # Function to start a virtual machine public_start() { export QEMUSH_NAME="$1" set -- "$@" \ -name "$QEMUSH_NAME" \ -monitor "unix:$(pathof socket),server,nowait" \ -daemonize if ! "$@"; then perror "error launching virtual machine \"${QEMUSH_NAME}\"" return 2 fi } # Attach to a running virtual machine output, the latest opened if no # argument is provided public_attach() { export QEMUSH_NAME="$1" exec socat -,rawer,escape=15 "UNIX-CONNECT:$(pathof socket)" } # List running virtual machines public_running() { cd || return echo "Running machines:" exec ls -t sockets/monitors } # List available virtual machines entrypoints public_ls() { cd || return echo "Available machines:" exec ls launchers } # Create a copy-on-write disk for a virtual machine public_diskadd() { export QEMUSH_NAME="$1" exec qemu-img create -f qcow2 "$(pathof disk)" "$2" } # Delete a disk public_diskrm() { for disk in "$@"; do export QEMUSH_NAME="$disk" rm -vi -- "$(pathof disk)" done } # List available disks public_diskls() { cd || return echo "Available disks:" exec ls disks } # Edit a virtual machine entrypoint with a text editor public_edit() { file="launchers/${1}" # I don't even know why shellcheck gives me this warning # shellcheck disable=2209 [ -z "$EDITOR" ] && EDITOR=vi set -e cd touch -- "$file" chmod u+x -- "$file" exec "$EDITOR" -- "$file" } # Delete a virtual machine entrypoint public_rm() { cd ~/launchers || return exec rm -vi -- "$@" } # Invoke bash as qemu user in its home directory public_shell() { cd || return exec bash -i } # Output the content of an entrypoint, with coloration if on a virtual # terminal public_cat() { cd ~/launchers || return exec cat -- "$@" } # Copy a file in entrypoints folder public_add() { if [ -z "$1" ]; then perror "specify the path of a launching script you want to add" return 1 fi if [ -n "$2" ]; then destination="$2" else destination=$(basename "$1") fi destination="${HOME}/launchers/${destination}" trap return EXIT set -e cp -vi -- "$1" "$destination" chmod 0740 -- "$destination" set +e trap - EXIT } # Run shell commands as qemu public_do() { exec sh -c "$*" } # Expose SPICE via TCP public_spice() { export QEMUSH_NAME="$1" if [ -n "$2" ]; then port=$2 else port=$(first-free-port 5900) fi exec tmux new -d -s "TCP port ${port} -> ${QEMUSH_NAME}" \ socat TCP-LISTEN:"${port},reuseaddr,fork" UNIX-CLIENT:"$(pathof spice)" } function_exists() { declare -F \ | cut -d \ -f 3- \ | grep '^public_' \ | sed 's/^public_//' \ | grep -q "^${1}\$" } # Defauts to `active` if no function is supplied; else checks for a public # function named after the argument; else fails if [ -z "$1" ]; then public_running elif function_exists "$1"; then function=$1 shift "public_${function}" "$@" else error_usage fi