Skip to content

In-place root growth: recovery and native acceptance

The grow-only resize -D contract, offline and guest expansion, failure recovery, and ext4/XFS native acceptance.

Status (2026-11-11): The unreleased development build implements barn resize -D, with ext4 and XFS acceptance on macOS/arm64 using QEMU/HVF guests. Released 1.1.0 still limits resize to CPU/memory. See VM specification design for input precedence and root growth for the command contract.

Supported scope

barn resize -D 128GiB meta
BARN_DISK=128GiB barn resize meta

The target is an absolute root capacity. Growth is supported; shrinking is rejected. Repeating a completed target neither changes the image nor restarts the VM. CPU/memory changes may accompany growth; scale does not multiply disk size. Changes to images, architecture, data disks, networking, and shares still require explicit recreation.

MBR targets cannot exceed 2 TiB. Supported roots are plain ext4/XFS partitions with 512-byte logical sectors, in GPT or primary MBR layouts. The root must be physically last without overlapping other partitions. LVM, encrypted roots, later partitions, and missing guest tools fail before image modification. Disk serial, UUID, PARTUUID, partition number, and starting offset bind the operation to the original device. The workflow does not format disks or recreate partitions.

Running nodes stop for image growth and return to running. Stopped nodes temporarily boot to inspect the layout, stop for image growth, boot to expand the filesystem, then return to stopped. The deployment lock spans this workflow, preventing another lifecycle command from starting QEMU during image modification.

Recovery protocol

The node’s root_resize record stores original capacity, target capacity, original power state, and guest root identity. Each execution rereads current state rather than adding capacity to an already enlarged value.

Durable stage Next action Recovery after interruption
preflight Start or inspect the guest; probe layout and tools Preserve original power intent and repeat the probe
image Prove QEMU exited; run qemu-img resize; verify and sync the image Inspect actual virtual size; an image at target is not grown again
guest Reconcile applied state; boot and run growpart plus resize2fs/xfs_growfs Keep the enlarged image and partition; continue filesystem growth and verification
verified Restore original power state, then clear the record Retry power restoration and the final state write

A failed preflight clears its record once original power state is restored, allowing a corrected target before any disk mutation. image is durable before image growth, so a CLI killed after qemu-img succeeds still retains its target. A metadata write failure restores metadata only; it never shrinks the real disk to simulate rollback. Applied state may already record the enlarged capacity while guest completion is pending. status and plan keep that pending condition visible; retrying resize with the same target clears it after verification. Fix guest tooling or connectivity and retry the suggested command; no separate repair command is needed.

Completion verifies image virtual size, guest block device size, partition extent, and filesystem geometry. ext4 uses block count times block size; XFS uses data block count times block size. Free space from df is not a capacity measurement. Checks allow partition-table tail space and filesystem alignment. The CPU/memory hot-plug marker resize_incomplete stays separate from root recovery.

Native acceptance

Acceptance used an isolated temporary BARN_HOME, two 1c1g guests, and 12 GiB initial roots. Images were Debian 13 20261103.2651.1 and Rocky Linux 9 9.8.20260525.2, both arm64. All measurements below are bytes.

Guest Root disk: before → after Root partition: before → after Filesystem: before → after
Debian 13 / ext4 12,884,901,888 → 15,032,385,536 12,750,667,264 → 14,898,150,912 12,750,663,680 → 14,898,147,328
Rocky Linux 9 / XFS 12,884,901,888 → 15,032,385,536 11,730,402,816 → 13,877,886,464 11,730,399,232 → 13,877,882,880
  • A running ext4 guest grew to 14 GiB and remained running. Repeating the target preserved its QEMU PID; a 13 GiB shrink failed before mutation.
  • A stopped XFS guest grew to 14 GiB and returned to stopped. Repeating the target preserved the root file’s modification time.
  • Injecting a resize2fs failure left the ext4 disk and partition enlarged for an 18 GiB target while the filesystem stayed at 17,045,630,976 bytes. The journal retained guest; restoring the tool and repeating 18 GiB completed expansion.
  • During growth to 20 GiB, a host tool killed the Barn CLI after actual image expansion. The image was already 21,474,836,480 bytes, the journal remained image, and the VM was stopped. Retrying restored running state and verified a filesystem capacity of 21,340,598,272 bytes.
  • VM UUID, generation, disk serial, filesystem UUID, PARTUUID, SSH host public key, and the SHA-256 of a prewritten 1 MiB random data file were retained throughout.

Debian 13 also uses systemd-growfs at boot. The failure test disabled automatic growth with /etc/growroot-disabled and masked systemd-growfs-root.service in the disposable guest, proving Barn’s script can complete growth independently. A separate case verified recovery when boot services had already grown the filesystem but a guest command failed. Test nodes were destroyed; the host’s six existing QEMU processes and arguments were preserved, and host networking was unchanged. Linux hosts, amd64 guests, and MBR layouts were not replayed on real VMs in this round.

Code and checks

internal/private/root_resize.go holds the deployment lock and reuses existing start, stop, and process identity checks. The internal/private/root_resize_guest.py validates layout, identity, and capacity. The internal/state/root_resize.go is written atomically with node state. The disk layer grows qcow2 only while offline.

internal/private/root_resize_test.go cover lost responses before and after side effects, retries, shrink rejection, node selection, capacity committed before completion, and identity/capacity failures. CLI regressions cover -D/BARN_DISK, retry inputs, recovery hints for stopped nodes, and SPEC/ROOT output. Delivery checks use source make check and site make check.

Offline image mutation and subsequent guest partition/filesystem expansion follow QEMU’s disk tool documentation. The growpart no-change exit code follows the cloud-utils manual. Online QMP growth and additional root layouts require separate design and acceptance.

Implementation evidence is local commit 1e24768, not yet present on the reviewed remote main. New source files below are identified by repository-relative paths; see the command reference version baseline.

Table reader