#!/usr/bin/env sh set -x # version=0.11.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 ' # 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") 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} cat - print the content of a 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() { if [ "$1" = -f ] || [ "$1" = --foreground ]; then daemonize= else daemonize=-daemonize fi while echo "$1" | grep -q '^-'; do shift; done; 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 if [ -n "$1" ]; then QEMUSH_NAME=$1 socket_path=$(pathof socket) else socket_path=$(find ~/sockets/monitors -type s -printf '%T@ %p\n' | sort -r -n | head -1 | cut -d \ -f 2-) QEMUSH_NAME=$(basename "$socket_path") fi printf 'Attaching to \033[1m%s\033[0m, escape with C-d (EOF)\n' "$QEMUSH_NAME" socat -,rawer,escape=4 "UNIX-CONNECT:${socket_path}" echo } # 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 a shell as qemu user in its home directory public_shell() { cd || return # Again... # shellcheck disable=2209 [ -z "$SHELL" ] && SHELL=sh set -- "$SHELL" case "$SHELL" in sh|bash|zsh|ksh|mksh|oksh) set -- "$@" -i ;; esac exec "$@" } # 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)" } case "$1" in "") public_running ;; running|ls|add|edit|cat|start|attach|rm|diskls|diskadd|diskrm|shell|do|spice|help) function=$1 shift "public_${function}" "$@" ;; *) error_usage ;; esac