feat: initial qemu-ga based netvm command to control guest

This commit is contained in:
Lance Vick 2025-04-24 23:10:49 -07:00
parent b64d76b60d
commit 18fa25b87e
Signed by: lrvick
GPG Key ID: 8E47A1EC35A1551D
5 changed files with 201 additions and 1 deletions

View File

@ -62,13 +62,14 @@ FROM stagex/user-yq@sha256:47a39bfdeffff4344f41d60aa81671c7fd30c3e5e6d21ced21a05
FROM stagex/user-edk2@sha256:db24be51d35117d264dccfc44f0ca331f59d738083170cd9bb86b49a5c06abff AS user-edk2
FROM stagex/core-ca-certificates@sha256:d6fca6c0080e8e5360cd85fc1c4bd3eab71ce626f40602e38488bfd61fd3e89d AS core-ca-certificates
FROM stagex/user-linux-guest-net@sha256:994b6fe49dd4331b32b0854055bff31b06db5eabdeafb32b2c0d55465b7ccf45 AS user-linux-guest-net
FROM stagex/user-linux-airgap@sha256:c8575c92aa63544ee92a820a97034fcc203abf2671c0e7e21d0c4e20daef8827 AS user-linux-airgap
FROM stagex/user-linux-airgap@sha256:4f163e5f1f09f87d8f0fcf060193345f0b4dd8dbff2a2b2aec1cd4c254a628ca AS user-linux-airgap
FROM stagex/user-libimobiledevice-glue@sha256:3ce674285cbc04b694b7e400703868fcaac65401f2f2ca2aa2b720b3e0efee3c AS user-libimobiledevice-glue
FROM stagex/user-libimobiledevice@sha256:fcda68bdc397213fa76bd893472a304b093522aaac28e36f458275b93bb1af34 AS user-libimobiledevice
FROM stagex/user-libplist@sha256:2d776cb4eca3689a8bd6ac755a23f492850bf6c7b0c72e3525db6135e4d6e0bc AS user-libplist
FROM stagex/user-libusb@sha256:53d499555164f12d9e87118a6d44e1d07f0b1cc9081a29eb66975662be818a00 AS user-libusb
FROM stagex/user-libusbmuxd@sha256:1e97f0a2ede0ee5fac9b056d0395e12b77c9f0bf550f9d0c20734ce0617eb51f AS user-libusbmuxd
FROM stagex/user-usbmuxd@sha256:90f687d2368328b76141badc382a21873a5b44d4ddccf851c017caf1e78af418 AS user-usbmuxd
FROM stagex/user-socat@sha256:990a70ae13462d8ba0a925fe959dd83070cbecdb3f91ff145caca5232171f3b8 AS user-socat
FROM scratch AS base
ARG VERSION development
@ -124,7 +125,11 @@ COPY --from=user-libimobiledevice . initramfs
COPY --from=user-libplist . initramfs
COPY --from=user-libusb . initramfs
COPY --from=user-libusbmuxd . initramfs
COPY --from=core-gcc /usr/lib/. initramfs/usr/lib/
COPY --from=user-usbmuxd . initramfs
COPY --from=user-glib . initramfs
COPY --from=user-numactl . initramfs
COPY --from=user-qemu /usr/bin/qemu-ga initramfs/usr/bin/
COPY src/guest/rootfs/ initramfs
RUN <<-EOF
@ -234,6 +239,7 @@ COPY --from=user-libslirp . initramfs
COPY --from=user-seabios . initramfs
COPY --from=user-canokey-qemu . initramfs
COPY --from=user-qemu . initramfs
COPY --from=user-socat . initramfs
COPY --from=user-libzbar . initramfs
COPY --from=user-keyfork . initramfs
COPY --from=user-icepick . initramfs

View File

@ -73,6 +73,9 @@ vm: out/dev-shell.digest out/airgap.iso out/sdcard.img
-device intel-iommu,intremap=on \
-netdev user,id=net0 \
-device e1000,netdev=net0 \
-chardev socket,path=out/qga.sock,server=on,wait=off,id=qga0 \
-device virtio-serial \
-device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0 \
$(if $(filter $(EFI),true) ,\
-bios /usr/share/ovmf/OVMF.fd \
-drive id=boot$(,)if=virtio$(,)format=raw$(,)file=out/airgap.iso \

View File

@ -0,0 +1,30 @@
#!/bin/sh
### BEGIN INIT INFO
# Provides: qemu-ga
# Required-Start: $remote_fs $syslog
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop:
# Short-Description: Start QEMU Guest Agent
### END INIT INFO
case "$1" in
start)
echo "Starting QEMU Guest Agent"
cat /proc/cpuinfo | grep QEMU && qemu-ga &
;;
stop)
echo "Stopping QEMU Guest Agent"
killall qemu-ga
;;
restart)
"$0" stop
"$0" start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac
exit 0

