vagrant-qemu 0.6.4 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +107 -0
  3. data/README.md +203 -18
  4. data/lib/vagrant-qemu/action/cleanup_virtiofs.rb +18 -0
  5. data/lib/vagrant-qemu/action/mount_virtiofs.rb +35 -0
  6. data/lib/vagrant-qemu/action/prepare_forwarded_port_collision_params.rb +17 -11
  7. data/lib/vagrant-qemu/action/reset_virtiofs_args.rb +40 -0
  8. data/lib/vagrant-qemu/action/start_instance.rb +69 -5
  9. data/lib/vagrant-qemu/action.rb +8 -0
  10. data/lib/vagrant-qemu/cap/disk.rb +46 -1
  11. data/lib/vagrant-qemu/command/serial_config.rb +52 -0
  12. data/lib/vagrant-qemu/config.rb +22 -3
  13. data/lib/vagrant-qemu/driver.rb +267 -51
  14. data/lib/vagrant-qemu/errors.rb +18 -0
  15. data/lib/vagrant-qemu/plugin.rb +10 -0
  16. data/lib/vagrant-qemu/synced_folder_virtiofs.rb +259 -0
  17. data/lib/vagrant-qemu/version.rb +1 -1
  18. data/locales/en.yml +36 -0
  19. metadata +7 -56
  20. data/.gitignore +0 -24
  21. data/CLAUDE.md +0 -81
  22. data/Gemfile +0 -16
  23. data/Rakefile +0 -42
  24. data/spec/acceptance/basic_up_spec.rb +0 -83
  25. data/spec/acceptance/halt_destroy_spec.rb +0 -69
  26. data/spec/acceptance/helper.rb +0 -69
  27. data/spec/acceptance/network_spec.rb +0 -134
  28. data/spec/acceptance/port_collision_spec.rb +0 -53
  29. data/spec/e2e/advanced_network_spec.rb +0 -123
  30. data/spec/e2e/disk_spec.rb +0 -107
  31. data/spec/e2e/forwarded_port_spec.rb +0 -107
  32. data/spec/e2e/halt_spec.rb +0 -48
  33. data/spec/e2e/helper.rb +0 -161
  34. data/spec/e2e/package_spec.rb +0 -95
  35. data/spec/e2e/provision_spec.rb +0 -35
  36. data/spec/e2e/reload_spec.rb +0 -58
  37. data/spec/e2e/smoke_spec.rb +0 -60
  38. data/spec/e2e/socket_network_spec.rb +0 -214
  39. data/spec/e2e/socket_vmnet_spec.rb +0 -131
  40. data/spec/e2e/support/qemu_no_stream_shim.sh +0 -14
  41. data/spec/spec_helper.rb +0 -77
  42. data/spec/unit/action/cloud_init_network_spec.rb +0 -177
  43. data/spec/unit/action/destroy_spec.rb +0 -46
  44. data/spec/unit/action/export_spec.rb +0 -103
  45. data/spec/unit/action/import_spec.rb +0 -52
  46. data/spec/unit/action/package_spec.rb +0 -22
  47. data/spec/unit/action/package_vagrantfile_spec.rb +0 -41
  48. data/spec/unit/action/prepare_forwarded_port_collision_params_spec.rb +0 -104
  49. data/spec/unit/action/read_state_spec.rb +0 -50
  50. data/spec/unit/action/start_instance_port_spec.rb +0 -77
  51. data/spec/unit/action/start_instance_spec.rb +0 -64
  52. data/spec/unit/action/warn_networks_spec.rb +0 -38
  53. data/spec/unit/cap/disk_spec.rb +0 -104
  54. data/spec/unit/config_spec.rb +0 -186
  55. data/spec/unit/driver/delete_spec.rb +0 -39
  56. data/spec/unit/driver/options_yaml_spec.rb +0 -49
  57. data/spec/unit/driver/package_spec.rb +0 -51
  58. data/spec/unit/driver/ssh_port_spec.rb +0 -46
  59. data/spec/unit/driver/start_cmd_advanced_spec.rb +0 -120
  60. data/spec/unit/driver/start_cmd_order_spec.rb +0 -67
  61. data/spec/unit/driver/start_cmd_socket_vmnet_spec.rb +0 -144
  62. data/spec/unit/driver/start_cmd_spec.rb +0 -161
  63. data/spec/unit/driver/start_edge_cases_spec.rb +0 -72
  64. data/spec/unit/driver/state_spec.rb +0 -60
  65. data/spec/unit/driver/stop_spec.rb +0 -101
  66. data/spec/unit/network/backend_for_spec.rb +0 -31
  67. data/spec/unit/network/build_network_config_spec.rb +0 -49
  68. data/spec/unit/network/generate_mac_spec.rb +0 -26
  69. data/spec/unit/network/socket_spec.rb +0 -44
  70. data/spec/unit/network/socket_vmnet_spec.rb +0 -146
  71. data/spec/unit/network/tap_spec.rb +0 -15
  72. data/spec/unit/network/vmnet_spec.rb +0 -64
  73. data/vagrant-qemu.gemspec +0 -52
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 275a26aad79e641534b4c9ea0bb7a9824f964b61c66dbbe9e3bf0d7075f23081
4
- data.tar.gz: f4afe41ed386785952534fff85e7030030fab8b4defdd7438ce37cee649b1a50
3
+ metadata.gz: 29dd41685f09eaee73da2f6a10070679da9eb5220150efa47e4c016159264e0f
4
+ data.tar.gz: 65a2a89ee494f735f5de4b347517ad744a2c26b9dfdae55832a7572c216183c9
5
5
  SHA512:
