✨ Sync 2026-07-12 08:33:33

This commit is contained in:
github-actions[bot] committed 2026-07-12 08:33:33 +08:00
1 parent 01a1d05223
commit dbdb9f9a83
71 files changed
+18993 -4

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=qemu-vms
PKG_VERSION:=0.0.1
PKG_RELEASE:=1
PKG_MAINTAINER:=Konstantine Shevlakov <shevlakov@132lan.ru>
include $(INCLUDE_DIR)/package.mk
define Package/$(PKG_NAME)
SECTION:=utils
CATEGORY:=Utilities
PKGARCH:=all
TITLE:=QEMU Virtual Machines Simple (VMS)
DEPENDS:= +qemu-x86_64-softmmu \
+qemu-bridge-helper \
+minicom \
+socat
endef
define Package/$(PKG_NAME)/description
Scripts to run Virtual Machines using QEMU via uci shell.
Support mupltiple run stop and manage machnes via tty.
endef
define Package/$(PKG_NAME)/conffiles
/etc/config/qemu-vms
endef
define Build/Prepare
endef
define Build/Configure
endef
define Build/Compile
endef
define Package/$(PKG_NAME)/install
$(CP) ./root/* $(1)/
endef
$(eval $(call BuildPackage,$(PKG_NAME)))
+45
View File
@@ -0,0 +1,45 @@
config pci-passthrough 'wifi_example'
option pci_id '05:00.0'
option vendor_id '14c3 7915'
config usb-passthrough 'example_usb'
option vendor_id '0bda'
option product_id 'c811'
config network 'tap_example'
option mac '52:54:00:31:6c:4d'
option ifname 'tap0'
config network 'tap_example_bridge'
option mac '52:54:00:31:6c:4e'
option ifname 'tap1'
option bridge 'br-tap'
config vm 'vm_example'
option enabled '1'
option image '/mnt/disk/vm/openwrt.img'
option memory '256'
option smp '1'
option cpu 'host'
option pci_passthrough 'wifi_ap'
option display_type 'serial'
list network 'tap_example'
config vm 'winxp_example'
option enabled '1'
option memory '2048'
option smp '2'
option cpu 'qemu64'
option display_type 'vnc'
option vnc_port '5902'
list network 'tap_example_bridge'
option cdrom '/mnt/disk/vm/wxp.iso'
option image '/mnt/disk/vm/xp.img'
option disk_bus 'ide'
list custom_arg '-boot order=d'
list custom_arg '-machine pc-i440fx'
list custom_arg '-global q35-pcihost.x-pci-hole64-fix=false'
list custom_arg '-drive if=floppy,file=/mnt/disk/vm/floppy_xp.img,format=raw'
list custom_arg '-device VGA,vgamem_mb=64'
+249
View File
@@ -0,0 +1,249 @@
#!/bin/sh /etc/rc.common
START=95
STOP=10
USE_PROCD=1
STATUS_DIR="/var/run/qemu-vms"
is_anonymous_section() {
# UCI auto-generated names look like: cfgXXXXXX (cfg + hex)
case "$1" in
cfg[0-9a-f][0-9a-f][0-9a-f][0-9a-f]*) return 0 ;;
*) return 1 ;;
esac
}
warn_if_anonymous() {
local cfg="$1"
if is_anonymous_section "$cfg"; then
logger -t qemu-vms "WARNING: VM section '$cfg' is anonymous (no name set). " \
"Paths (console/qmp socket, pidfile) are derived from this auto-generated " \
"UCI id and may change if the config is reordered or the section is recreated. " \
"Please set an explicit section name, e.g. config vm 'myvm'."
fi
}
build_netargs() {
local cfg="$1"
idx=0
netdev_opts=""
device_opts=""
config_list_foreach "$cfg" network _add_net
}
_add_net() {
local netcfg="$1"
local mac ifname bridge
config_get mac "$netcfg" mac
config_get ifname "$netcfg" ifname
config_get bridge "$netcfg" bridge
local id="net${idx}"
# QEMU's tap "script=" hook only ever calls the script with the tap
# ifname as $1 - there is no way to pass a second argument (like the
# target bridge) through QEMU itself. So instead of pointing directly
# at /etc/qemu-ifup, generate a tiny per-interface wrapper that bakes
# in this network section's bridge and calls /etc/qemu-ifup with both
# arguments. If "bridge" is empty, the wrapper still runs but passes
# an empty $2, and /etc/qemu-ifup simply skips the enslave step.
local ifup_wrapper="$STATUS_DIR/qemu-ifup-$netcfg.sh"
mkdir -p "$STATUS_DIR"
cat > "$ifup_wrapper" <<-EOF
#!/bin/sh
exec /etc/qemu-ifup "\$1" "$bridge"
EOF
chmod +x "$ifup_wrapper"
netdev_opts="$netdev_opts -netdev tap,id=$id,ifname=$ifname,script=$ifup_wrapper,downscript=no"
local mac_opt=""
[ -n "$mac" ] && mac_opt=",mac=$mac"
device_opts="$device_opts -device virtio-net-pci${mac_opt},netdev=$id"
idx=$((idx + 1))
}
build_usbargs() {
local cfg="$1"
usb_opts=""
usb_needed=0
config_list_foreach "$cfg" usb_passthrough _add_usb
}
_add_usb() {
local usbcfg="$1"
local vendor_id product_id
config_get vendor_id "$usbcfg" vendor_id
config_get product_id "$usbcfg" product_id
[ -n "$vendor_id" ] && [ -n "$product_id" ] || return 0
usb_opts="$usb_opts -device usb-host,vendorid=0x$vendor_id,productid=0x$product_id"
usb_needed=1
}
build_customargs() {
local cfg="$1"
custom_opts=""
config_list_foreach "$cfg" custom_arg _add_customarg
}
_add_customarg() {
local arg="$1"
# Deliberately unquoted below (word-splitting is intentional): a
# single list entry like "-device foo,bar=baz" should become two
# separate argv elements ("-device" and "foo,bar=baz"), same as if
# the admin had typed it directly on a qemu command line. This is an
# escape hatch for arbitrary flags we don't have a dedicated UCI
# option for - the admin is trusted with their own local config.
custom_opts="$custom_opts $arg"
}
build_diskargs() {
local cfg="$1"
local image="$2"
local bus
config_get bus "$cfg" disk_bus "virtio"
disk_controller_opts=""
disk_opts=""
case "$bus" in
virtio)
disk_opts="-drive file=$image,format=raw,if=virtio"
;;
ide)
# Widest guest compatibility (old Windows/DOS/legacy kernels
# without virtio drivers), at some cost to I/O throughput.
disk_opts="-drive file=$image,format=raw,if=ide"
;;
scsi)
disk_controller_opts="-device virtio-scsi-pci,id=scsi0"
disk_opts="-drive file=$image,format=raw,if=none,id=drive0 -device scsi-hd,drive=drive0,bus=scsi0.0"
;;
sata|ahci)
disk_controller_opts="-device ahci,id=ahci0"
disk_opts="-drive file=$image,format=raw,if=none,id=drive0 -device ide-hd,drive=drive0,bus=ahci0.0"
;;
*)
logger -t qemu-vms "WARNING: VM '$cfg' has unknown disk_bus '$bus', falling back to virtio"
disk_opts="-drive file=$image,format=raw,if=virtio"
;;
esac
}
start_vm() {
local cfg="$1"
local enabled image memory smp cpu pci_passthrough
config_get_bool enabled "$cfg" enabled 0
[ "$enabled" = "1" ] || return 0
local existing_pid
if [ -n "$existing_pid" ] && kill -0 "$existing_pid" 2>/dev/null && \
grep -aq "qemu-system-x86_64" "/proc/$existing_pid/cmdline" 2>/dev/null; then
logger -t qemu-vms "start_vm: '$cfg' already running (pid $existing_pid), skipping"
return 0
fi
warn_if_anonymous "$cfg"
config_get image "$cfg" image
config_get cdrom "$cfg" cdrom
config_get memory "$cfg" memory 128
config_get smp "$cfg" smp 1
config_get cpu "$cfg" cpu "host"
config_get pci_passthrough "$cfg" pci_passthrough
config_get display_type "$cfg" display_type "serial"
config_get vnc_port "$cfg" vnc_port
local console_sock="/var/run/qemu-$cfg.sock"
local console_log="/var/log/qemu-$cfg-console.log"
local qmp_sock="/var/run/qemu-$cfg.qmp"
build_netargs "$cfg"
build_usbargs "$cfg"
build_customargs "$cfg"
build_diskargs "$cfg" "$image"
local pci_id="" vendor_id="" pci_opts=""
if [ -n "$pci_passthrough" ]; then
config_get pci_id "$pci_passthrough" pci_id
config_get vendor_id "$pci_passthrough" vendor_id
if [ -n "$vendor_id" ] && [ -n "$pci_id" ]; then
[ -e "/sys/bus/pci/devices/0000:$pci_id/driver" ] || \
echo "$vendor_id" > /sys/bus/pci/drivers/vfio-pci/new_id 2>/dev/null
pci_opts="-device vfio-pci,host=$pci_id"
fi
fi
local cdrom_opts=""
[ -n "$cdrom" ] && cdrom_opts="-cdrom $cdrom"
local usb_controller_opts=""
[ "$usb_needed" = "1" ] && usb_controller_opts="-device qemu-xhci,id=usb-bus0"
local vnc_opts=""
if [ "$display_type" = "vnc" ] && [ -n "$vnc_port" ]; then
local vnc_display=$((vnc_port - 5900))
local vnc_ws_port=$((vnc_port + 1000))
if [ "$vnc_display" -ge 0 ] 2>/dev/null; then
vnc_opts="-vnc :${vnc_display},websocket=${vnc_ws_port}"
else
logger -t qemu-vms "WARNING: VM '$cfg' has vnc_port=$vnc_port, which is below 5900. Skipping VNC."
fi
elif [ "$display_type" = "vnc" ]; then
logger -t qemu-vms "WARNING: VM '$cfg' has display_type=vnc but no vnc_port set. Skipping VNC."
fi
local display_opts="-display none -nographic"
[ -n "$vnc_opts" ] && display_opts=""
#rm -f "$console_sock" "$qmp_sock"
mkdir -p "$STATUS_DIR"
mkdir -p "$(dirname "$console_log")"
procd_open_instance "qemu-$cfg"
procd_set_param command /usr/bin/qemu-system-x86_64 \
-enable-kvm -cpu "$cpu" -smp "$smp" -m "${memory}M" \
$disk_controller_opts $disk_opts \
$device_opts $netdev_opts $pci_opts $cdrom_opts $usb_controller_opts $usb_opts $vnc_opts $custom_opts \
-device virtio-rng-pci \
-machine q35 \
-chardev socket,id=con0,path="$console_sock",server=on,wait=off,logfile="$console_log",logappend=on \
-serial chardev:con0 \
-chardev socket,id=qmp0,path="$qmp_sock",server=on,wait=off \
-qmp chardev:qmp0 \
-monitor none $display_opts
procd_set_param respawn 3600 5 5
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param pidfile "$STATUS_DIR/$cfg.pid"
procd_close_instance
# NOTE: no persistent socat/console bridge here on purpose.
# QEMU discards console output cleanly when nobody is connected to
# console_sock (server=on,wait=off), while logfile= still captures
# everything continuously (boot log never stalls). Interactive
# access is attached on-demand via /usr/bin/vm-console, which only
# connects for the duration of the minicom session.
}
start_service() {
config_load qemu-vms
if [ -n "$1" ]; then
start_vm "$1"
else
config_foreach start_vm vm
fi
}
service_triggers() {
procd_add_reload_trigger "qemu-vms"
}
+22
View File
@@ -0,0 +1,22 @@
#!/bin/sh
# /etc/qemu-ifup
#
# Invoked by QEMU itself (via -netdev tap,...,script=/etc/qemu-ifup) right
# after it creates the tap interface. $1 is the tap ifname QEMU created;
# $2 (bridge) is passed through via the netdev's "downscript"-adjacent
# vhost/helper convention used by build_netargs in /etc/init.d/qemu-vms,
# which appends the bridge name as a second positional argument.
#
# We only enslave the interface into a bridge if one was actually
# configured on the network section - a tap interface with no "bridge"
# option stays standalone (e.g. for point-to-point or manually managed
# setups).
ifname="$1"
bridge="$2"
ip link set "$ifname" up
if [ -n "$bridge" ]; then
ip link set "$ifname" master "$bridge"
fi
+61
View File
@@ -0,0 +1,61 @@
#!/bin/sh
# vm-console <vm-name> [log|attach]
#
# attach (default): connects to the VM's QEMU console socket on demand,
# bridges it to a temporary PTY via socat, launches minicom, and tears
# the bridge down again on exit. QEMU only sees a client connected for
# the duration of this session, so boot never stalls waiting for a
# reader, and disconnecting minicom never backs up into QEMU.
#
# log: just tails the always-on boot/console logfile (safe to run at
# any time, does not touch the live console socket at all).
VM="$1"
MODE="${2:-attach}"
if [ -z "$VM" ]; then
echo "Usage: vm-console <vm-name> [log|attach]" >&2
exit 1
fi
CONSOLE_SOCK="/var/run/qemu-$VM.sock"
CONSOLE_LOG="/var/log/qemu-$VM-console.log"
TMP_PTY="/var/run/qemu-$VM.console-pty.$$"
case "$MODE" in
log)
[ -f "$CONSOLE_LOG" ] || { echo "No logfile yet: $CONSOLE_LOG" >&2; exit 1; }
exec tail -n 200 -f "$CONSOLE_LOG"
;;
attach)
[ -S "$CONSOLE_SOCK" ] || { echo "VM '$VM' is not running (no socket at $CONSOLE_SOCK)" >&2; exit 1; }
rm -f "$TMP_PTY"
socat UNIX-CONNECT:"$CONSOLE_SOCK" PTY,link="$TMP_PTY",raw,echo=0 &
SOCAT_PID=$!
# give socat a moment to create the pty symlink
for i in $(seq 1 20); do
[ -e "$TMP_PTY" ] && break
sleep 0.1
done
if [ ! -e "$TMP_PTY" ]; then
echo "Failed to create console bridge for '$VM'" >&2
kill "$SOCAT_PID" 2>/dev/null
exit 1
fi
cleanup() {
kill "$SOCAT_PID" 2>/dev/null
rm -f "$TMP_PTY"
}
trap cleanup EXIT INT TERM
minicom -D "$TMP_PTY"
;;
*)
echo "Unknown mode: $MODE (expected 'log' or 'attach')" >&2
exit 1
;;
esac