From 05b1fa16c269526316ea99f8e95e527f5eda005c Mon Sep 17 00:00:00 2001 From: gusthoff Date: Fri, 18 Sep 2026 14:16:51 +0200 Subject: [PATCH 1/4] Vagrant: drop the box's stale eth0 netplan stanza The box ships `/etc/netplan/01-netcfg.yaml` declaring an `eth0` that does not exist on this hardware; the NIC is `enp0s3`, configured by `00-installer-config.yaml`. netplan feeds every declared interface into the generated `systemd-networkd-wait-online` drop-in, and that unit is invoked without `--any`, so it waits for `eth0` until its 120 s timeout expires and then fails. `network-online.target`, and therefore `ssh.service`, stays blocked for that whole time. Remove the file during provisioning, with `run: "always"` so existing VMs are corrected on their next `vagrant up` rather than only VMs created after a destroy. Measured on `bento/ubuntu-26.04` 202606.01.0: boot 3:11 before, 1:45 after, with `systemd-networkd-wait-online` down from a 120 s timeout to 178 ms. Co-Authored-By: Claude Opus 5 (1M context) --- Vagrantfile | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Vagrantfile b/Vagrantfile index bd9c0696c..339753d74 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -348,6 +348,24 @@ Vagrant.configure("2") do |config| config.vm.synced_folder '.', '/vagrant', disabled: true + # The box ships a leftover /etc/netplan/01-netcfg.yaml declaring an `eth0` + # that does not exist here (the NIC is enp0s3, configured by + # 00-installer-config.yaml). netplan feeds every declared interface into the + # generated systemd-networkd-wait-online drop-in, and that unit is invoked + # without --any, so it waits for eth0 until its 120 s timeout expires and + # then fails. network-online.target -- and therefore ssh.service -- is + # blocked for that whole time, which is what Vagrant's "Connection reset. + # Retrying..." loop waits out. + # + # run: "always" because existing VMs report "Machine already provisioned" + # and would otherwise never run this; the guard makes it a no-op once done. + config.vm.provision "netplan-cleanup", type: :shell, run: "always", inline: <<-SHELL + if [ -f /etc/netplan/01-netcfg.yaml ]; then + rm -f /etc/netplan/01-netcfg.yaml + netplan generate + fi + SHELL + config.vm.define "web" do |web| web.vm.box = "bento/ubuntu-26.04" web.vm.box_version = "202606.01.0" From e437298d581e418589c87641e125880560f0d8d1 Mon Sep 17 00:00:00 2001 From: gusthoff Date: Fri, 18 Sep 2026 16:56:15 +0200 Subject: [PATCH 2/4] Vagrant: share one base disk image between machines Several machines run at once, one `web`/`epub` pair per worktree, and all of them are created from the same box. `vb.linked_clone` makes each machine share a single base disk image instead of taking a full private copy, so the host page cache can serve the blocks they read in common instead of reading each copy separately. Creation-time only: existing machines keep their full copies until they are destroyed and recreated, so this has no effect until then and is not yet exercised. Co-Authored-By: Claude Opus 5 (1M context) --- Vagrantfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Vagrantfile b/Vagrantfile index 339753d74..246e53bc7 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -344,6 +344,13 @@ Vagrant.configure("2") do |config| config.vm.provider "virtualbox" do |vb| vb.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/v-root", "1"] + + # Several machines run at once (one pair per worktree), all created from + # the same box. A linked clone shares one base disk image instead of + # giving each machine its own full copy, so the host page cache serves + # the blocks they read in common rather than reading each copy + # separately. + vb.linked_clone = true end config.vm.synced_folder '.', '/vagrant', disabled: true From dd48ff514b0d66862e81fd83ae0205b69f828639 Mon Sep 17 00:00:00 2001 From: gusthoff Date: Fri, 18 Sep 2026 16:56:39 +0200 Subject: [PATCH 3/4] Vagrant: make host I/O caching an opt-in knob VirtualBox creates the storage controller with host I/O caching off, so every guest read goes to the physical disk. With several machines booting near-identical images that is the dominant cost of `vagrant up`: the guest waits tens of seconds for its own virtual disk to appear, and everything else, including `ssh.service`, queues behind it. `LEARN_VM_HOST_IO_CACHE` turns caching on, and `LEARN_VM_STORAGE_CONTROLLER` names the controller to act on, since that name is box-specific. Both are read on every `vagrant up`, so they apply to existing machines. The default is off, matching VirtualBox: with caching on, guest writes may sit in host RAM, so a host crash can corrupt a guest file system. An unrecognized cache value is refused rather than ignored, so a typo cannot silently leave the controller in a state the caller did not ask for. Validation: `useHostIOCache` confirmed to flip in the VM's own configuration; boot time falls by roughly 9x with caching on, and the source-code example build is unchanged either way, as it reads from the synced folders and is otherwise CPU-bound. Co-Authored-By: Claude Opus 5 (1M context) --- Vagrantfile | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/Vagrantfile b/Vagrantfile index 246e53bc7..9d0738540 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -351,6 +351,56 @@ Vagrant.configure("2") do |config| # the blocks they read in common rather than reading each copy # separately. vb.linked_clone = true + + # VirtualBox creates this controller with host I/O caching off + # (useHostIOCache="false"), so every guest read goes to the physical + # disk. Turning caching on lets the host page cache absorb those reads, + # which is worth a lot at boot. + # + # It is nevertheless left **off** by default, because of durability: + # with caching on, guest writes may sit in host RAM, so a host crash or + # power loss can corrupt a guest file system. Opt in per invocation + # when a fast boot is worth that exposure. + # + # Two related knobs: LEARN_VM_STORAGE_CONTROLLER picks *which* + # controller to act on, LEARN_VM_HOST_IO_CACHE picks *what to set* on + # it. The controller name therefore applies to both directions -- it is + # needed to turn caching on just as much as to turn it off -- and + # clearing it skips the customization altogether, which makes the cache + # setting irrelevant. + # + # vagrant up + # the default: caching off, controller "VirtIO Controller" + # LEARN_VM_HOST_IO_CACHE=on vagrant up + # caching on -- much faster boot, at the durability cost above + # LEARN_VM_STORAGE_CONTROLLER="SATA Controller" vagrant up + # a box whose controller is named differently; find the name with + # VBoxManage showvminfo --machinereadable \ + # | grep -i '^storagecontroller' + # LEARN_VM_STORAGE_CONTROLLER="SATA Controller" \ + # LEARN_VM_HOST_IO_CACHE=on vagrant up + # both together: caching on for a differently-named controller + # LEARN_VM_STORAGE_CONTROLLER= vagrant up + # skip the customization entirely, leaving the controller at + # whatever it is already set to. LEARN_VM_HOST_IO_CACHE is + # ignored in this case. An escape hatch for when a wrong name + # makes `VBoxManage storagectl` fail the boot. + host_io_cache = ENV.fetch("LEARN_VM_HOST_IO_CACHE", "off") + unless ["on", "off"].include?(host_io_cache) + # Refuse rather than silently skip: an unrecognized value would + # otherwise leave the controller at whatever it already is, so a + # typo such as "true" or "0" would quietly do nothing while looking + # like it complied -- misleading in either direction. + raise "LEARN_VM_HOST_IO_CACHE must be \"on\" or \"off\", " \ + "got #{host_io_cache.inspect}" + end + storage_controller = + ENV.fetch("LEARN_VM_STORAGE_CONTROLLER", "VirtIO Controller") + unless storage_controller.empty? + vb.customize ["storagectl", :id, + "--name", storage_controller, + "--hostiocache", host_io_cache] + end end config.vm.synced_folder '.', '/vagrant', disabled: true From 7472f1643844ea70e063fd5788ffc8130e792106 Mon Sep 17 00:00:00 2001 From: gusthoff Date: Fri, 18 Sep 2026 16:57:02 +0200 Subject: [PATCH 4/4] Vagrant: mask snapd to shorten the boot chain `snapd.apparmor.service` and `snapd.socket` sit on the critical chain to `basic.target`, and so delay `ssh.service` -- the unit Vagrant waits for while it prints "Connection reset. Retrying...". Nothing in these build VMs uses snap. The units are masked rather than removed: masking is idempotent, so the provisioner is safe to re-run, and `systemctl unmask` restores them. `run: "always"` so existing machines are covered too, since they report "Machine already provisioned" and would otherwise never run it. Validation: both machines report `masked` for all four units, a second run is a no-op, and `systemctl is-system-running` still reports `running` with no failed units. Co-Authored-By: Claude Opus 5 (1M context) --- Vagrantfile | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Vagrantfile b/Vagrantfile index 9d0738540..8cde07311 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -423,6 +423,17 @@ Vagrant.configure("2") do |config| fi SHELL + # snapd.apparmor.service and snapd.socket sit on the critical chain to + # basic.target, and therefore to ssh.service -- the unit Vagrant waits for + # when it prints "Connection reset. Retrying...". Nothing in these build + # VMs uses snap, so the units are masked rather than removed: masking is + # idempotent and `systemctl unmask` puts it back. + config.vm.provision "snapd-mask", type: :shell, run: "always", inline: <<-SHELL + systemctl mask --quiet \ + snapd.service snapd.socket snapd.seeded.service snapd.apparmor.service \ + 2>/dev/null || true + SHELL + config.vm.define "web" do |web| web.vm.box = "bento/ubuntu-26.04" web.vm.box_version = "202606.01.0"