View File

@ -0,0 +1,30 @@
#!/bin/sh
### BEGIN INIT INFO
# Provides: qemu-ga
# Required-Start: $remote_fs $syslog
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop:
# Short-Description: Start QEMU Guest Agent
### END INIT INFO
case "$1" in
start)
echo "Starting QEMU Guest Agent"
cat /proc/cpuinfo | grep QEMU && qemu-ga &
;;
stop)
echo "Stopping QEMU Guest Agent"
killall qemu-ga
;;
restart)
"$0" stop
"$0" start
;;
*)
echo "Usage: $0 {start|stop|restart}"
exit 1
;;
esac
exit 0

View File

@ -0,0 +1,131 @@
#!/bin/bash
set -eu
COMMAND=($@)
QGA_SOCKET=/var/run/netvm_qga.sock
LOCKFILE=/var/run/netvm.pid
json() {
jq -ncM "$@"
}
qemu_execute() {
local COMMAND ARGS
COMMAND="$1"
ARGS="${2-}"
json --arg cmd "$COMMAND" --argjson args "$ARGS" '{"execute": $cmd, "arguments": $args}' >&$FD_SOCKET_OUT
local LINE
read -t 5 -r -u $FD_SOCKET_IN LINE
local ERROR=$(jq -r '.error.desc // empty' <<< "$LINE")
if [[ -n "$ERROR" ]]; then
echo "$ERROR" >&2
return 1
fi
GA_RETURN=$(jq -cM .return <<< "$LINE")
}
qemu_ga() {
local COMMAND ARGS
COMMAND="$1"
ARGS="$2"
coproc FDS (
exec socat - UNIX-CONNECT:"${QGA_SOCKET}"
)
FD_SOCKET_IN=${FDS[0]}
FD_SOCKET_OUT=${FDS[1]}
local PID=$$
qemu_execute guest-sync "$(json --argjson pid "$PID" '{"id": $pid}')"
[[ "$(jq -re . <<< "$GA_RETURN")" = "$$" ]] || (echo "guest-sync mismatch" >&2 && return 1)
qemu_execute "$COMMAND" "$ARGS"
echo "$GA_RETURN" 2>&1
local RETURN
kill -INT "$FDS_PID" 2>/dev/null
wait "$FDS_PID" || RETURN=$?
if [[ $RETURN != 130 ]]; then
return $RETURN
fi
}
function start(){
[ ! -f "${LOCKFILE}" ] || { echo "Error: Netvm already running"; exit 1; }
echo "Starting netvm";
qemu-system-x86_64 -m 512M \
--machine q35 \
-nographic \
-serial none \
-monitor none \
-net none \
-cdrom /guest.img \
-boot order=d \
-chardev socket,path=/var/run/netvm_qga.sock,server=on,wait=off,id=qga0 \
-device virtio-serial \
-device virtserialport,chardev=qga0,name=org.qemu.guest_agent.0 &
echo $! > "${LOCKFILE}"
}
function stop(){
pkill -F "${LOCKFILE}" && rm "${LOCKFILE}"
}
function status(){
qemu_ga guest-get-host-name "{}" | jq -r '."host-name"'
pid=$(qemu_ga guest-exec '{"path": "uptime", "capture-output": true}' | jq -r '.pid')
out=$(qemu_ga guest-exec-status "$(jq -n --argjson pid "$pid" '{pid: $pid }')" \
| jq -r '."out-data"' \
| base64 -d \
)
echo $out
}
function run(){
[ -z "${COMMAND[1]}" ] && { echo "Error: missing command"; exit 1; }
[ -f "${LOCKFILE}" ] || { echo "Error: Netvm is not running"; exit 1; }
[ -S "${QGA_SOCKET}" ] || { echo "Error: Netvm QGA socket is missing"; exit 1; }
local cmd="${COMMAND[1]}"
local args="${COMMAND[@]:2}"
local args_json="[]"
if [[ -n "$args" ]]; then
args_json=$(printf '%s\n' "$args" | jq -R . | jq -s .)
fi
local request
request=$( \
jq -n \
--arg path "$cmd" \
--argjson args "$args_json" \
'{
path: $path,
arg: $args,
"capture-output": true
}' \
)
pid=$(qemu_ga guest-exec "$request" | jq -r '.pid')
out=$(qemu_ga guest-exec-status "$(jq -n --argjson pid "$pid" '{pid: $pid }')" \
| jq -r '."out-data"' \
)
sleep 1
echo $out
echo $out | base64 -d
}
function help(){
echo "Valid operations: start, stop, run, help"
}
case "${COMMAND[0]}" in
status) status ;;
start) start ;;
stop) stop ;;
run) run ;;
help) help ;;
*) help ;;
esac