diff --git a/bin/lol b/bin/lol new file mode 100755 index 0000000..9399bc5 --- /dev/null +++ b/bin/lol @@ -0,0 +1,238 @@ +#!/usr/bin/env sh +# 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