6
- metadata.gz: b05b9827dc708cf7dab8a417caae11eeed6fda22380e6d80d8eb3fd8a08c974b70f26b32ee0a10ceb06699a69ed59f1dcbfb42b856bed00ddb5bdeefa04acd37
7
- data.tar.gz: dd55d31e14d55305e1c7714d74eb5d273fed56ba3b4ea927fb37ff305d337dbfd752f913f3b509562c395d8734950079d155e59950138c601041d29b0f10f744
6
+ metadata.gz: cb872c571092692bdcf7d5946ce85eeea75dede69ee619a56c34dd062ace730b36b0aba3c414e0b6c75ff2e2e530dd50ad77c574b5fe0294fae4deab63147d9d
7
+ data.tar.gz: deecca7a10a369f259df3c517db2b9d96a0b39b27a1050997897bb592944bcb678937fa8e4daadf28882543f4813626a95dd304bef94550bfd52ce54d25a131b
data/CHANGELOG.md CHANGED
@@ -223,3 +223,110 @@
223
223
  * README: bring a `socket_opts` `listen=`/`connect=` pair up with
224
224
  `vagrant up --no-parallel`. A plain `vagrant up` boots both machines in
225
225
  parallel, and if the connector starts first the link never comes up.
226
+
227
+ # 0.7.0 (2026-10-15)
228
+
229
+ Thanks to @christhomas, who contributed most of this release
230
+ (#81, #85, #87–#95).
231
+
232
+ New:
233
+
234
+ * VirtioFS synced folders: `config.vm.synced_folder ..., type: "qemu_virtiofs"`.
235
+ The provider starts one `virtiofsd` per share, backs guest RAM with a shared
236
+ memory file and mounts the shares after boot; the daemons are stopped on
237
+ halt, destroy, package and failed boots. New config: `virtiofsd_bin`,
238
+ `virtiofs_guest_uid` / `virtiofs_guest_gid` (default 1000) and
239
+ `extra_virtiofsd_args`; `qe.memory` must be a positive size. Linux hosts and
240
+ WSL2 use the distribution's `virtiofsd`. macOS support is experimental and
241
+ needs a QEMU built with vhost-user and a macOS `virtiofsd` port, available
242
+ from a community Homebrew tap (see the README). (#81)
243
+ * `vagrant serial-config [name|id] [--json]` prints the Unix socket path or TCP
244
+ host and port of each QEMU machine's serial console. (#37, #92)
245
+ * README: how to package a QEMU machine into a reusable box, plus a tested
246
+ round-trip recipe in `examples/package-roundtrip`. (#94)
247
+
248
+ Behavior changes:
249
+
250
+ * If the guest does not become reachable within `config.vm.boot_timeout`
251
+ (default 300 s), or Vagrant reports an invalid boot state, the provider now
252
+ stops the QEMU instance it just started and keeps its disks. Previously QEMU
253
+ was left running. Emulated guests (a guest architecture different from the
254
+ host, or no HVF/KVM) and several VMs booting in parallel can need more than
255
+ 300 s; set `config.vm.boot_timeout = 600` or more. A guest that used to
256
+ finish booting in the background after the timeout now has to be brought up
257
+ again with `vagrant up`, from the start. Interrupting the boot wait with
258
+ Ctrl-C still leaves QEMU running. (#89, refs #21)
259
+ * `config.vm.disk :disk, primary: true, size: ...` now grows the existing boot
260
+ disk instead of attaching a new blank disk; it never shrinks it, and reports
261
+ sizes such as `100 GiB`. The blank disk an older version created for the
262
+ declaration (`vagrant_primary.qcow2`, or `<name>.qcow2` for a custom name)
263
+ is no longer attached; the provider warns while that file exists, and
264
+ the README's "Upgrading older primary disk declarations" section shows how to
265
+ attach it again. (#66, #88, #93, #95)
266
+ * With `net_device = nil`, an inherited SSH forward is disabled and the
267
+ configured `ssh_port` is kept instead of a collision-corrected one. (#71, #87)
268
+
269
+ Fixes:
270
+
271
+ * macOS: firmware for an aarch64 guest is now looked up in the host's Homebrew
272
+ prefix rather than one chosen by the guest architecture, fixing Intel Macs.
273
+ If Vagrant's Ruby runs under Rosetta on Apple Silicon, set `HOMEBREW_PREFIX`
274
+ or `qe.qemu_dir`. (#50, #59, #90)
275
+ * Waiting for a monitor reply during `vagrant halt` is now bounded, so halt can
276
+ always escalate to quit and kill. With `control_port` set, halt connects to
277
+ 127.0.0.1: on macOS 27 connecting to localhost could pick ::1 and appear
278
+ connected, so powerdown and quit were never delivered and QEMU was killed
279
+ only after both timeouts. (#89)
280
+ * `vagrant halt` could fail with `Errno::ENOENT` when QEMU removed its pidfile
281
+ just as the provider read it. An empty pidfile no longer reads as a running
282
+ VM, and a force kill waits for QEMU to exit.
283
+ * Updating the SSH forward no longer leaks into other machines that share a
284
+ forwarded_port rule from the top-level or box Vagrantfile.
285
+ * README: AlmaLinux 9 x86_64 guests need `qe.cpu = "max"` under emulation
286
+ (#67, #91); the `other_default` default is `-parallel none`.
287
+
288
+ Project:
289
+
290
+ * Cross-platform CI on GitHub Actions: unit specs on Linux, macOS and Windows;
291
+ guest smoke tests on Linux, macOS and WSL2; e2e on Linux (KVM), macOS (TCG:
292
+ vmnet, socket_vmnet, VirtioFS) and VirtioFS in WSL2. (#85)
293
+ * The gem now ships only `lib/`, `locales/`, `LICENSE`, `README.md` and
294
+ `CHANGELOG.md`.
295
+
296
+ # 0.8.0 (2026-11-09)
297
+
298
+ Behavior changes:
299
+
300
+ * The QEMU pidfile and the runtime record `options.yml` moved from
301
+ `~/.vagrant.d/tmp/vagrant-qemu/<id>/` into the project, under
302
+ `.vagrant/machines/<name>/qemu/<id>/run/`. A removed tmp directory, or a
303
+ later command run under another `VAGRANT_HOME`, no longer makes a running VM
304
+ look stopped, which had left `vagrant halt` doing nothing and
305
+ `vagrant destroy` leaving QEMU running. The monitor and serial sockets stay
306
+ in the tmp directory, since macOS limits unix socket paths to 104 bytes;
307
+ their paths are recorded at launch, so halt and `vagrant serial-config`
308
+ still reach them. (#96)
309
+ * A VM started by 0.3.7 to 0.7.0 keeps its old layout while it runs: it is
310
+ still found, halted and destroyed as before, and moves to the new layout the
311
+ next time it starts. Only then is it protected as above. Reading the old
312
+ layout will be kept for at least one minor release.
313
+ * Halt your VMs before downgrading the plugin: older releases look only in the
314
+ tmp directory and report a VM started by 0.8.0 as stopped.
315
+
316
+ Fixes:
317
+
318
+ * A pidfile now counts only if the command line of its process contains that
319
+ pidfile, so a pidfile left behind by a crash or SIGKILL whose PID was reused
320
+ no longer reads as a running VM, and halt never signals that process.
321
+ * A VM whose QEMU runs as another user (for example started with
322
+ `sudo vagrant up`) no longer fails with a bare `Errno::EPERM` or
323
+ `Errno::EACCES`: the provider says to run the command as that user.
324
+ * When the QEMU monitor cannot be reached, halt sends SIGTERM right away, so
325
+ QEMU closes its disk images (the guest gets no shutdown request), and
326
+ SIGKILLs it if it is still running 10 seconds later. It no longer waits out
327
+ `graceful_timeout` twice and then SIGKILLs.
328
+ * A pidfile and sockets left after a SIGKILL are removed once QEMU is gone.
329
+ * If QEMU is still running 10 seconds after SIGKILL, halt and destroy now fail
330
+ with an error naming the PID. Before, halt reported success, destroy deleted
331
+ the VM's files and reload left the old QEMU running. A QEMU that has exited
332
+ and only waits to be reaped counts as gone.
data/README.md CHANGED
@@ -35,10 +35,12 @@ Others:
35
35
  * SSH into VM
36
36
  * Provision the instances with any built-in Vagrant provisioner
37
37
  * Synced folder support via SMB
38
- * Basic operation: up, ssh, halt, destroy
38
+ * Synced folder support via VirtioFS for Linux guests
39
+ * Basic operation: up, ssh, halt, destroy, package
39
40
  * Basic suport to forwarded ports, see [vagrant doc](https://www.vagrantup.com/docs/networking/forwarded_ports) for details
40
41
  * Support Cloud-init, see [vagrant doc](https://developer.hashicorp.com/vagrant/docs/cloud-init/usage) for details
41
42
  * Support Disks, see [vagrant doc](https://developer.hashicorp.com/vagrant/docs/disks/usage) for details
43
+ * `config.vm.disk :disk, primary: true, size: "100GB"` grows the existing boot disk. The provider reports growth before resizing and warns when a smaller requested size is skipped; it never shrinks the disk. This resizes the virtual disk only: grow the guest's partition/filesystem separately, or use a box whose cloud-init handles that. The declaration is reapplied on start/reload, unlike `qe.disk_resize`, which applies during import. See the [upgrade note for older primary disk declarations](#upgrading-older-primary-disk-declarations).
42
44
  * Advanced networking (opt-in): dual-NIC with `private_network` support via QEMU native vmnet (macOS), TAP (Linux), or a `socket` netdev — multicast (Linux/Windows) or point-to-point listen/connect (no root, works on macOS)
43
45
 
44
46
  ## Usage
@@ -66,6 +68,53 @@ Notes:
66
68
  see [vagrant doc](https://www.vagrantup.com/docs/synced-folders/smb) for details
67
69
  * need username/password to access shared folder
68
70
 
71
+ ### VirtioFS synced folders
72
+
73
+ Install `virtiofsd` on the host and use a Linux guest with VirtioFS support.
74
+ The plugin searches `PATH`, `/usr/libexec`, and `/usr/lib/qemu` for the daemon;
75
+ set `qe.virtiofsd_bin` when it is installed elsewhere.
76
+
77
+ Apple Silicon macOS support is experimental. The community tap maintained by
78
+ christhomas supplies his macOS virtiofsd port and upstream QEMU built with
79
+ `--enable-vhost-user`. This QEMU formula conflicts with stock Homebrew `qemu`;
80
+ unlink or uninstall the stock formula before installing the tap build:
81
+
82
+ ```sh
83
+ brew unlink qemu # if the stock formula is installed
84
+ brew install antimatter-studios/tap/qemu antimatter-studios/tap/virtiofsd
85
+ ```
86
+
87
+ CI also points the provider at the tap build's firmware directory. If
88
+ `vagrant up` cannot find the aarch64 firmware, do the same:
89
+
90
+ ```sh
91
+ export QEMU_DIR="$(brew --prefix antimatter-studios/tap/qemu)/share/qemu"
92
+ ```
93
+
94
+ CI pins the tap to `f51cb0fcd636247ea977898e7d01a893ee05cf86` and tests this
95
+ combination with TCG; it does not establish macOS HVF support. Linux and Windows WSL2 use the Ubuntu
96
+ `virtiofsd` package. In WSL2 both QEMU and the daemon run inside Linux, so
97
+ shares can live on the native Linux filesystem; Windows DrvFs paths are
98
+ not required. Use `type: "qemu_virtiofs"` explicitly, or restrict
99
+ `config.vm.allowed_synced_folder_types` to `[:qemu_virtiofs]`; its priority is
100
+ below rsync and NFS so installing the daemon does not override those types.
101
+
102
+ ```ruby
103
+ config.vm.synced_folder "./src", "/home/vagrant/src", type: "qemu_virtiofs"
104
+ config.vm.provider "qemu" do |qe|
105
+ qe.virtiofsd_bin = "/path/to/virtiofsd" # optional
106
+ end
107
+ ```
108
+
109
+ `virtiofs_guest_uid` and `virtiofs_guest_gid` default to `1000`. UID/GID
110
+ translation is used when the installed daemon supports it. Additional daemon
111
+ arguments can be passed through `extra_virtiofsd_args` as an array.
112
+ VirtioFS requires a positive `qe.memory`: bare numbers mean MiB, and
113
+ `K`, `M`, `G`, and `T` suffixes are accepted. The size is rounded up to a whole
114
+ MiB for both guest RAM and its shared backend (`"1.3G"` becomes `1332M`). `nil` is rejected for VirtioFS
115
+ because the shared memory backend needs an explicit size. QEMU creates an
116
+ unlinked RAM backing file in the host temporary directory.
117
+
69
118
  ## Box format
70
119
 
71
120
  Same as [vagrant-libvirt version-1](https://github.com/vagrant-libvirt/vagrant-libvirt#version-1):
@@ -74,6 +123,40 @@ Same as [vagrant-libvirt version-1](https://github.com/vagrant-libvirt/vagrant-l
74
123
  * `metadata.json` file describing box image (provider, virtual_size, format)
75
124
  * `Vagrantfile` that does default settings
76
125
 
126
+ ### Package a QEMU machine into a reusable box
127
+
128
+ From a source project's directory, start the QEMU VM and package it:
129
+
130
+ ```sh
131
+ vagrant up --provider=qemu
132
+ vagrant package --output ../portable.box
133
+ ```
134
+
135
+ To package a named machine, pass its name before `--output`, for example
136
+ `vagrant package web --output ../portable.box`.
137
+
138
+ In a fresh consumer project, add the archive and select that box in its
139
+ `Vagrantfile`:
140
+
141
+ ```sh
142
+ vagrant box add portable-qemu ../portable.box
143
+ ```
144
+
145
+ ```ruby
146
+ Vagrant.configure("2") do |config|
147
+ config.vm.box = "portable-qemu"
148
+ end
149
+ ```
150
+
151
+ Then boot it with `vagrant up --provider=qemu`. The archive uses the `libvirt`
152
+ box format, which this plugin can import; let `vagrant box add` read its provider
153
+ metadata. Packaging stops the source VM and includes its box disks and
154
+ architecture. Extra disks, DVDs and cloud-init seed ISOs are excluded. Set up
155
+ networking and synced folders in the consumer project.
156
+
157
+ For a tested source-to-consumer round trip, see the
158
+ [packaging recipe](examples/package-roundtrip/README.md).
159
+
77
160
  ## Configuration
78
161
 
79
162
  ### Options
@@ -87,10 +170,10 @@ This provider exposes a few provider-specific configuration options:
87
170
  * `cpu` - The cpu model of VM, default: `host` for native virtualization, `max` when emulating a non-host arch
88
171
  * `smp` - The smp setting (Simulate an SMP system with n CPUs) of VM, default: `2`
89
172
  * `memory` - The memory setting of VM, default: `4G`
90
- * `disk_resize` - The target disk size of the primary disk, requires resizing of filesystem inside of VM, default: `nil`.
173
+ * `disk_resize` - The target disk size of the primary disk, requires resizing of filesystem inside of VM, default: `nil`. Applied only when the VM is imported. With a `primary: true` disk declaration as well, the disk ends up at the larger of the two sizes, and if `disk_resize` is the larger one every start warns that the smaller declaration cannot shrink the disk; set only one of them.
91
174
  * debug/expert
92
175
  * `ssh_host` - The SSH IP used to access VM, default: `127.0.0.1`
93
- * `ssh_auto_correct` - Auto correct port collisions for ssh port, default: `false`
176
+ * `ssh_auto_correct` - Auto correct port collisions for the provider's SSH forward, default: `false`. When `net_device = nil`, no synthetic SSH forward is added and the direct SSH port is preserved; configure `ssh_host` and `ssh_port` for your custom network.
94
177
  * `net_device` - The network device, default: auto-detected — `virtio-net-device` (arm64 `virt`) or `virtio-net-pci` (x86_64 `q35`)
95
178
  * `drive_interface` - The interface type for the main drive, default `virtio`
96
179
  * `image_path` - The path (or array of paths) to qcow2 image for box-less VM, default is nil value
@@ -103,9 +186,9 @@ This provider exposes a few provider-specific configuration options:
103
186
  * `debug_port` - The port number used to export serial port of the vm for debug, default is nil value. (nil means use unix socket, see "Debug" below for details)
104
187
  * `no_daemonize` - Disable the "daemonize" mode of QEMU, default is false. (see "Windows host" below as example)
105
188
  * `firmware_format` - The format of aarch64 firmware images (`edk2-aarch64-code.fd` and `edk2-arm-vars.fd`) loaded from `qemu_dir`, default: `raw`
106
- * `other_default` - The other default arguments used by this plugin, default: `%W(-parallel null -monitor none -display none -vga none)`
189
+ * `other_default` - The other default arguments used by this plugin, default: `%W(-parallel none -monitor none -display none -vga none)`
107
190
  * `extra_image_opts` - Options passed via `-o` to `qemu-img` when the base qcow2 images are created, default: `[]`
108
- * `graceful_timeout` - Seconds to wait at each `vagrant halt` stage before escalating, default: `60`. Halt sends ACPI `system_powerdown`, waits up to this long, then sends QEMU's `quit` monitor command (a clean shutdown that flushes and closes the disk images), waits again, and finally SIGKILLs QEMU as a last resort — so halt still completes even when the guest was already halted from inside (e.g. `sudo systemctl halt`), where `system_powerdown` is a no-op
191
+ * `graceful_timeout` - Seconds to wait at each `vagrant halt` stage before escalating, default: `60`. Halt sends ACPI `system_powerdown`, waits up to this long, then sends QEMU's `quit` monitor command (a clean shutdown that flushes and closes the disk images), waits again, and finally SIGKILLs QEMU as a last resort — so halt still completes even when the guest was already halted from inside (e.g. `sudo systemctl halt`), where `system_powerdown` is a no-op. If the monitor cannot be reached (for example its socket was deleted), halt stops waiting on it: it sends SIGTERM, which makes QEMU close its disk images like `quit` but gives the guest no shutdown request, and SIGKILLs QEMU if it is still running 10 seconds later. If QEMU is still running 10 seconds after SIGKILL, halt and destroy fail with an error instead of reporting success or deleting the VM's files
109
192
  * advanced networking (requires `advanced_network = true`)
110
193
  * `advanced_network` - Enable dual-NIC advanced networking with `private_network` support, default: `false`
111
194
  * `net_mode` - Network backend: `:auto` (detect by platform), `:vmnet_shared`, `:vmnet_host`, `:vmnet_bridged` (macOS, sudo), `:socket_vmnet` (macOS, no sudo — via the socket_vmnet daemon), `:tap` (Linux), `:socket` (QEMU `socket` netdev — multicast or point-to-point, see `socket_opts`), default: `:auto`
@@ -143,7 +226,7 @@ to `nil` value to skip related qemu arguments.
143
226
  * `net_device`: skip all network related arguments:
144
227
  * `-device xxx,netdev=net0`
145
228
  * `-netdev user,id=net0,xxx`
146
- * NOTES: there will be no network, ssh won't work
229
+ * NOTES: the provider adds no network; SSH works only if you set up your own network and point `ssh_host` and `ssh_port` at it
147
230
  * `drive_interface`: skip drive for the main image, `-drive if=xxx,xxx`
148
231
  * `firmware_format`: skip firmware setup for aarch64, `-drive if=pflash,xxx`
149
232
 
@@ -436,6 +519,20 @@ Platform support:
436
519
 
437
520
  Serial port is exported to unix socket: `<user_home>/.vagrant.d/tmp/vagrant-qemu/<id>/qemu_socket_serial`, or `debug_port`.
438
521
 
522
+ Run `vagrant serial-config` to list serial endpoints for all machines, or
523
+ `vagrant serial-config NAME` for one machine. The command does not start machines;
524
+ uncreated machines are listed as `NotCreated`. Created machines show `UnixSocket`
525
+ or `Host`/`Port`. Stopped machines report their last-start endpoint when it was recorded, even if
526
+ the configuration has since changed; that endpoint may not be listening.
527
+ Machines using another provider are skipped.
528
+
529
+ Use `vagrant serial-config --json` for an array of machine names and endpoint
530
+ objects. Each record has `machine` (the Vagrant machine name) and `endpoint`.
531
+ A Unix endpoint has `type: "unix"` and `path`; a TCP endpoint has `type: "tcp"`,
532
+ `host` and `port`. An uncreated machine has a `null` endpoint. For newly started instances,
533
+ the command uses the endpoint recorded at launch, even if `debug_port` changes
534
+ in the Vagrantfile; older instances fall back to their current configuration.
535
+
439
536
  To debug and login to the GuestOS from serial port:
440
537
 
441
538
  * unix socket
@@ -510,31 +607,68 @@ vagrant plugin list | grep vagrant-qemu
510
607
 
511
608
  > This command will list all installed plugins, and you should see the vagrant-qemu plugin with the locally built version.
512
609
 
513
- ### Running Tests
610
+ ### Testing
611
+
612
+ Tests are RSpec, in three layers. GitHub Actions runs all of them on every pull request (`.github/workflows/ci.yml`); a new push to a pull request cancels its older run, and changes that only touch `*.md` files are not tested.
613
+
614
+ | Layer | What | Where CI runs it |
615
+ |---|---|---|
616
+ | Unit (`spec/unit`) | Plugin code, no QEMU | Ubuntu, macOS, Windows |
617
+ | Acceptance (`spec/acceptance`) | Vagrant actions against a mock `qemu` (bash scripts) | Ubuntu, macOS |
618
+ | Guest smoke (`examples/ci-smoke`) | Boots a Debian guest: up, ssh, status, halt, destroy | Ubuntu, macOS, Windows WSL2 |
619
+ | e2e (`spec/e2e`) | Real VMs: disks, forwarded ports, provision, reload, package, networking | Ubuntu with KVM; vmnet and socket_vmnet specs on macOS |
620
+ | VirtioFS e2e (`spec/e2e/virtiofs_spec.rb`) | Two resolved shares, host/guest writes, reload, halt/up, daemon and socket cleanup on destroy | Ubuntu, Apple Silicon macOS, Windows WSL2 |
621
+
622
+ CI uses the latest Vagrant release and prints the Ruby and Vagrant versions in each job. Hosted macOS runners have no HVF, so CI runs macOS guests with TCG; the HVF path is only tested locally.
623
+
624
+ Run them locally:
514
625
 
515
626
  ```sh
516
- # Unit tests (fast, no QEMU needed)
627
+ # Unit and acceptance (fast, no real VM)
517
628
  bundle exec rake spec:unit
518
-
519
- # Acceptance tests (mock QEMU, no real VM)
520
629
  bundle exec rake spec:acceptance
521
630
 
522
- # End-to-end tests (requires QEMU and a box image). e2e exercises the
523
- # INSTALLED plugin — rebuild and reinstall first (the suite fails fast on
524
- # a version mismatch):
631
+ # e2e exercises the INSTALLED plugin: rebuild and reinstall first (the
632
+ # suite fails fast on a version mismatch)
525
633
  bundle exec rake build
526
634
  vagrant plugin install ./pkg/vagrant-qemu-<version>.gem
527
635
  TEST_QEMU=1 bundle exec rake spec:e2e
528
636
 
529
- # End-to-end with vmnet (requires sudo + macOS; needs an aarch64 cloud-init box)
530
- TEST_QEMU=1 TEST_VMNET=1 TEST_BOX_CLOUDINIT=perk/ubuntu-2204-arm64 sudo -E bundle exec rake spec:e2e
637
+ # vmnet (macOS, needs sudo) and socket_vmnet (macOS, needs the daemon:
638
+ # brew install socket_vmnet && sudo brew services start socket_vmnet)
639
+ TEST_VMNET=1 sudo -E bundle exec rspec spec/e2e/advanced_network_spec.rb
640
+ TEST_SOCKET_VMNET=1 bundle exec rspec spec/e2e/socket_vmnet_spec.rb
531
641
 
532
- # All tests
533
- bundle exec rake spec
642
+ # VirtioFS: install the host daemon (and compatible QEMU on macOS) first.
643
+ # Uses Linux provider defaults (KVM), macOS TCG, and a box matching the host.
644
+ TEST_VIRTIOFS=1 TEST_BOX=cloud-image/debian-12 bundle exec rspec spec/e2e/virtiofs_spec.rb
534
645
  ```
535
646
 
647
+ The e2e boxes default to Apple Silicon (aarch64) boxes; on another host set `TEST_BOX` and `TEST_BOX_CLOUDINIT` to boxes of the host architecture (CI uses `cloud-image/ubuntu-22.04` for both).
648
+
649
+ Adding tests:
650
+
651
+ * A new e2e example in `spec/e2e` tagged `:requires_qemu` runs in CI's Ubuntu e2e job without workflow changes.
652
+ * A feature that needs extra host setup (a daemon, a tool such as `virtiofsd`, root) gets its own tag, an environment switch in `spec/spec_helper.rb` next to `TEST_VMNET`, and a workflow step that installs the dependency and sets the switch.
653
+
536
654
  ## Known issue / Troubleshooting
537
655
 
656
+ If boot times out or Vagrant reports an invalid boot state, the provider stops
657
+ the QEMU instance it just launched and keeps its disks. It requests QEMU quit
658
+ directly and allows 3–10 seconds to flush disk writes, based on `qe.graceful_timeout`,
659
+ before falling back to forced termination. Increase `config.vm.boot_timeout` if
660
+ the guest needs more time to become reachable. When QEMU runs on TCG (a guest
661
+ architecture different from the host, or no HVF/KVM available) or several VMs
662
+ boot in parallel, set it to 600 seconds or more, as this project's CI does for
663
+ its TCG runs:
664
+
665
+ ```ruby
666
+ config.vm.boot_timeout = 600
667
+ ```
668
+
669
+ Interrupting the boot wait with Ctrl-C retains the existing behavior and can
670
+ leave QEMU running.
671
+
538
672
  ### 1. failed to create shared folder
539
673
 
540
674
  ```
@@ -619,9 +753,60 @@ official build from [vagrantup.com](https://www.vagrantup.com/downloads) if
619
753
  needed). Do **not** work around it by pinning `logger` in a Gemfile — that tends
620
754
  to deepen the conflict.
621
755
 
756
+ ### 6. AlmaLinux 9 x86_64 guest times out waiting for SSH on Apple Silicon
757
+
758
+ Check the guest CPU model if the Vagrantfile explicitly sets `qe.cpu = "qemu64"`.
759
+ With the `almalinux/9` box version `9.2.20230513`, that model caused a boot timeout
760
+ in an Apple Silicon test; changing it to `max` allowed the guest to boot and SSH
761
+ successfully. For cross-architecture emulation, use:
762
+
763
+ ```ruby
764
+ config.vm.provider "qemu" do |qe|
765
+ qe.arch = "x86_64"
766
+ qe.cpu = "max"
767
+ end
768
+ ```
769
+
770
+ The provider already selects `max` by default when the guest architecture differs
771
+ from the host, so removing an explicit `qemu64` override also restores that default.
772
+ CPU models determine which instruction-set features the guest can use; QEMU's
773
+ [CPU ABI table](https://www.qemu.org/docs/master/system/qemu-cpu-models.html#abi-compatibility-levels-for-cpu-models)
774
+ places `qemu64` below the x86-64-v2 baseline required by
775
+ [RHEL 9](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/considerations_in_adopting_rhel_9/ref_architectures_considerations-in-adopting-rhel-9).
776
+
777
+ This workaround is for an x86_64 guest running under TCG emulation. A shell
778
+ provisioner that installs OpenSSH cannot fix a failure before the first SSH
779
+ connection, because provisioning starts after the communicator becomes ready.
780
+
781
+ ### Upgrading older primary disk declarations
782
+
783
+ Before the fix in [PR #88](https://github.com/ppggff/vagrant-qemu/pull/88),
784
+ `primary: true` created a separate blank disk, usually `vagrant_primary.qcow2`,
785
+ instead of growing the boot disk. After upgrading, that extra disk is no longer
786
+ automatically attached. Its file is retained with the VM.
787
+
788
+ If you stored data on that extra disk, halt the VM and back up the existing file
789
+ under `.vagrant/machines/<name>/qemu/<id>/vagrant_primary.qcow2`. Replace the old
790
+ primary declaration with a non-primary declaration using the same name:
791
+
792
+ ```ruby
793
+ config.vm.disk :disk, name: "vagrant_primary", primary: false, size: "100GB"
794
+ ```
795
+
796
+ Use the size from your original declaration. The provider reuses the existing
797
+ file without recreating it; if it is missing, a new blank disk would be created.
798
+ If you gave the old disk a custom name, use that name instead. Run `vagrant up
799
+ --provider=qemu` and mount its existing filesystem in the guest as before.
800
+
801
+ If you also want to grow the actual boot disk, add a separate declaration with
802
+ a distinct name, for example:
803
+
804
+ ```ruby
805
+ config.vm.disk :disk, name: "boot", primary: true, size: "100GB"
806
+ ```
807
+
622
808
  ## TODO
623
809
 
624
810
  * Support NFS shared folder
625
- * Support package VM to box
626
811
  * More configures
627
812
  * GUI mode
@@ -0,0 +1,18 @@
1
+ require_relative "../synced_folder_virtiofs"
2
+
3
+ module VagrantPlugins
4
+ module QEMU
5
+ module Action
6
+ class CleanupVirtioFS
7
+ def initialize(app, env)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ SyncedFolderVirtioFS.new.cleanup(env[:machine], {})
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,35 @@
1
+ require "log4r"
2
+ require "json"
3
+ require "shellwords"
4
+
5
+ module VagrantPlugins
6
+ module QEMU
7
+ module Action
8
+ class MountVirtioFS
9
+ def initialize(app, env)
10
+ @app = app
11
+ @logger = Log4r::Logger.new("vagrant_qemu::action::mount_virtiofs")
12
+ end
13
+
14
+ def call(env)
15
+ machine = env[:machine]
16
+
17
+ # prepare sees Vagrant's resolved folder types. Use precisely that
18
+ # mapping, including shares selected through allowed_synced_folder_types.
19
+ mounts_file = machine.data_dir.join("virtiofs", "mounts.json")
20
+ if !machine.provider_config.virtiofs_qemu_args.empty? && mounts_file.file?
21
+ JSON.parse(File.read(mounts_file)).each do |tag, guestpath|
22
+
23
+ escaped_guestpath = Shellwords.escape(guestpath)
24
+ machine.communicate.sudo("mkdir -p #{escaped_guestpath}")
25
+ machine.communicate.sudo("mount -t virtiofs #{Shellwords.escape(tag)} #{escaped_guestpath}")
26
+ machine.ui.info("Mounted virtiofs #{tag} at #{guestpath}")
27
+ end
28
+ end
29
+
30
+ @app.call(env)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -26,20 +26,26 @@ module VagrantPlugins
26
26
  end
27
27
  end
28
28
 
29
- has_ssh_forward = false
30
- machine.config.vm.networks.each do |type, options|
31
- next if type != :forwarded_port
29
+ # Update the SSH rule through network(), never in place: a rule from the
30
+ # top-level or box Vagrantfile is one hash shared by every machine.
31
+ has_ssh_forward = machine.config.vm.networks.any? do |type, options|
32
+ type == :forwarded_port && options[:id] == "ssh"
33
+ end
32
34
 
33
- # update ssh.host to ssh_port
34
- if options[:id] == "ssh"
35
- options[:host] = machine.provider_config.ssh_port
36
- options[:auto_correct] = machine.provider_config.ssh_auto_correct
37
- has_ssh_forward = true
38
- break
39
- end
35
+ # There is no provider-managed SSH forward when the user disables our
36
+ # NIC (e.g. a custom bridged NIC with direct SSH). Vagrant/box configs
37
+ # may still contain an inherited SSH rule: exclude it from collisions.
38
+ if machine.provider_config.net_device.nil?
39
+ machine.config.vm.network(:forwarded_port, id: "ssh", disabled: true) if has_ssh_forward
40
+ @app.call(env)
41
+ return
40
42
  end
41
43
 
42
- if !has_ssh_forward
44
+ if has_ssh_forward
45
+ machine.config.vm.network(:forwarded_port, id: "ssh",
46
+ host: machine.provider_config.ssh_port,
47
+ auto_correct: machine.provider_config.ssh_auto_correct)
48
+ else
43
49
  machine.config.vm.network :forwarded_port,
44
50
  :guest => 22,
45
51
  :host => machine.provider_config.ssh_port,
@@ -0,0 +1,40 @@
1
+ require_relative "../synced_folder_virtiofs"
2
+
3
+ module VagrantPlugins
4
+ module QEMU
5
+ module Action
6
+ class ResetVirtioFSArgs
7
+ def initialize(app, env)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ machine = env[:machine]
13
+ machine.provider_config.virtiofs_qemu_args = []
14
+ SyncedFolderVirtioFS.new.cleanup(machine, {})
15
+ @starting = true
16
+ result = @app.call(env)
17
+ @starting = false
18
+ result
19
+ end
20
+
21
+ def recover(env)
22
+ machine = env[:machine]
23
+ return unless @starting && !machine.provider_config.virtiofs_qemu_args.empty?
24
+ # Warden unwinds StartInstance first. Once its recovery stops QEMU,
25
+ # the daemons can stop too. A running guest keeps its shares.
26
+ return if machine.provider.driver.running?
27
+ unless env[:virtiofs_logs_shown]
28
+ Dir.glob(machine.data_dir.join("virtiofs/*.log")).each do |path|
29
+ machine.ui.error("virtiofsd log (#{path}):\n#{File.read(path)}")
30
+ end
31
+ end
32
+ SyncedFolderVirtioFS.new.cleanup(machine, preserve_logs: true)
33
+ machine.provider_config.virtiofs_qemu_args = []
34
+ rescue StandardError => error
35
+ machine.ui.error("VirtioFS recovery failed: #{error.message}")
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end