diff --git a/docker/local-emulator/qemu/build-image.sh b/docker/local-emulator/qemu/build-image.sh index 26f476516..36f76d99f 100755 --- a/docker/local-emulator/qemu/build-image.sh +++ b/docker/local-emulator/qemu/build-image.sh @@ -333,19 +333,25 @@ build_one() { local monitor_sock="$tmp_dir/monitor.sock" local qga_sock="$tmp_dir/qga.sock" local snapshot_args=() + local runtime_disk_args=() local virtfs_args=(-virtfs "local,path=$tmp_dir,mount_tag=hostfs,security_model=none") if [ "$EMULATOR_BUILD_SNAPSHOT" = "1" ]; then + # STACKCFG runtime ISO lets stack.service start during the build — same + # disk shape render-stack-env expects at runtime. Placed before netdev + # so its virtio-blk PCI slot precedes virtio-net-pci, matching the + # resume argv order in run-emulator.sh (slots must line up or + # migrate-incoming fails the device-tree check). + runtime_disk_args=( + -drive "file=$runtime_iso,format=raw,if=virtio,readonly=on" + ) # QMP for stop/migrate/quit; virtio-serial + QGA channel so we can exec # inside the guest post-resume (only needed at runtime but harmless here). - # STACKCFG runtime ISO lets stack.service start during the build — same - # disk shape render-stack-env expects at runtime. snapshot_args=( -chardev "socket,id=monitor,path=$monitor_sock,server=on,wait=off" -mon "chardev=monitor,mode=control" -chardev "socket,path=$qga_sock,server=on,wait=off,id=qga0" -device virtio-serial -device "virtserialport,chardev=qga0,name=org.qemu.guest_agent.0" - -drive "file=$runtime_iso,format=raw,if=virtio,readonly=on" # Empty PCIe root port reserved for runtime hot-plug of virtio-9p. # The integrated pcie.0 bus on q35 / arm64-virt is static — hotplug # only works through a root port. Must be present at snapshot capture @@ -367,6 +373,7 @@ build_one() { -drive "file=$tmp_img,format=qcow2,if=virtio,discard=on,detect-zeroes=unmap" \ -drive "file=$seed_iso,format=raw,if=virtio,readonly=on" \ -drive "file=$bundle_iso,format=raw,if=virtio,readonly=on" \ + ${runtime_disk_args[@]+"${runtime_disk_args[@]}"} \ -netdev user,id=net0 \ -device virtio-net-pci,netdev=net0 \ ${virtfs_args[@]+"${virtfs_args[@]}"} \ diff --git a/docker/local-emulator/qemu/common.sh b/docker/local-emulator/qemu/common.sh index 38385e308..f5d3392d9 100755 --- a/docker/local-emulator/qemu/common.sh +++ b/docker/local-emulator/qemu/common.sh @@ -193,8 +193,8 @@ capture_vm_state() { if [ "$waited" -ge "$migrate_timeout" ]; then err "QMP migrate timed out after ${migrate_timeout}s" err "Last query-migrate response: $({ - printf '%s\n' '{\"execute\":\"qmp_capabilities\"}' - printf '%s\n' '{\"execute\":\"query-migrate\"}' + printf '%s\n' '{"execute":"qmp_capabilities"}' + printf '%s\n' '{"execute":"query-migrate"}' } | qmp_session "$sock" 2>/dev/null || true)" return 1 fi diff --git a/docker/local-emulator/qemu/run-emulator.sh b/docker/local-emulator/qemu/run-emulator.sh index aba9311b0..0845ff153 100755 --- a/docker/local-emulator/qemu/run-emulator.sh +++ b/docker/local-emulator/qemu/run-emulator.sh @@ -168,16 +168,12 @@ runtime_fingerprint() { } ensure_runtime_config_iso() { - local cfg_iso - cfg_iso="$(runtime_iso_path)" - if [ -s "$cfg_iso" ]; then - return 0 - fi - - # Fallback used when this script is invoked directly (e.g. `pnpm - # emulator:start`) rather than through the stack-cli, which generates the - # ISO via packages/stack-cli/src/lib/iso.ts. Mirrors the field set + volume - # label so the guest's render-stack-env mounts it the same way. + # Regenerate unconditionally: port env vars (PORT_PREFIX, EMULATOR_*_PORT) + # may have changed since the last run, and an ISO cached from a prior + # invocation would silently override them. The stack-cli path writes the + # ISO first via packages/stack-cli/src/lib/iso.ts; this re-write produces + # the same content for that flow (same field set + volume label) and is + # cheap enough (~ms) to run on every start. write_runtime_config_iso "$VM_DIR" } @@ -740,10 +736,9 @@ stop_vm() { fi fi rm -f "$VM_DIR/qemu.pid" "$VM_DIR/monitor.sock" "$VM_DIR/qga.sock" "$VM_DIR/serial.log" - # Do NOT remove runtime-config.iso: the CLI owns its lifecycle and run-emulator.sh - # cannot regenerate it. Removing here breaks the snapshot → cold-boot fallback - # (which calls stop_vm before recursing into cmd_start → ensure_runtime_config_iso). - # `cmd_reset` wipes $RUN_DIR entirely when a full reset is wanted. + # runtime-config.iso is left in place; ensure_runtime_config_iso regenerates + # it on the next start. `cmd_reset` wipes $RUN_DIR entirely when a full reset + # is wanted. } cmd_start() { @@ -854,7 +849,7 @@ snapshot_fallback_to_cold_boot() { warn "Retrying with cold boot (EMULATOR_NO_SNAPSHOT=1)..." stop_vm # Wipe the overlay + fingerprint so build_qemu_cmd re-creates a fresh one. - # runtime-config.iso is preserved by stop_vm (the CLI owns it). + # runtime-config.iso is regenerated by ensure_runtime_config_iso on recursion. rm -f "$VM_DIR/disk.qcow2" "$VM_DIR/base-image.fingerprint" \ "$VM_DIR/seed.phantom" "$VM_DIR/bundle.phantom" EMULATOR_NO_SNAPSHOT=1