diff --git a/Containerfile b/Containerfile index 94ce920..ba170f4 100644 --- a/Containerfile +++ b/Containerfile @@ -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 diff --git a/Makefile b/Makefile index 44633d0..5f35df0 100644 --- a/Makefile +++ b/Makefile @@ -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 \ diff --git a/src/guest/rootfs/etc/init.d/S21qemu-ga b/src/guest/rootfs/etc/init.d/S21qemu-ga new file mode 100755 index 0000000..cab4038 --- /dev/null +++ b/src/guest/rootfs/etc/init.d/S21qemu-ga @@ -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 diff --git a/src/host/rootfs/etc/init.d/S21qemu-ga b/src/host/rootfs/etc/init.d/S21qemu-ga new file mode 100755 index 0000000..cab4038 --- /dev/null +++ b/src/host/rootfs/etc/init.d/S21qemu-ga @@ -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 diff --git a/src/host/rootfs/usr/local/bin/netvm b/src/host/rootfs/usr/local/bin/netvm new file mode 100755 index 0000000..9936be7 --- /dev/null +++ b/src/host/rootfs/usr/local/bin/netvm @@ -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