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 = [ ];
|
imports = [ ];
|
||||||
|
|
||||||
@ -22,6 +57,7 @@
|
|||||||
{
|
{
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
qemu
|
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