Add a qemu port of my bhyverc script for running virtual machines on Linux.
This commit is contained in:
parent
3b007f8bc5
commit
37abf58271
@ -5,6 +5,41 @@
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
qemurc =
|
||||
(pkgs.writeScriptBin "qemurc" (
|
||||
builtins.readFile (
|
||||
pkgs.replaceVars ./files/qemurc.bash {
|
||||
"OVMFfd" = "${pkgs.OVMF.fd}";
|
||||
mount_root = "/vm";
|
||||
zfs_root = "zroot/linux/nix/vm";
|
||||
}
|
||||
)
|
||||
)).overrideAttrs
|
||||
(old: {
|
||||
buildCommand = ''
|
||||
${old.buildCommand}
|
||||
patchShebangs $out
|
||||
'';
|
||||
});
|
||||
qemurc_wrapped =
|
||||
(pkgs.writeScriptBin "qemurc" ''
|
||||
#!/usr/bin/env bash
|
||||
export "PATH=${
|
||||
lib.makeBinPath [
|
||||
pkgs.swtpm
|
||||
pkgs.tmux
|
||||
]
|
||||
}:''${PATH}"
|
||||
exec ${qemurc}/bin/qemurc "''${@}"
|
||||
'').overrideAttrs
|
||||
(old: {
|
||||
buildCommand = ''
|
||||
${old.buildCommand}
|
||||
patchShebangs $out
|
||||
'';
|
||||
});
|
||||
in
|
||||
{
|
||||
imports = [ ];
|
||||
|
||||
@ -22,6 +57,7 @@
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
qemu
|
||||
qemurc_wrapped
|
||||
];
|
||||
}
|
||||
]
|
||||
|
375
nix/configuration/roles/qemu/files/qemurc.bash
Normal file
375
nix/configuration/roles/qemu/files/qemurc.bash
Normal file
@ -0,0 +1,375 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
set -euo pipefail
|
||||
IFS=$'\n\t'
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
# Share a host directory to the guest via 9pfs.
|
||||
#
|
||||
# Inside the VM run:
|
||||
# mount -t virtfs -o trans=virtio sharename /some/vm/path
|
||||
# mount -t 9p -o cache=mmap -o msize=512000 sharename /mnt/9p
|
||||
# mount -t 9p -o trans=virtio,cache=mmap,msize=512000 bind9p /path/to/mountpoint
|
||||
|
||||
# Example usage:
|
||||
#
|
||||
# doas qemurc create-disk mint 10
|
||||
# doas env CD=/vm/iso/linuxmint-22.2-cinnamon-64bit.iso qemurc start mint
|
||||
# doas qemurc start mint
|
||||
# doas env WAYLAND_DISPLAY="$XDG_RUNTIME_DIR/$WAYLAND_DISPLAY" XDG_RUNTIME_DIR=/run/user/0 qemurc start mint
|
||||
|
||||
|
||||
: ${VERBOSE:="NO"} # or YES
|
||||
if [ "$VERBOSE" = "YES" ]; then
|
||||
set -x
|
||||
fi
|
||||
|
||||
: ${CPU_CORES:="1"}
|
||||
: ${MEMORY:="1G"}
|
||||
: ${GTK_ENABLE:="NO"} # Only enable one, either GTK or VNC
|
||||
: ${VNC_ENABLE:="NO"} # Only enable one, either GTK or VNC
|
||||
: ${VNC_LISTEN:="127.0.0.1:0"}
|
||||
: ${VNC_WIDTH:="1920"}
|
||||
: ${VNC_HEIGHT:="1080"}
|
||||
: ${AUDIO_ENABLE:="NO"}
|
||||
: ${TPM_ENABLE:="NO"}
|
||||
: ${BIND9P:=""}
|
||||
: "${CD:=}"
|
||||
|
||||
: ${SHUTDOWN_TIMEOUT:="600"}
|
||||
: ${MOUNT_ROOT:="@mount_root@"}
|
||||
: ${ZFS_ROOT:="@zfs_root@"}
|
||||
|
||||
|
||||
|
||||
############## Setup #########################
|
||||
|
||||
|
||||
function cleanup {
|
||||
sync
|
||||
|
||||
for p in "${pids[@]}"; do
|
||||
log "Killing $p"
|
||||
kill "$p"
|
||||
log "Killed $p"
|
||||
done
|
||||
|
||||
for vm in "${vms[@]}"; do
|
||||
log "Stopping $vm"
|
||||
stop_one "$vm"
|
||||
log "Stopped $vm"
|
||||
done
|
||||
}
|
||||
pids=()
|
||||
vms=()
|
||||
trap "set +e; cleanup" EXIT
|
||||
|
||||
function die {
|
||||
local status_code="$1"
|
||||
shift
|
||||
(>&2 echo "${@}")
|
||||
exit "$status_code"
|
||||
}
|
||||
|
||||
function log {
|
||||
(>&2 echo "${@}")
|
||||
}
|
||||
|
||||
############## Program #########################
|
||||
|
||||
function main {
|
||||
local cmd
|
||||
cmd=$1
|
||||
shift
|
||||
if [ "$cmd" = "start" ]; then
|
||||
init
|
||||
start "${@}"
|
||||
elif [ "$cmd" = "stop" ]; then
|
||||
init
|
||||
stop "${@}"
|
||||
elif [ "$cmd" = "status" ]; then
|
||||
init
|
||||
status "${@}"
|
||||
elif [ "$cmd" = "console" ]; then
|
||||
init
|
||||
console "${@}"
|
||||
elif [ "$cmd" = "_start_body" ]; then
|
||||
init
|
||||
start_body "${@}"
|
||||
elif [ "$cmd" = "create-disk" ]; then
|
||||
create_disk "${@}"
|
||||
else
|
||||
(>&2 echo "Unknown command: $cmd")
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function start {
|
||||
local num_vms="$#"
|
||||
if [ "$num_vms" -eq 0 ]; then
|
||||
log "No VMs specified."
|
||||
return 0
|
||||
fi
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
local name="$1"
|
||||
shift 1
|
||||
log "Starting VM $name."
|
||||
start_one "$name"
|
||||
[ "$#" -eq 0 ] || sleep 5
|
||||
done
|
||||
}
|
||||
|
||||
function start_one {
|
||||
local name="$1"
|
||||
local tmux_name="$name"
|
||||
tmux new-session -d -s "$tmux_name" "$0" "_start_body" "$name"
|
||||
}
|
||||
|
||||
function launch_pidfile {
|
||||
local pidfile="$1"
|
||||
shift 1
|
||||
mkdir -p "$(dirname "$pidfile")"
|
||||
cat > "${pidfile}" <<< "$$"
|
||||
set -x
|
||||
exec "${@}"
|
||||
}
|
||||
export -f launch_pidfile
|
||||
|
||||
function stop {
|
||||
local num_vms="$#"
|
||||
if [ "$num_vms" -eq 0 ]; then
|
||||
log "No VMs specified."
|
||||
return 0
|
||||
fi
|
||||
|
||||
while [ "$#" -gt 0 ]; do
|
||||
local name="$1"
|
||||
shift 1
|
||||
log "Stopping VM $name."
|
||||
stop_one "$name"
|
||||
[ "$#" -eq 0 ] || sleep 5
|
||||
done
|
||||
}
|
||||
|
||||
function stop_one {
|
||||
local name="$1"
|
||||
local pidfile="/run/qemurc/${name}/pid"
|
||||
|
||||
if [ ! -e "$pidfile" ]; then
|
||||
log "Pid file $pidfile does not exist."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local qemu_pid
|
||||
qemu_pid=$(cat "$pidfile")
|
||||
|
||||
if ps -p "$qemu_pid" >/dev/null; then
|
||||
# We cannot send a graceful shutdown command externally to qemu: https://gitlab.com/qemu-project/qemu/-/issues/148
|
||||
log "Killing ${name}:${qemu_pid}."
|
||||
kill -SIGTERM "$qemu_pid"
|
||||
fi
|
||||
|
||||
local timeout_start timeout_end
|
||||
timeout_start=$(date +%s)
|
||||
while ps -p "$qemu_pid" >/dev/null; do
|
||||
timeout_end=$(date +%s)
|
||||
if [ $((timeout_end-timeout_start)) -ge "$SHUTDOWN_TIMEOUT" ]; then
|
||||
log "${name}:${qemu_pid} took more than $SHUTDOWN_TIMEOUT seconds to shut down. Hard powering down."
|
||||
break
|
||||
fi
|
||||
|
||||
log "Waiting for ${name}:${qemu_pid} to exit."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
kill -9 "$qemu_pid"
|
||||
|
||||
local timeout_start timeout_end
|
||||
timeout_start=$(date +%s)
|
||||
while ps -p "$qemu_pid" >/dev/null; do
|
||||
timeout_end=$(date +%s)
|
||||
if [ $((timeout_end-timeout_start)) -ge "$SHUTDOWN_TIMEOUT" ]; then
|
||||
log "${name}:${qemu_pid} took more than $SHUTDOWN_TIMEOUT seconds to hard power down. Giving up."
|
||||
break
|
||||
fi
|
||||
|
||||
log "Waiting for ${name}:${qemu_pid} to hard power down."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
rm -f "$pidfile"
|
||||
|
||||
log "Finished stopping $name."
|
||||
}
|
||||
|
||||
function status {
|
||||
local num_vms="$#"
|
||||
|
||||
if [ "$num_vms" -gt 0 ]; then
|
||||
for name in "$@"; do
|
||||
status_one "$name"
|
||||
done
|
||||
else
|
||||
log "No VMs specified."
|
||||
fi
|
||||
}
|
||||
|
||||
function status_one {
|
||||
local name="$1"
|
||||
local pidfile="/run/qemurc/${name}/pid"
|
||||
|
||||
if [ ! -e "$pidfile" ]; then
|
||||
log "$name is not running."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local qemu_pid
|
||||
qemu_pid=$(cat "$pidfile")
|
||||
|
||||
if ! ps -p "$qemu_pid" >/dev/null; then
|
||||
log "$name is not running."
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "$name is running as pid $qemu_pid."
|
||||
}
|
||||
|
||||
function console {
|
||||
local num_vms="$#"
|
||||
|
||||
if [ "$num_vms" -gt 0 ]; then
|
||||
for name in "$@"; do
|
||||
log "Attaching to console of VM $name."
|
||||
console_one "$name"
|
||||
done
|
||||
else
|
||||
log "No VMs specified."
|
||||
fi
|
||||
}
|
||||
|
||||
function console_one {
|
||||
local name="$1"
|
||||
local tmux_name="$name"
|
||||
exec tmux a -t "$tmux_name"
|
||||
}
|
||||
|
||||
function init {
|
||||
mkdir -p /run/qemurc
|
||||
}
|
||||
|
||||
############## qemu ############################
|
||||
|
||||
function create_disk {
|
||||
local name="$1"
|
||||
local gigabytes="$2"
|
||||
|
||||
local zfs_path="${ZFS_ROOT}/${name}"
|
||||
local mount_path="${MOUNT_ROOT}/${name}"
|
||||
|
||||
zfs create -o mountpoint=none -o canmount=off "$zfs_path"
|
||||
zfs create -o "mountpoint=$mount_path" -o canmount=on "$zfs_path/settings"
|
||||
zfs create -s "-V${gigabytes}G" -o volmode=dev -o primarycache=metadata -o secondarycache=none "$zfs_path/disk0"
|
||||
zfs snapshot -r "$zfs_path@empty"
|
||||
|
||||
install -m0600 "@OVMFfd@/FV/OVMF_VARS.fd" "${mount_path}/"
|
||||
tee "${mount_path}/settings" <<EOF
|
||||
CPU_CORES="$CPU_CORES"
|
||||
MEMORY="$MEMORY"
|
||||
GTK_ENABLE="$GTK_ENABLE"
|
||||
VNC_ENABLE="$VNC_ENABLE"
|
||||
VNC_LISTEN="$VNC_LISTEN"
|
||||
VNC_WIDTH="$VNC_WIDTH"
|
||||
VNC_HEIGHT="$VNC_HEIGHT"
|
||||
AUDIO_ENABLE="$AUDIO_ENABLE"
|
||||
TPM_ENABLE="$TPM_ENABLE"
|
||||
BIND9P="$BIND9P"
|
||||
EOF
|
||||
|
||||
}
|
||||
|
||||
function start_body {
|
||||
local name="$1"
|
||||
local zfs_path="${ZFS_ROOT}/${name}"
|
||||
local mount_path="${MOUNT_ROOT}/${name}"
|
||||
local run_path="/run/qemurc/${name}"
|
||||
local mount_cd="$CD"
|
||||
local swtpm_sock="${run_path}/swtpm.sock"
|
||||
local swtpm_path="${MOUNT_ROOT}/${name}/swtpm"
|
||||
|
||||
install -d -m 0700 "$run_path"
|
||||
|
||||
if [ -e "${mount_path}/settings" ]; then
|
||||
source "${mount_path}/settings"
|
||||
fi
|
||||
|
||||
local additional_args=()
|
||||
|
||||
if [ -n "$BIND9P" ]; then
|
||||
additional_args+=(-device "virtio-9p-type,fsdev=${BIND9P},mount_tag=bind9p")
|
||||
fi
|
||||
|
||||
if [ -n "$mount_cd" ]; then
|
||||
additional_args+=(-cdrom "$mount_cd")
|
||||
fi
|
||||
if [ "$VNC_ENABLE" = "YES" ]; then
|
||||
additional_args+=(-vnc "${VNC_LISTEN},power-control=on")
|
||||
fi
|
||||
|
||||
if [ "$AUDIO_ENABLE" = "YES" ]; then
|
||||
additional_args+=(-audio "driver=pa,model=virtio,server=/run/user/11235/pulse/native")
|
||||
fi
|
||||
|
||||
if [ "$TPM_ENABLE" = "YES" ]; then
|
||||
install -d -m 0700 "$swtpm_path"
|
||||
swtpm socket --tpm2 --tpmstate dir="$swtpm_path" --ctrl type=unixio,path="$swtpm_sock" &
|
||||
local tpm_pid=$!
|
||||
pids+=("$tpm_pid")
|
||||
additional_args+=(-chardev "socket,id=chrtpm,path=$swtpm_sock"
|
||||
-tpmdev "emulator,id=tpm0,chardev=chrtpm"
|
||||
-device "tpm-tis,tpmdev=tpm0")
|
||||
fi
|
||||
|
||||
if [ "$GTK_ENABLE" = "YES" ]; then
|
||||
additional_args+=(
|
||||
-device 'virtio-gpu-gl,hostmem=8G,blob=true,venus=true'
|
||||
-display 'gtk,gl=on'
|
||||
-vga virtio
|
||||
)
|
||||
fi
|
||||
|
||||
|
||||
vms+=("$name")
|
||||
|
||||
local pidfile="/run/qemurc/${name}/pid"
|
||||
|
||||
local launch_cmd=()
|
||||
launch_cmd+=(
|
||||
launch_pidfile "$pidfile"
|
||||
qemu-system-x86_64
|
||||
-accel kvm
|
||||
-cpu host
|
||||
-smp cores="$CPU_CORES"
|
||||
-m "$MEMORY"
|
||||
-rtc base=localtime
|
||||
-drive "file=\"@OVMFfd@/FV/OVMF_CODE.fd\",if=pflash,format=raw,readonly=on"
|
||||
-drive "if=pflash,format=raw,file=\"$(readlink -f "${mount_path}/OVMF_VARS.fd")\""
|
||||
-drive "if=none,file=/dev/zvol/${zfs_path}/disk0,format=raw,id=hd0"
|
||||
-device 'nvme,serial=deadbeef,drive=hd0'
|
||||
-nic 'user,hostfwd=tcp::60022-:22'
|
||||
-boot order=d
|
||||
"${additional_args[@]}"
|
||||
)
|
||||
set +e
|
||||
rm -f "$pidfile"
|
||||
(
|
||||
IFS=$' \n\t'
|
||||
set -ex
|
||||
bash -c "${launch_cmd[*]}"
|
||||
)
|
||||
local exit_code=$?
|
||||
log "Exit code ${exit_code}"
|
||||
set -e
|
||||
}
|
||||
|
||||
main "${@}"
|
Loading…
x
Reference in New Issue
Block a user