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
	 Tom Alexander
						Tom Alexander