This is the multi-page printable view of this section. .
Barn Blog
-
1: Release Notes
- 1.1: Barn 1.2.0: resources, root growth, and host names
- 1.2: Barn 1.1.0: image selection and live progress
- 1.3: Barn 1.0.0: the first stable release
- 1.4: Barn 0.9.3: safer resizing, selected nodes, and recovery
- 1.5: Barn 0.9.2: signed Barn.app and explicit SSH/exec arguments
- 1.6: Barn 0.9.1: Go 1.27.3 toolchain and Barn.app rename
- 1.7: Barn 0.9.0: native macOS guests and smoother Linux recovery
- 1.8: Barn 0.8.0: clearer recovery and refreshed images
- 1.9: Barn 0.7.0: simpler test labs and automatic recovery
- 1.10: Barn 0.6.0: Ubuntu 24.04 and smoother local labs
- 1.11: Barn 0.5.0: explicit disposal, explicit repositories, reproducible images
- 1.12: Barn 0.4.0: one command, working data disks, one voice
- 1.13: Barn 0.3.0: explicit catalogs and actionable readiness
- 1.14: Barn 0.2.0: selected convergence and release integrity
- 1.15: RC6 development candidate: owner-scoped Tier-1 delivery
- 1.16: Development snapshot: the Go 1.27 product surface
-
2: Design Notes
- 2.1: Main and command reference review
- 2.2: Why Barn Has No Projects
- 2.3: Why Every Barn Node Has Two NICs
- 2.4: Declarative Does Not Mean Destructive
- 2.5: A PID Is Not a Virtual Machine
- 2.6: repo.yaml Is Intent; catalog.json Is Evidence
- 2.7: From Upstream Images to a Signed Catalog
- 2.8: What Barn Can Learn from Vagrant
- 2.9: Replacing Vagrant with Barn in Pigsty: resources, names, and migration work
- 2.10: Pigsty host setup: fixed names and CA trust
- 2.11: VM specification parameters: commands, short flags, environment, and root disks
- 2.12: Host DNS and domain resolution for Pigsty on Barn
- 2.13: In-place root growth: recovery and native acceptance
- 2.14: Barn 1.2.0 release readiness and acceptance
- 2.15: Running Barn from Windows
- 2.16: Beyond amd64 and arm64
- 2.17: Evolving the CLI Without Breaking Its Contracts
- 2.18: The Lifetime of a Native Mac VM
Engineering updates, architectural essays, and release records for Barn. For step-by-step usage guides and command references, see the Documentation.
- Release Notes — Version highlights, upgrade instructions, and dated validation records. Current release: Barn 1.1.0.
- Design Notes — Architectural decisions behind Barn’s single-deployment model, Ansible inventory compatibility, in-place convergence, and host networking.
1 - Release Notes
Barn 1.1.0 (2026-11-09) is the current stable release. Earlier 0.x builds were GitHub pre-releases. See Install Barn for installation commands and asset verification.
This archive covers every release and validation milestone from the initial 2026-08-24 development snapshot through v1.1.0.
1.1 - Barn 1.2.0: resources, root growth, and host names
Barn 1.2.0 was released on 2026-11-12 from commit 90f6158. It adds resource overrides, recoverable root-disk growth, configurable lifecycle concurrency, and inventory-based host names. The bilingual command reference covers all 57 operation commands and their options.
Upgrade from 1.1.0
Omitted Linux CPU/memory values now resolve to 1 vCPU / 2 GiB, down from 2 vCPUs / 4 GiB. This also affects existing inventories that omit these fields. To preserve the earlier defaults, set vm_cpu: 2 and vm_mem: 4GiB under all.vars, retain any per-host overrides, and inspect barn plan before applying changes. Existing applied VMs keep their resources until a resource-changing operation is requested. Root and data-disk defaults remain 64 GiB and 128 GiB.
VM resources
Use --cpu/-C, --mem/-M, --scale/-S, and --disk/-D alongside image and architecture selection. Corresponding BARN_* environment variables provide defaults; command-line values take precedence. Generated inventories save the base settings. Plans, validation, status, and lifecycle output display effective resources as SPEC labels such as 1c2g; JSON retains numeric resources, bases, scale, and input sources.
resize --disk/-D grows an existing root disk. Barn stops QEMU before enlarging the disk, verifies its last plain ext4/XFS partition inside the guest, and records recovery stages so an interrupted operation can resume. It preserves VM identity, keys, and data and restores the original running/stopped intent. Shrinking and unsupported layouts are rejected. See root growth and recovery.
Lifecycle concurrency
--parallel/-p controls node concurrency for up, start, stop, restart, reload, recreate, destroy, and purge. The default is the logical CPU count minus two, with a minimum of one. Zero runs all selected nodes concurrently; a positive value sets the limit. Retry commands preserve explicit overrides.
Inventory host names
Declare service names in all.vars.vm_hosts, preview with barn hosts install -f pigsty.yml --json, and apply with barn hosts install -f pigsty.yml --yes. The inventory path works before VM creation; omitting -f continues to use applied node names. Identical mappings merge case-insensitively; conflicting addresses fail. These host-side mappings leave VM identity and guest configuration unchanged. See extra host mappings.
Dependencies and packaging
Source builds, CI, and release artifacts use Go 1.27.4. The dependency refresh updates golang.org/x/sys to 0.50.0, golang.org/x/term to 0.48.0, golang.org/x/text to 0.44.0, and github.com/klauspost/compress to 1.20.4. Packaging uses GoReleaser 2.20.1, bundled nFPM 2.48.0, and Syft 1.57.1. RPM payload verification now requires SOURCE_DATE_EPOCH timestamps. Pins and license inventories are recorded in the release source.
govulncheck found no vulnerabilities in imported packages or reachable symbols. Its module-level report lists GO-2026-5932 in golang.org/x/crypto/openpgp, which Barn does not import.
Image catalog
The embedded signed catalog remains revision 2026110502, with 9 distribution families and 17 architecture-specific stable artifacts. Both official repositories passed signature verification, activation in fresh data roots, embedded-byte comparison, and availability/size checks. Ubuntu 24.04 on the native architecture remains the default image.
Release validation
All 20 published attachments were downloaded again and matched their checksums. Acceptance passed 18 Linux/KVM operations, 15 native Mac lifecycle phases, and desktop, bidirectional clipboard, and window-close checks. The public installers, Homebrew upgrade and formula test, and three-platform Homebrew CI passed. See the release validation record for signing, notarization, upgrade, and recovery evidence.
Install or build
Stop running Mac VMs before replacing Barn.app, then upgrade through Homebrew:
Or install the versioned release:
The installer verifies archive SHA-256. The Darwin arm64 archive includes the signed, notarized CLI, helper, and Barn.app; other archives and Linux DEB/RPM packages contain the CLI and matching helper. Find every platform in the download table, then follow installation or source builds.
1.2 - Barn 1.1.0: image selection and live progress
Released on 2026-11-09. Barn 1.1.0 adds image and guest-architecture overrides, live progress tables, and more reliable CPU/memory resizing. It embeds signed image catalog revision 2026110502, with 9 distribution releases and 17 architecture-specific artifacts. The default remains Ubuntu 24.04 on the host’s native architecture.
Image and architecture selection
Use --image/-I and --arch/-A with plan, up, reload, recreate, and resize to override the inventory for one command. Image overrides apply to selected nodes, and suggested follow-up commands retain the selections, overrides, and repository. Existing VMs require recreate to change their image or architecture.
init and generated setup templates save the choices into the new inventory; validate accepts the same overrides without changing the file. Image info, pull, and named imports accept -A. Architecture names include amd64 (x86_64, x64), arm64 (aarch64, a64), and native; image info and image pull retain the earlier -a spelling.
Matching guests use HVF or KVM. Foreign guests use QEMU TCG for compatibility testing, with a stable ARM CPU model for UEFI boot. On Linux, install the target QEMU system emulator and matching firmware separately; barn setup prepares native host dependencies. All nodes in a deployment share one guest architecture.
Progress and results
Linux and macOS lifecycle commands, setup, network operations, image transfers, and repository maintenance now share live progress tables and consistent completion summaries. Terminal redraws stop before a prompt or error, and redirected output uses ordinary lines. Validation lists the checks it performed. Status tables and labels use plain ASCII, including [DONE], [FAILED], and [CANCELLED], and barn purge deletes the deployment immediately by default (pass --ask for an interactive confirmation prompt). Use --json or --yaml for structured results.
Recovery fixes
- Offline resize holds the deployment lock across inspection, shutdown, and commit, preserving QEMU process identity when another command tries to start the same node.
- Interrupted online resize retains a recovery marker before hotplugging.
statusreports that live CPU or memory may differ from the applied configuration (stage: "resize",reason: "resize_incomplete"); retryresizeor runup/startto recover. - Plans for deployment-wide changes include new nodes in the recreation scope and avoid suggesting an
upcommand before recreation is complete. - Lifecycle retry commands preserve an explicit official
--repo, even whenBARN_REPOpoints elsewhere. Missing native QEMU programs trigger host preparation; missing foreign Linux emulators name the required dependencies.
YAML parsing uses go.yaml.in/yaml/v4 4.0.0, and flag parsing uses pflag 1.1.0. Existing YAML input/output settings are retained, and unfinished YAML documents report the parser’s EOF line. Packaged licenses and SPDX documents cover the updated dependencies.
Upgrade and downloads
Stop running macOS guests before replacing Barn.app, then upgrade:
Or download and run this release’s installer:
The installer verifies the selected archive against checksums.txt. The Darwin arm64 archive includes the signed, notarized CLI, hosts helper, and Barn.app; the other archives and Linux DEB/RPM packages provide the CLI and matching helper. VM disks, keys, and state remain under BARN_HOME.
See the documentation, full changelog, and changes since 1.0.0.
1.3 - Barn 1.0.0: the first stable release
Released on 2026-11-05. Barn 1.0.0 is the first stable release. For the latest stable release, see Barn 1.1.0.
Barn 1.0.0 completes a pre-release audit of destructive operations, data-disk recovery, catalog integrity, and process cancellation. It embeds signed image catalog revision 2026110502, including Debian 13 image 20261103.2651.1 for amd64 and arm64.
Install Barn · GitHub release and assets · Changelog
Changes
- Whole-deployment recreate keeps omitted nodes.
barn recreatewithout node selectors refuses an inventory that no longer lists an existing node (conflict, reasonnodes_removed, exit 4) before any VM stops. Earlier releases destroyed the omitted node without recreating it. Runbarn destroy NODEto remove it, or add it back to the inventory. The confirmation lists every node that will be replaced, and the scope is checked again under the destruction lock (deployment_changedwhen the applied state changed during preflight). - Destroying the control node keeps the lab usable. The first remaining node becomes the control node, so
ssh-config,hosts,exec, andprovisionkeep working. Update the inventory’s control role, then runbarn upto install its control SSH access. Barn refuses to destroy the control node, before deleting anything, when the successor has an unfinished prepare record that cannot be completed; the error names the successor. - Data disks are reset only on recognized corruption. Blank disks are initialized. An ext4 or XFS filesystem is reset only when the checker reports recognized corruption, including on a persistent disk. Partition tables, signatures without a UUID, unsupported filesystem features, and unknown checker failures keep their data and produce a warning. Existing guests receive the updated helper on their next
barn up. - One confirmation before host changes. On a terminal,
up,start,restart, andreloadshow the host-preparation plan and ask once before installing packages or the Barn network. Without a terminal they report what is missing; runbarn setup --yesfor unattended preparation. --yesfor destruction.destroy,recreate,mac destroy, andmac recreateaccept--yes.--forcekeeps working, andmac stop --forcestill powers the VM off.- Catalog failures exit 7. A catalog signature or signed revision that does not verify exits 7 (
integrity, reasoncatalog_signatureorcatalog_equivocation). Permission and I/O errors on a cached signature remain runtime failures (exit 1). Both official repositories share the embedded upgrade baseline, and a new trusted signature over identical catalog bytes is accepted. - Cancellation reaches
sudo. Cancelled commands receive SIGTERM, whichsudoforwards, and Barn waits at most 10 seconds for the process and its inherited pipes. - Host networking and Mac images.
barn network uninstallkeeps global networkd unit state when other host links exist (preserve_networkd,checks.networkd). Mac image pruning keeps bases that interrupted recreates still reference. - CLI.
barnwithout a command prints the complete help;--jsonand--yamlkeep theactions[]document. Explicit--json,--yaml, and--verboseoverrideBARN_OUTPUTandBARN_VERBOSE, and help stays available when those variables are invalid. - Dependencies. Go 1.27.3,
golang.org/x/net0.62.0, andgo.yaml.in/yaml/v33.0.6.
Upgrade
Upgrade through the channel you installed with. The installer selects the latest stable release (1.1.0), verifies the archive against the release’s checksums.txt, and installs into ~/.local/bin without sudo; set BARN_VERSION=1.0.0 to pin 1.0.0. DEB and RPM packages are attached to the GitHub release.
Upgrading from 0.9.3 keeps VM disks, SSH keys, state, and configuration under ~/.barn and requires no recreation. Stop macOS VMs before replacing the native component. The Apple Silicon archive and the Homebrew installation include the signed, notarized Barn.app with the CLI and hosts helper; Intel macOS and Linux amd64/arm64 archives provide the CLI and hosts helper.
Check scripts for three changes: catalog signature failures exit 7 instead of 1, a whole-deployment recreate exits 4 when the inventory omits an existing node, and interactive up asks before preparing the host.
Known limitations
- A malformed Mac recovery journal stops
barn mac image ls, the space estimate inbarn mac doctor, and image pruning until it is repaired. - One unreadable Mac machine state stops
barn mac ls; the other machines stay usable by name. - Linux guest shares on macOS hosts need a guest kernel with 9p support.
Security fixes land in the latest 1.x patch release; 0.x releases receive no further fixes. See the Linux quick start and Mac guide.
1.4 - Barn 0.9.3: safer resizing, selected nodes, and recovery
Released on 2026-10-31. Barn 0.x releases use GitHub’s pre-release channel. For the latest release, see Barn 1.1.0.
Barn 0.9.3 focuses on safer changes to local labs. It adds resource-only VM resize, lets selected nodes converge independently, accepts ordinary user path forms for inventories and shares, and recovers interrupted lifecycle operations.
Install Barn · Download 0.9.3 · Source release notes
Changes
- Edit
vm_cpuorvm_memand runbarn resize [node...]. Supported increases are hot-plugged when QEMU has reserved capacity; decreases and unsupported cases use an automatic stop/start while preserving the VM identity, disks, and SSH keys. Usebarn recreatefor image, disk, network, or share changes. plan,up,reload,recreate, andresizewith selectors retain the applied definitions of unselected peers. Conflicts in addresses, aliases, control-node roles, and deployment-wide settings are checked before a node stops or is deleted.barn init [template] --imageandbarn setup [template] --imageselect an image alias, channel, or version in generated templates without downloading it or rewriting an existing inventory.- Relative paths,
~/, and directory symlinks work forBARN_HOME, local repositories, catalog export, inventories, imported images, and share sources where applicable. Inventory-relative shares resolve beside the inventory’s real file; spaces, Unicode, overlapping sources, and readable directories owned by another user are supported. - Provisioning accepts any positive
--paralleland--timeout. Local images and catalog artifacts with declared sizes can exceed 8 GiB; digest-indexed imports, bounded reads, registry size checks, and cache verification protect the image store.barn mac logs --linesaccepts any positive line count. - The embedded signed catalog is revision
2026102802, with 9 distribution families and 17 architecture-specific artifacts. The default remainsu24:stableon the host architecture. - Interrupted Linux and macOS lifecycle, persistent-disk, and replacement operations leave journaled state for safe recovery. Signals produce the structured
cancelledresult.barn mac up --no-waitreturns when the VM process starts; runbarn mac up NAMEagain before using SSH or exec on a machine whose first boot is still provisioning.
Upgrade
Select 0.9.3 explicitly with the download installer, or upgrade the Homebrew formula / Linux package. Existing VM disks and configuration remain under ~/.barn; no VM recreation is required.
Run barn update when you want to activate the current signed catalog from a repository. Pin vm_image: family@version when a pipeline must remain on one exact image across catalog updates. Stop macOS VMs before replacing the native component. See the Linux quick start and Mac guide.
1.5 - Barn 0.9.2: signed Barn.app and explicit SSH/exec arguments
Released on 2026-10-30. Barn 0.x releases use GitHub’s pre-release channel.
Barn 0.9.2 includes the complete native macOS guest runtime in the Apple Silicon download and Homebrew installation. You can create Linux and macOS VMs after installing a single package, without installing Go or Xcode.
Install Barn · Download 0.9.2 · Source release notes
Changes
- The Darwin arm64 archive contains
barn,barn-hosts-helper, andBarn.appfrom the same source commit. All three executables use Developer ID signatures and are submitted together for Apple notarization; the app carries a stapled ticket and passes Gatekeeper assessment. brew install pgsty/infra/barninstalls the CLI, QEMU, helper, and native app. The CLI discovers the app under Homebrew’slibexec. macOS guests require Apple Silicon, macOS 27+, and a logged-in desktop session.- The helper is signed before its final SHA-256 is embedded in the CLI. Packaging checks the pair again after attaching the native payload to the release archive.
ssh,exec,mac ssh, andmac execrequire--before remote commands. SSH passes a command line to the guest shell; exec preserves individual arguments. Usesh -cwith exec for pipelines or redirection.- Linux arm64/amd64 and Intel macOS continue to ship the CLI and helper for QEMU Linux guests.
Install and use
The first macOS VM downloads Apple’s restore image and prepares a reusable base installation. Supply an existing compatible IPSW with barn mac setup --ipsw PATH to use a local restore image.
Upgrade
Stop running Mac VMs before replacing the app, then run brew update and brew upgrade pgsty/infra/barn. VM disks and configuration remain in ~/.barn; no recreation is required. Update older SSH/exec scripts to use -- and pass exec programs and arguments separately.
For the download installer, specify BARN_VERSION=0.9.2 because GitHub’s releases/latest endpoint does not select pre-releases. The release includes four platform archives, Linux DEB/RPM packages, SPDX inventories, checksums, the installer, and the generated Homebrew formula. release.json records the exact source commit and pinned build tools.
Path, sharing, node-selection, and limit improvements implemented after this release are documented under Barn 0.9.3 Changes.
1.6 - Barn 0.9.1: Go 1.27.3 toolchain and Barn.app rename
Released on 2026-10-22. Barn 0.x releases are published via GitHub’s pre-release channel. For the latest release with a signed and notarized Barn.app in the Apple Silicon archive and Homebrew formula, see Barn 1.1.0.
Barn 0.9.1 updates the Go toolchain and dependencies and completes the native Mac app rename to Barn.app. CLI commands, inventory format, image catalog, and existing VM state paths remain compatible with 0.9.0.
Install Barn · Download 0.9.1 · Validation history
Changes
- Go 1.27.3 is the baseline for source builds, CI, and release artifacts. All modules required by
go.mod—including indirect and dependency-test modules—are updated to their latest stable versions as of 2026-10-22, includinggolang.org/x/cryptov0.58.0,x/netv0.61.0,x/sysv0.49.0,x/termv0.47.0, andx/textv0.43.0. - Barn.app replaces the
Barn Mac.appbundle name, using bundle identifiercom.pgsty.barnand updated Barn menu labels. The 0.9.1 CLI continues to discover olderBarn Mac.appbundles if present. Native protocol version 2 and~/.barn/macare unchanged. - The standalone Mac runner uses hardened-runtime signing and requests a secure timestamp when signing with a Developer ID certificate. Payload validation checks the app bundle name, identifier, and executable alongside its version, commit, and content digests. Both the installer and Homebrew template use the new app path.
- Build and verification tools are updated to GoReleaser 2.18.3 (with bundled nFPM 2.47.0), standalone nFPM 2.48.0, Syft 1.55.0, Staticcheck 2026.2.2, deadcode 0.52.0, golangci-lint 2.15.0, and govulncheck 1.9.0. GitHub Actions remain pinned by commit, and dependency-license inventories match the updated modules and toolchain.
Upgrade
Pin 0.9.1 explicitly when running the download installer, or upgrade via the Homebrew formula or Linux package manager (see the installation guide).
Before replacing the native Mac component, stop running Mac VMs and upgrade the CLI, hosts helper, and app together from the same build—the 0.9.0 CLI only looks for the old Barn Mac.app bundle name. Desktop window positions now use ~/Library/Preferences/com.pgsty.barn.plist, so saved window coordinates may reset once. VM disks, credentials, and configuration under ~/.barn/mac are preserved without recreating VMs or refreshing image catalogs.
The 0.9.1 release assets include macOS and Linux (arm64 / amd64) CLI and hosts-helper archives plus Linux DEB and RPM packages. To use barn mac, build the CLI and Barn.app together from tag v0.9.1 with Go 1.27.3 and Xcode 27 as described in the macOS guide.
To build the CLI alone from tag v0.9.1:
See the 0.9.1 validation record for the build, CI, VM acceptance, and signing scope.
1.7 - Barn 0.9.0: native macOS guests and smoother Linux recovery
Released on 2026-09-30. Barn 0.9.0 manages both Linux labs and native macOS machines from a single CLI, while improving diagnostics and recovery after interrupted operations. For the latest release, see Barn 1.1.0.
Install Barn · Download 0.9.0 assets · Full changelog
macOS machines on Apple Silicon
barn mac creates named macOS 27 virtual machines with a native desktop window, SSH, shared folders, and text clipboard sharing. The first machine installs a shared golden base from Apple’s verified restore image, and subsequent machines clone that base with APFS copy-on-write—each getting its own writable disks, credentials, and private subnet.
Mac guests require Apple Silicon, macOS 27+, and the native app bundle built from the release tag with Xcode 27 (see the macOS guide). In accordance with Apple’s Virtualization.framework limit and software license, up to two macOS VMs can run concurrently per Mac across all tools. Mac VM state lives under ~/.barn/mac, independent of your Linux inventory and untouched by Linux destroy or purge.
Easier Linux lab recovery
Re-running barn up resumes unfinished setup: you can fix an inventory error after a failed first start and retry in place, or resume an interrupted destroy. After a host reboot, if the OS assigns a saved QEMU PID to an unrelated process, Barn detects the mismatched process birth time, marks the VM stopped, and never signals the unrelated process.
Concurrent mutating commands wait up to ten minutes for the deployment lock and report which operation holds it, while read-only status and SSH commands remain available. Execution plans now list each node’s data disks, including the default 128 GiB /data disk.
Diagnostics with a next step
Inventory validation errors report the violated rule, value, and source line, and suggest corrections for mistyped vm_* fields. Failed external commands preserve the relevant tail of stderr, and JSON error responses use a consistent error class with optional reason and next fields.
Host setup explains package and network changes before prompting for sudo. Symlinked ~/.ssh/config files managed by dotfile tools are left untouched; Barn prints the Include line for you to add.
Updated Linux images
Embedded catalog 2026093001 covers 41 artifacts across 9 families. Debian 12/13 and Rocky Linux 8/9 use Barn-normalized images: Debian includes offline XFS tools and a pre-generated en_US.UTF-8 locale while keeping C.UTF-8 as default; Rocky Linux 8 uses its shipped chrony configuration and completes SELinux filesystem labeling during image build to avoid first-boot relabeling delays. Ubuntu retains Canonical’s upstream image bytes, with Ubuntu 24.04 (u24) as the default guest OS.
barn update refreshes the local Linux image catalog without modifying the barn binary or existing VM disks.
Upgrade notes
- The CLI command is
barn, the default inventory filename isbarn.yml, state lives under~/.barn, and environment variables use theBARN_*prefix. - The
rmalias has been removed; runbarn purgeexplicitly to delete an entire Linux deployment including persistent disks and keys. - In JSON output,
recreate_requiredandnodes_removedare nowreasonvalues undererror: "conflict",resource_conflictis renamed toresource, and missing inventories or unknown images exit with code2(usage). See the exit-code reference. stopandstartpreserve all disks, whilerecreatereplaces root and non-persistent data disks. See the storage guide for howuphandles damaged data-disk filesystems during recovery.
Get started with the Linux tutorial or the macOS guide, and report issues in GitHub Issues.
1.8 - Barn 0.8.0: clearer recovery and refreshed images
Historical release record (0.8.0). Old release downloads are retired; use the installation guide for the current release.
Barn 0.8.0 cuts down on manual cleanup after interrupted host setup or partial VM starts, and refreshes the Debian and Ubuntu images while preserving previous catalog artifacts and existing VM identities.
What changed
- Host preparation follows the command. Interactive
start,restart, andreloadcan install missing host tools and bring an inactive Barn network back online. On macOS, first-time network installation locates or installs Homebrew (or downloads and verifies the pinned archive) before prompting for administrator credentials, preventing Homebrew from invalidating an earliersudotoken. - Failed nodes do not block independent peers. During
uporstart, a missing host share fails only the affected node while healthy stopped peers continue to start. Errors report both the missing host source path and guest mount point without creating empty placeholder directories, andrestart,reload, andrecreateverify share paths before stopping running VMs. - Recovery preserves identity. If a deployment public key is missing, Barn derives it from the existing private key; if the private key is lost, Barn prints backup recovery steps rather than generating a replacement keypair that existing guests would reject. macOS
socket_vmnetlog-directory recovery preserves network ownership markers to avoid false subnet-conflict errors. - Failures keep their context. Scoped retry hints preserve custom
-finventory paths,--repooverrides, and command names (a failedstartsuggestsstart). Initial setup and its retry share a single operation ID, bounded event logs can be written and read before deployment state exists, and inspecting uncached image metadata no longer requiresqemu-img. - Accurate cleanup summaries. Deleting persistent disks or running
purgereports the final deleted state without listing the same disks as both retained and removed. Orphaned disks from previously removed nodes no longer blockdestroyof the remaining lab.
September images
Embedded catalog 2026092001 contains 37 artifacts across 9 families, retaining all 27 previous artifacts and adding new amd64 and arm64 stable builds:
| Family | System version | Image catalog version |
|---|---|---|
d12 |
Debian 12.15 | 20260909.2596.1 |
d13 |
Debian 13.7 | 20260914.2601.1 |
u22 |
Ubuntu 22.04.5 | 20260913.0.0 |
u24 |
Ubuntu 24.04.5 | 20260911.0.0 |
u26 |
Ubuntu 26.04.1 | 20260918.0.0 |
Debian images include offline XFS tools and a pre-generated en_US.UTF-8 locale (C.UTF-8 remains default). Ubuntu images preserve Canonical’s upstream bytes and configure accounts and networking via cloud-init. Existing VMs and pinned image versions keep their current base images; see the image reference for catalog update details.
Install or upgrade
For an existing lab, inspect barn plan and run barn up. start powers on existing nodes as-is, while up applies inventory changes and retries unfinished guest bootstrap. See the storage guide for how up handles damaged test data disks during recovery.
Validation and limits
Four hosts—two macOS arm64/HVF machines (m1, m5) and two Linux amd64/KVM machines (m0, m3)—completed the 7-distro pro acceptance pass covering Rocky Linux 9.8/10.2, Debian 12.15/13.7, and Ubuntu 22.04.5/24.04.5/26.04.1. Candidate 6d7870e performed a clean initialization from the LAN repository, and runtime candidate 1c054a0 upgraded in place and passed repeated healthy up, stop/start, two rounds of guest SSH, data-disk checks, Ansible configuration reads, and control-node lateral SSH (plus 7-node Ansible ping on both Macs) while preserving VM UUIDs, image identities, and disk inodes.
Single-run timings from the acceptance pass (the first up includes image download):
| Host | Platform | Clean first up (6d7870e) |
Healthy up (1c054a0) |
Existing-disk start (1c054a0) |
|---|---|---|---|---|
m0 |
Linux amd64 / KVM |
117.915 s | 3.924 s | 36.944 s |
m1 |
macOS arm64 / HVF |
86.309 s | 1.745 s | 39.639 s |
m3 |
Linux amd64 / KVM |
98.443 s | 2.169 s | 44.068 s |
m5 |
macOS arm64 / HVF |
87.946 s | 1.467 s | 38.162 s |
Release tag v0.8.0 (320a32afa8f6fca02592215aa0d5607ca4e852b2) updates only documentation relative to the tested runtime and passed local make check, source CI, the packaging snapshot, and the tag workflow. All 20 release assets (19 payloads plus checksums.txt) were verified against the public pre-release. Earlier regression and image-boot records are archived in the UX audit and image refresh record.
Isolated barn update runs against both official repositories verified signatures and activated catalog 2026092001, with all ten new image objects returning HTTP 200 and matching content lengths. Isolated installer runs on macOS arm64 and Linux amd64 verified 0.8.0 (320a32a) binary delivery, and the Homebrew tap update passed local audits and Homebrew CI.
Known limitations in 0.8.0:
- macOS host directory sharing for Linux guests remains blocked by upstream QEMU
virtfsdirectory-descriptor behavior on Darwin; preflight now diagnoses this earlier. - A missing control-node guest private key is now detected even when an older ready marker exists, though restoring peer SSH still requires recreating the control node. See the validation history for the full dated test matrix.
1.9 - Barn 0.7.0: simpler test labs and automatic recovery
Historical release record (0.7.0). Old release downloads are retired; use the installation guide for the current release.
Barn 0.7.0 streamlines first-run startup and recovery for local test labs: re-running barn up finishes interrupted work, retries guest bootstrap stages, and updates guest helper scripts in place without restarting running VMs.
What changed
- Shorter first run. Interactive
upcan generate the default inventory, install missing host tools, and bring an intact inactive Barn network back online. Fast checks stay quiet while longer tasks display progress and a compact summary. - Independent guest stages. Data disks, host shares, guest hostnames, node-to-node SSH, and private networking initialize independently. If an optional stage fails, Barn reports the specific limitation while keeping management SSH available, and subsequent
upruns skip stages that are already healthy. - Automatic test-disk recovery. Healthy filesystems are mounted as-is; unrecognized or confirmed-corrupt filesystems on test data disks are reformatted and remounted with an explicit data-loss notice. Transient probe failures, missing devices, busy mounts, and host I/O errors never trigger formatting.
- Fewer manual fixes. Unwritable host shares fall back to read-only and recover on the next
uponce permissions are fixed. Occupied automatic SSH forward ports are reassigned for stopped guests. Interrupted image downloads resume automatically and fail over across official repositories while enforcing SHA-256 digest verification. - Clean output and backward-compatible state. Guest warnings are stored in a disposable cache outside the schema-2 VM documents and node directories, so older Barn binaries can still inspect and manage the deployment.
Upgrade notes
- Re-running
upperforms guest recovery (there is no separaterepaircommand), whereasstartpowers on existing nodes without applying inventory changes. Passing--no-waitskips readiness and recovery checks. See the storage guide for data-disk recovery rules. - A guest with working management SSH and optional stage warnings returns exit code
0. Automation that requires every optional feature should inspectnodes[].warningsandnodes[].repairsin--jsonor--yamloutput. - If an earlier 0.6.0 bootstrap failed after deleting the staged control-node private key,
uprestores management SSH and reports acontrol-sshwarning; runbarn planandbarn recreateon the control node if it needs peer-to-peer SSH.
Install or upgrade
The release includes macOS and Linux (amd64/arm64) archives, Linux DEB/RPM packages, the installer script, checksums, SBOMs, and a Homebrew formula.
Validation
Release commit 9c6d4896d93733d1cb60a7e5d8591e9a06659c9d passed source CI, the packaging snapshot, and the tag workflow, producing 20 verified release assets. Public macOS arm64 and Linux amd64 installer runs verified binary delivery for 0.7.0, and macOS validation covered CLI smoke checks and cross-compilation.
On Ubuntu 26.04 amd64 with KVM/QEMU 10.2.1 and Ubuntu 24.04 guests, recovery tests exercised ext4/XFS corruption, retained persistent disks, failed probes, busy mounts, read-only share fallback, and repeated healthy up runs without restarting the QEMU process:
- An interrupted 0.6.0 bootstrap (stopped at the management-egress probe) resumed under 0.7.0 in 3.3 s while preserving its data disk and UUID, and remained manageable by the 0.6.0 binary (
status,stop,start,destroy). - A deliberately corrupted disposable ext4 data disk was reformatted and remounted in 3.2 s with an explicit data-loss notice while keeping the VM process running.
1.10 - Barn 0.6.0: Ubuntu 24.04 and smoother local labs
Historical release record (0.6.0). Old release downloads are retired; use the installation guide for the current release.
Barn 0.6.0 makes Ubuntu 24.04 the default guest image and improves planning, startup, scale-out, and node recreation for local Pigsty labs.
What changed
- Ubuntu 24.04 by default. New inventories and the embedded catalog default to
u24:stable. Catalog revision2026090501also bundles the normalized Debian 12/13 and Rocky Linux 8/9 image updates published in the official repositories. - Run
planbefore host setup.barn planresolves catalog images without requiring QEMU or host networking to be installed first (registeredlocal-*images still useqemu-imgcache inspection). Plans display exact image versions, aggregate CPU/memory/disk totals, pending node starts, modified fields, and disk actions, while preserving custom-finventory paths in suggested next commands. - Clearer status and error reporting.
barn statusincludes image, CPU, and memory columns, and a damaged state file on one node no longer hides healthy peers—corrupt state errors name the exact file and cause. Image digest mismatches return exit code7, SSH exit code255passes through unchanged, and--no-waitexplicitly notes that readiness checks were skipped. - Preflight checks before disruption. Targeted
recreatedetects conflicting peer changes before deleting any disks, andreloadvalidates startup dependencies before stopping running guests.stop,destroy, and runtime cleanup update deployment state consistently under the deployment lock. - Automatic guest
/etc/hostsand SSH updates. Starting or removing nodes refreshes Barn-managed/etc/hostsblocks and control-node SSH configuration inside running guests, so recreated peers remain reachable without manually pruning guestknown_hosts. User-authored SSH config entries and global option scopes remain intact. - Cleaner CLI parsing. Custom
initoutput paths print matching follow-up commands, output flags no longer swallow adjacent option values, malformed integer sizes and multi-document YAML files are rejected rather than truncated, andALL_PROXY/all_proxyacts as a fallback behind scheme-specific proxy variables andNO_PROXY.
Upgrade notes
Upgrading Barn leaves existing VM disks and explicit vm_image settings untouched. If an older inventory relied on the implicit Debian 13 default, add vm_image: d13 under all.vars before running barn up with 0.6.0. Always inspect barn plan before applying changes.
Applying CPU or memory changes requires barn recreate, which replaces root and non-persistent data disks while retaining persistent data disks. Passing --no-wait skips both readiness checks and guest /etc/hosts updates until the next barn up. Automation should consume --json or --yaml output; partial status results return healthy nodes alongside a failures array and exit code 5.
Install or upgrade
The release also provides four platform archives, amd64/arm64 DEB and RPM packages, and a Homebrew formula. See the Linux tutorial to get started.
Validation
The lifecycle and UX changes passed code review and the full local make check suite. An isolated macOS arm64/HVF lab verified Ubuntu 24.04 boot, scale-out, control-to-peer SSH, stop/start, reload, recreate, partial status failure handling, and scale-in, and the guest SSH option-scoping fix was verified against sshd/ssh -G configuration evaluation.
The tag workflow repeated all source checks and verified the release archives, Linux packages, SBOMs, installer script, Homebrew formula, release metadata, and SHA-256 checksums.
1.11 - Barn 0.5.0: explicit disposal, explicit repositories, reproducible images
Historical release record (0.5.0). Official mirror failover was later added in 0.7.0, and the rm alias was removed in 0.9.0. Old release downloads are retired; see the CLI reference and installation guide for current usage.
Barn 0.5.0 adds an explicit command to dispose of an entire lab at once, lets operators choose between the global and China Mainland image repositories, and introduces a reproducible build pipeline for normalized official guest images while keeping the Pigsty inventory contract, deployment state format, and embedded catalog revision 2026082903.
What changed
barn purge(barn rmin 0.5.0–0.8.0) removes the entire Linux deployment—including persistent data disks, deployment keys, state files, and the default SSH config fragment—without interactive confirmation, while preserving the shared image cache and host-global network. Runningpurgewhen no deployment exists succeeds as a no-op; if residual node directories cannot be verified as Barn-owned,purgestops rather than deleting unknown files.- Explicit repository selection. Released binaries default to
https://repo.pigsty.io/barn, while--mirrorselects the China Mainland mirrorhttps://repo.pigsty.cc/barn. Precedence follows--repo,--mirror,BARN_REPO, and the compiled default, and both official roots verify against the same signed catalog. - Strict repository pulls. Upstream URLs recorded in the image catalog document image provenance and are no longer used as implicit download fallbacks. The selected repository must serve the exact catalog-named
.qcow2artifact, or the pull fails with instructions to switch repositories or import a local file. - Digest-pinned offline image builder. The source repository includes an offline normalization pipeline for Debian 12/13 and Rocky Linux 8/9 across
amd64andarm64, recording the exact package closure, SBOM, provenance, andtestingmanifest so candidate repositories can be assembled for native boot tests and signing. - Toolchain updates. Source and release pins move to Go 1.27.1, GoReleaser 2.18.0, golangci-lint 2.13.2, and updated Go modules.
Image normalization pipeline
The eight-target build matrix prepares normalized guest candidates:
- Debian 12/13: installs offline XFS userspace tools for
xfsdata disks. - Rocky Linux 8: provides
/usr/bin/python3, fixessshd_config.ddrop-in inclusion, and cleans legacy network interface state. - Rocky Linux 9: cleans legacy network interface state to match Rocky Linux 8.
Candidate builds remain unsigned and marked testing until they pass native boot/readiness smoke tests, two-build reproducibility checks, production signing, and catalog publication (published in 0.6.0).
Upgrade and disposal
Upgrading from 0.4.0 requires no state, inventory, or catalog migration, and existing cached images remain valid. Use barn purge only when you want to discard all deployment disks and keys without a prompt; continue to use barn destroy when preserving persistent data disks or removing specific nodes.
Validation
Release commit fc85b65ff6a24b0933b56ae1179be9ada2ba91b1 passed the full local source gate—including module and shell checks, unit and race tests, go vet, Staticcheck, deadcode, errcheck, govulncheck, four-target cross-builds, image-pipeline and installer tests, dependency-license checks, and GoReleaser 2.18.0 validation—and the tag workflow verified all archives, DEB/RPM packages, SBOMs, Homebrew formula, installer script, release metadata, and SHA-256 checksums.
Install or upgrade
Download the installer and assets from the Barn 0.5.0 GitHub Release:
The release also includes the Homebrew formula and amd64/arm64 DEB and RPM packages.
1.12 - Barn 0.4.0: one command, working data disks, one voice
Historical release record (0.4.0). Old release downloads are retired; use the installation guide for the current release.
Barn 0.4.0 turns the first run into a single command, makes default data disks work out of the box on Debian and Ubuntu images, and unifies output formatting across all CLI commands while keeping the Pigsty inventory format, fixed-IP deployment model, state layout, and embedded catalog revision 2026082903.
What changed
- Single-command first run. When run in an interactive terminal on a host where the fixed-IP network has not been installed yet,
barn upautomatically invokesbarn setup: it prints the host preparation plan, asks for confirmation before the privileged step, and continues directly into VM startup.barn initnow points tobarn upas the next command. vm_disks[].fsdefaults toauto. Blank data disks are formatted as XFS when the guest hasmkfs.xfsand fall back to ext4 otherwise, matching Pigsty’s Vagrant workflow. Previously, thexfsdefault failed on upstream Debian and Ubuntu cloud images that shipped withoutxfsprogs. Existing deployments created under the old default are not flagged as configuration drift and keep their persistent disks intact.- Clearer guest bootstrap errors. Guest failure markers now capture the failing command’s last output line, so
upreportsguest bootstrap failed during data-disks: xfs requested but mkfs.xfs is unavailablerather than a bare exit status. - Unified CLI output. Lifecycle commands and
statusprint a consistent node table and, afterup, anext: barn ssh <node>hint.planandvalidateprint a single summary line when no changes are needed, whileimage list,network status,doctor, and preflight errors use plain prose without internal error codes (spec hashand internal metadata remain available via--json). Unknown-node errors list the available node names. - Standard SSH command pass-through.
barn ssh <node> -- 'df -h /data; id'forwards the remote command string to OpenSSH unchanged.
Upgrading
If a Debian 12/13 or Ubuntu node created with 0.3.0 or earlier failed to mount /data, run barn recreate <node> once so the disk formats as ext4. Non-interactive automation running up on an unprepared host without a TTY should still run barn setup --yes beforehand.
Validation
Source commit 8ecb8476c7dbc934d8cbbee935ac53884d00fcdb passed the full local source gate—unit and race tests, go vet, Staticcheck, deadcode, errcheck, govulncheck, shell and module checks, four-target cross-builds, image-pipeline and installer tests, and dependency-license verification—and the tag workflow built and verified every archive, native package, SBOM, and installer script. Dated macOS arm64/HVF and Ubuntu amd64/KVM acceptance records are listed in the validation history.
Install or upgrade
Download the installer and assets from the Barn 0.4.0 GitHub Release:
The release includes the Homebrew formula and amd64/arm64 DEB and RPM packages.
1.13 - Barn 0.3.0: explicit catalogs and actionable readiness
Historical release record (0.3.0). Old release downloads are retired; use the installation guide for the current release.
Barn 0.3.0 replaces implicit catalog refreshes with explicit update commands, reports guest bootstrap failures per node, and streamlines the CLI surface while keeping the Pigsty inventory and fixed-IP deployment model.
What changed
- Explicit catalog updates.
up,plan, and standardimagecommands read the active local catalog snapshot without fetching remote metadata behind the scenes. Runbarn updateto fetch the latest signed catalog from the configured repository, orbarn image syncto activate a specific URL or local file. - Per-node bootstrap diagnostics. Guest initialization records the active bootstrap stage atomically. Partial failures list each failed node, stage, and error message, and readiness timeouts point directly to
barn logs <node>. - In-place readiness rechecks. Running guests can re-run readiness verification without restarting QEMU, and partial lifecycle runs still regenerate the host SSH client config for all committed nodes.
- Native guest interface names. Netplan matches NICs by deterministic MAC address without renaming guest network interfaces, and
vm_disks[].fs: autoprefers XFS with automatic fallback to ext4. - Streamlined CLI. The redundant
sscommand and ambiguous root/flag shorthands are removed, andbarn image resetreplacesreset-manifest(kept as an alias).
Validation
Source commit da6d02426da93c677c94c67fec4eb4fcecf4766a passed the full local source gate and a complete GoReleaser snapshot—covering unit and race tests, go vet, Staticcheck, govulncheck, four-target cross-builds, image-pipeline and installer checks, archive/package parity, dependency licenses, SBOMs, and Linux package verification—and the tag workflow repeated these gates before publication. Dated macOS arm64/HVF and Ubuntu amd64/KVM acceptance records are listed in the validation history.
Install or upgrade
Download the installer and assets from the Barn 0.3.0 GitHub Release:
The release includes the Homebrew formula and amd64/arm64 DEB and RPM packages. Existing 0.1.0 and 0.2.0 deployment and catalog state remains compatible.
1.14 - Barn 0.2.0: selected convergence and release integrity
Historical release record (0.2.0). Old release downloads are retired; use the installation guide for the current release.
Barn 0.2.0 keeps the Pigsty inventory and on-disk deployment format introduced in 0.1.0 while fixing VPN route preflight false positives and selected-node convergence issues uncovered during native host testing.
What changed
- VPN-aware route preflight. Broad VPN exclusion routes such as
10.0.0.0/8no longer block Barn’s more specific10.10.10.0/24lab subnet. Equal or more-specific foreign routes, overlapping interfaces, occupied IP addresses, or a missing Barn route still fail preflight before any VM starts. - True selected-node scale-out.
barn up <node>downloads images and prepares disks only for the targeted node. Uncreated inventory peers appear asabsent, and SSH config,/etc/hosts, guest provisioning, UUID/port allocation, lifecycle commands, and persistent-disk checks operate strictly on the committed node set while leaving the full inventory intact for later expansion. - Updated
/etc/hostsownership markers. Newly provisioned guests write# barn-deployment-hostand clean up both that marker and the 0.1.0# barn-project-hostmarker when refreshing host entries. - Operational polish. Includes graceful command cancellation, structured JSON/YAML output, interactive confirmations,
barn reload, diagnostic redaction, and a trimmed Go dependency graph.
Native and release evidence
- macOS
arm64/HVF (with MonoProxy active): verified selectedu24-1creation, SSH,stop/start, incremental cachedel9-1scale-out, two-node lateral SSH alongside fiveabsentpeers, and full deploymentdestroy. - Ubuntu 26.04
amd64/KVM: verified read-only inspection and control-node SSH against an existing four-node deployment. - CI and packaging: passed local
make check, source CI, and the release packaging workflow, producing four platform archives,amd64/arm64DEB and RPM packages, SPDX SBOMs, a Homebrew formula,install.sh, SHA-256 checksums, and release metadata. - Image catalog: embeds signed catalog revision
2026082903(9 families, 27 architecture artifacts), withhttps://repo.pigsty.cc/barnas the default repository in 0.2.0 andhttps://repo.pgsty.com/barnverified as an alternate endpoint.
Install or upgrade
Download install.sh and the release assets from the Barn 0.2.0 GitHub Release:
0.1.0 deployment state remains directly readable; running barn status while existing VMs are up records any missing process birth timestamps, and existing guests adopt the new /etc/hosts marker the next time they are recreated.
1.15 - RC6 development candidate: owner-scoped Tier-1 delivery
Pre-Barn historical archive (Piglet 1.0.0-rc.6, 2026-08-25). This candidate was a local verification milestone and was never published as a GitHub Release or package repository; the SHA-256 digests below record the retained local build artifacts. For current Barn usage and support status, see the documentation and status page.
Piglet 1.0.0-rc.6 was a local development candidate that validated the Quick and four-node full profiles on both Tier-1 native hypervisor platforms (macOS arm64 / HVF and Linux amd64 / KVM) and introduced journaled host-network transactions for crash recovery.
Frozen build identity
| Field | RC6 value |
|---|---|
| Version | 1.0.0-rc.6 |
| Source commit | 7db733184463cc189ffa738335c213fc9a2982de |
| Go | go1.27.0 |
| Source epoch | 1787657920 |
| Build timestamp | 2026-08-25T11:38:40Z |
| Channel | development |
| Signature / attestation | false / false |
Exact full profile SHA-256 |
912fea61bf1602c2a437570561a6ab4d0a5a8c152695147ad9e96ae831bc9336 |
Four native product scenarios
| Host | Scenario | Result | Guest contract |
|---|---|---|---|
macOS arm64 / HVF |
Quick | PASS | Ubuntu 24.04.4, dba UID/GID 88, 2 vCPU, 64 GiB root, 64 GiB /data |
Linux amd64 / KVM |
Quick | PASS | Ubuntu 24.04.4, dba UID/GID 88, 2 vCPU, 64 GiB root, 64 GiB /data |
macOS arm64 / HVF |
Exact full |
PASS | .10–.13, UID 88, 64 GiB roots, 128 GiB data disks, 2/1/1/1 vCPU, 4-way peer mesh |
Linux amd64 / KVM |
Exact full |
PASS | .10–.13, UID 88, 64 GiB roots, 128 GiB data disks, 2/1/1/1 vCPU, 4-way peer mesh |
All eight full-profile guests booted Ubuntu 24.04.4, passed contract assertions, and were cleanly destroyed afterward while retaining shared image caches, project markers, and keys.
Journaled host-network transactions
Network install and uninstall use a root-owned, strict-prefix transaction:
- Root planning produces a token bound to the invoking owner, host identity, and observed prestate.
- Apply re-evaluates the plan under the host-global lock and accepts only that token.
- A typed,
fsync-committed journal is written before any host mutation begins. - Each step records a checkpoint only after verifying its postcondition.
- If a transaction is interrupted, ordinary network changes are blocked until an explicit forward or rollback recovery plan is approved.
macOS completed an interrupted forward-recovery test and converged to a healthy default host network. Linux completed install, rollback/retry, four-node operation, uninstall, and host restoration prior to a final patch that tightened socket-state canonicalization, systemd unit enablement scope, NetworkManager ordering, and recovery field scoping (verified via source checks and -race tests).
macOS shared mode and subnet conflicts
The default macOS mode is socket_vmnet host mode; shared mode also passed a two-node test on an alternate free subnet as an explicit opt-in fallback.
During testing, the default subnet encountered VMNET_SHARING_SERVICE_BUSY (1009) when an older Piglet-created bridge100 interface was still present. Preflight checks interface ownership markers rather than adopting an existing interface just because its .1/24 address matches. When a subnet conflict occurs, operators can either remove the conflicting interface or move the entire lab to a clean RFC 1918 /24 subnet—the runtime never renumbers individual guests or picks a random subnet on its own.
Reproducible local archives
Two independent local RC6 build trees produced byte-identical archives, checksums, release metadata, and Homebrew formula files:
| Archive | SHA-256 |
|---|---|
Darwin amd64 |
1295288ff198b53fcb761a6e8794087d75a46105fc19980fabcd44f0c70fb9ef |
Darwin arm64 |
308310b0f2d179f98c8be78ea09f89d226167072c936387bc42d717a922ec0c6 |
Linux amd64 |
547a61f6cc0768071df349cbf5c17d7ddfe3ab3cea59e6b4026e3e3e616a5550 |
Linux arm64 |
afc9ce1043d377cc3b4ef8bdf77826a8139a8c839685025853e8bee8100c6a2e |
Remaining work before public release
At the close of RC6, the remaining items before public release were:
- Physical host-reboot persistence checks on both Tier-1 platforms
- Native smoke tests on macOS
amd64and Linuxarm64 - Re-running the Linux network install/uninstall/reinstall cycle with the final post-audit patch
- Guest image normalization, hosting, and active/standby manifest signing keys
- Public release tags, Homebrew tap and DEB/RPM package publication, and clean-host installer verification
See the tutorial, architecture guide, and status page for how these items were completed in subsequent Barn releases.
1.16 - Development snapshot: the Go 1.27 product surface
Pre-Barn historical archive (Piglet, 2026-08-24). At this snapshot there was no public release tag, signed production artifact, or package repository, and all embedded image records were marked testing. For current Barn behavior and support status, see the documentation and status page.
The 2026-08-24 source snapshot turned the initial QEMU prototype into a structured Go 1.27 VM manager with zero-configuration Quick mode, fixed-IP private labs, 13 built-in profiles, Pigsty inventory generation, persistent data disks, schema migration, and reproducible release packaging.
One native runtime
Piglet drives QEMU directly through HVF on macOS and KVM on Linux, running QEMU as the invoking user without falling back to TCG emulation or accepting arbitrary pass-through QEMU flags. It validates image digests and .qcow2 headers, generates pure-Go NoCloud cidata seed images, checks QMP and process identity, verifies SSH readiness, and records atomic state updates, journals, and event logs.
Quick mode on both Tier-1 hosts
Zero-YAML Quick mode runs on macOS arm64 and Linux amd64, provisioning a single meta guest (dba user, 2 vCPU, 4 GiB RAM, 64 GiB root disk, sparse 64 GiB /data disk, user-mode NAT, SSH, and four localhost port forwards) and supporting plan, up, status, ssh/exec, stop/start/restart, drift detection, guarded recreate/destroy, --no-wait, persistent disk retention, key rotation, logs, repair, and redacted debug bundles.
Private labs and host networking
Private mode adds host-network preflight, status, install, and uninstall across both Tier-1 host families:
- macOS uses pinned
socket_vmnetv1.2.2 inhostmode by default (withshared/FD mode also tested). - Linux uses a reversible
systemd-networkd/ NetworkManager /qemu-bridge-helpertransaction.
Both platforms keep QEMU unprivileged and block network uninstallation while a lab lease is active. Operators can replace the default 10.10.10.0/24 subnet with another explicit RFC 1918 /24 subnet, moving the profile, host network, lease, node addresses, and Pigsty inventory together.
Four-node full and MinIO runs on both Tier-1 platforms verified fixed IPs, control-node lateral SSH, management internet access, disk identity, stop/start persistence, crash recovery, 30/30 soak cycles, and 16-disk MinIO storage layouts.
Schema-3 profiles and Pigsty inventory integration
The 13 embedded schema-3 profiles define 85 nodes using the dba account—standard nodes carry a 128 GiB /data disk, while MinIO nodes carry four 32 GiB disks. piglet pigsty inventory reads a Pigsty source tree, validates host/VIP/admin/service addresses, rebases custom subnets, and writes mode-0600 inventory files with ownership markers, while pigsty-vm exposes the same lifecycle through typed environment variables.
Persistence, schema upgrades, and automation
- Persistent disks: disks marked
persistent: truesurvive ordinarydestroyand compatiblerecreateruns; deleting them requires typing a confirmation phrase in a TTY or passingdestroy --force --delete-persistent --yes-delete-persistent. Project key cleanup defaults to dry-run and requiresproject purge-keys --yes. - State migration: schema
0 -> 1upgrades require stopped nodes and no active lease, creating a backup before applying an atomic update viaproject upgrade-state --dry-run|--yes. - Automation surface: includes node selectors,
--no-wait, structured JSON responses, typed exit codes, shell completion, managed SSH and/etc/hostsblocks, per-node logs, and redacted support bundles.
Images and release packaging
The embedded image matrix covers el9, el10, d12, d13, u22, u24, and u26 across amd64 and arm64 (7/7 booted per architecture; el8 was excluded from the initial matrix before being normalized in later Barn releases). The build toolchain produces and verifies four platform archives, a Homebrew formula, Linux amd64/arm64 DEB and RPM packages, SHA-256 checksums, SPDX SBOMs, and Cosign/SLSA signing test fixtures across two-run reproducibility checks.
Follow-up completed in RC6 and Barn 0.x
Because catalog schema-3 and inventory changes landed after the initial full/MinIO runs, exact-profile native replays, custom-subnet /etc/hosts publishing, and storage.data_root / ssh.wait_timeout wiring were completed the following day in the 2026-08-25 RC6 candidate, before the project simplified its model around a single Pigsty-native barn.yml inventory in Barn 0.1.0+. See the tutorial, architecture guide, and status page for current Barn behavior.
2 - Design Notes
These essays explain Barn’s state model, guest convergence, image trust, host networking, CLI contracts, and native Mac lifecycle. Design proposals also examine the Pigsty/Vagrant development loop, Windows hosting, and future guest architectures; each proposal states its implementation and validation status.
The native macOS Virtualization.framework subsystem (barn mac) was introduced in Barn 0.9.0; its signed app has shipped in Apple Silicon archives since 0.9.2. See Native macOS VMs, the barn mac Reference, and the Mac release handbook.
2.1 - Main and command reference review
Reviewed on 2026-11-11. Both repositories were on main, but neither had a clean working tree or matched its fetched remote head. The source checks passed on the reviewed working tree. The documentation now has a complete command reference with explicit stable, local-main, and uncommitted-feature labels.
Git and release state
| Repository | Local main | Fetched origin/main | State at review entry |
|---|---|---|---|
pgsty/barn |
6ab0cd5 |
b2cc8da (v1.1.0) |
Ahead 3; 11 tracked paths changed, plus .idea/, barn.yml, and cmd/barn/hosts_inventory_test.go untracked |
pgsty/barn.pgsty.com |
bc7968a |
d392cd5 |
Ahead 3; four reference files modified and six design-article files untracked |
GitHub’s latest regular release was v1.1.0, published on 2026-11-09. No v1.2.0 tag was present on the remote. The local 1.2.0 notes were marked as a draft.
The three source commits add configurable Linux lifecycle concurrency (158786a), unified specification inputs and recoverable root growth (1e24768), and the 1.2.0 toolchain/release preparation (6ab0cd5). The vm_hosts parser and hosts install -f CLI extension remained uncommitted. A build of that working tree therefore exposes an interface that a checkout of the remote main or even the local HEAD commit does not contain.
Existing edits and deletions were retained. This review changed documentation without creating commits, pushing branches, or deploying the site. Before bringing main to a clean, shared state, review and commit the intended hosts implementation together with its tests, decide the intended disposition of the pre-existing untracked files and two deleted historical root documents, and reconcile the three local commits with the remote. Source and site changes need separate commits and publication decisions.
Findings and corrections
| Finding | Correction |
|---|---|
| The reference summarized commands but lacked complete per-command usage | Added 57 operation entries across seven command families, each with syntax, all local options, examples, and operation scope; root/help and groups are covered by the index |
| Stable 1.1.0, local main, and uncommitted hosts features were easy to confuse | Added a dated version baseline and option-level availability; clarified configuration, project status, source builds, and Linux operations |
| README described 2 vCPUs/4 GiB as the default without distinguishing development | Kept that released default and documented local main’s 1 vCPU/2 GiB default |
| Development articles linked new files under remote main before those files were pushed | Replaced those unavailable links with the reviewed local commit and repository-relative source/test paths |
| Contributor instructions said Ubuntu images retained the upstream bytes | Corrected them against catalog provenance: Ubuntu 22.04/24.04/26.04 are offline-normalized with dba, UID/GID 88, locales, and verified upstream XFS tools; they use the direct pipeline outside the eight-image wrapper matrix |
| A Mac command summary implied all configuration changes required stopping | Clarified that clipboard configuration can be saved while running and takes effect at the next start |
The existing image reference’s nine families, 17 artifacts, exact stable versions, and aliases agree with internal/image/default-catalog.json. Catalog source_user and provenance, rather than stale prose, were used to distinguish normalized images from unmodified distribution images. The command reference also retains the custom HTTPS/local unsigned-catalog policy, scoped aliases, explicit remote -- separators, and the different confirmation rules for purge, destroy, and Mac commands.
Implementation review
The reviewed changes include specification precedence and type validation (cmd/barn/vm_inputs.go, internal/config/inventory.go, internal/config/resources.go), concurrency limits (internal/private/concurrency.go), and root-growth recovery (internal/private/root_resize.go, its guest helper, and internal/state/root_resize.go). Root growth verifies the managed root path/serial and proven QEMU process death before image mutation, persists recovery stages, checks guest partition/filesystem identity and capacity, and retains the original power-state intent for retry.
For the uncommitted hosts extension, the review checked inventory-only mappings, case-insensitive conflict detection, subnet constraints, preview without deployment state, and preservation of an explicit inventory in follow-up commands. Relevant regressions are in cmd/barn/hosts_inventory_test.go, internal/config/inventory_test.go, and internal/hostconfig/hostconfig_test.go. These files must travel with the corresponding implementation when it is committed.
Validation evidence
-
Source:
make checkpassed on the current source working tree: module/shell/maintenance checks, unit and race tests, vet, Staticcheck, dead-code intersection, errcheck, vulnerability scanning, four-target cross-builds, image-pipeline boundaries, installer tests, Mac payload tests, and licenses. The scanner reported no reachable vulnerabilities, plus one vulnerability at the unused module-package level. The native offline image-mutation test was explicitly skipped by the normal boundary suite. -
Image repositories:
make catalog-checkactivated both official signed catalogs in fresh temporary roots, matched embedded revision2026110502byte-for-byte, and checked availability/size for all 17 stable artifacts on each mirror. The catalog SHA-256 wasd41e46d20bc57cb1b071e3919a27a8f7f703df2addca879970a528e7ab004065. Artifact payloads were not streamed for full digest recomputation (verified_bytes: false). -
CLI coverage: Separately built v1.1.0 and working-tree CLIs supplied the real help output. Each language covers all 57 operation commands and 174 visible local-option rows; stable-only rows were compared with the release binary’s help. Paired heading IDs, syntax blocks, and command examples match.
-
Examples: All four generated templates passed
init,validate, andplanwith both builds in isolated temporaryBARN_HOMEdirectories. Defaults were checked numerically, as were development scaling and root-size inputs. Five complete inventory examples from the guide/reference pages passed validation with the applicable versions. -
Site: Production
make checkpassed, including internal links, fragments, and assets; the only warning was the expected upstream LibSass deprecation. The built-in browser control timed out, so visual page inspection was not completed.
No live VM lifecycle, host-network mutation, or privileged hosts installation was executed for this documentation review. Native hypervisor acceptance and a signed release-package run remain separate checks when delivering runtime changes or a formal release.
2.2 - Why Barn Has No Projects
This essay explains the Linux lab deployment model in Barn 1.1.0 (macOS guests are managed as independent named VMs).
Early prototypes of Barn treated every working directory as a separate project: a hidden directory marker identified the project, a central registry tracked active project paths, and a host-wide lease table arbitrated private subnet collisions. While that model made sense for a generic multi-project VM runner, it fought against Barn’s actual job: running a local Pigsty database lab with fixed IP addresses.
Because a Pigsty inventory already defines every node’s IP address and role, layering a per-directory project identity on top created failure modes that had nothing to do with database testing:
- Which file is authoritative for a node’s name and IP—the VM project file or
pigsty.yml? - Does moving or renaming a git checkout move the existing lab or spawn a second one?
- How should Barn recover when the directory marker survives but the central registry is lost (or vice versa)?
- Does a missing project directory mean the user deleted the project, or that an external drive is currently unmounted?
- Which project owns the single host-level
/24bridge?
A single local Pigsty lab needs none of that machinery, so we removed the project layer entirely.
For current inventory discovery order, field rules, and defaults, see the Configuration Reference.
One inventory is enough
A Pigsty inventory (barn.yml or pigsty.yml) serves directly as Barn’s desired-state configuration. Barn reads the host IPv4 keys (plus hostname or IPv6 keys on vm_skip: true external hosts), a small set of Pigsty identity fields (nodename, admin_ip, pg_cluster, pg_seq, node_admin_username, node_admin_uid), and the vm_* variable namespace.
Barn validates vm_* fields strictly and ignores upper-layer Pigsty variables (pg_role, pg_version, repo_*, node_packages, and so on). That boundary gives you the best of both worlds: a typo in vm_mem fails immediately during barn validate, while PostgreSQL and Ansible tuning parameters evolve freely in the same YAML file without triggering VM drift.
Node naming follows the same principle: Barn uses nodename when set, otherwise derives <pg_cluster>-<pg_seq>, and falls back to node-<last-octet>. There is no separate vm_name field that could drift out of sync with Pigsty’s hostname.
One owner-scoped state root
All applied deployment state lives in BARN_HOME (default ~/.barn; relative and symlinked directory paths are accepted, and an absolute BARN_HOME reaches the same deployment across directories). There are no hidden markers in your working directory and no path-based project registries. Commands that inspect or operate on applied state (barn status, barn ssh, barn stop, barn start, barn destroy) work identically from any directory; commands that read desired state (barn validate, barn plan, barn up, barn reload, barn resize, barn recreate) take -f or discover barn.yml / pigsty.yml in the current directory.
Because BARN_HOME belongs to your Unix user account, the single-deployment model is scoped per user on a development workstation:
- Moving or renaming your working directory never orphans running VMs or creates a duplicate lab.
- Accidentally deleting
barn.ymlleaves the applied state in~/.barnintact (barn planandbarn upcan still fall back to the committed specification). - Image caches, SSH keys, node disks, locks, and state files live under one predictable root (while short Unix socket paths,
~/.ssh/barn_config, and host network configurations remain in their standard locations; see Uninstall).
Missing configuration never implies deletion
Barn separates desired configuration from committed state. While modifying a lab’s topology requires an inventory file, a missing file or an omitted YAML group never authorizes Barn to delete existing VMs. Planning and destructive actions are always explicit, and recovery preserves any resource whose identity cannot be verified unambiguously.
A deliberate trade-off
Barn intentionally manages one Linux lab per BARN_HOME rather than multiplexing concurrent projects, registries, and subnet leases. For local database and infrastructure development, one inventory driving one deployment keeps IP addressing, ownership verification, drift detection, crash recovery, and teardown straightforward.
Read next: Why Every Barn Node Has Two NICs.
2.3 - Why Every Barn Node Has Two NICs
This essay explains the Linux lab network architecture in Barn 1.1.0 (macOS guests use independent per-VM vmnet NAT subnets).
A Pigsty inventory identifies every database node by a stable IPv4 address: PostgreSQL streaming replication, Patroni/etcd consensus, HAProxy backends, keepalived VIPs, Prometheus targets, and Ansible playbooks all expect 10.10.10.11 to name the same host across boots. Forwarding a loopback port on the host can expose SSH or port 5432 to your laptop, but peer VMs cannot form a cluster over host loopback forwards.
At the same time, freshly booted cloud images need effortless outbound internet access to download packages without requiring you to maintain custom host NAT/MASQUERADE firewall rules. Barn solves both problems cleanly by giving every Linux node two virtual network interfaces (NICs).
Two interfaces, two distinct jobs
| Interface | Addressing | Responsibility |
|---|---|---|
| Management NIC | QEMU user-mode NAT (slirp), DHCP |
Outbound internet access, DNS, default route, and host-loopback SSH port forwarding |
| Fixed-IP lab NIC (MAC-matched) | Static RFC1918 /24 inventory IP |
Host-to-node, node-to-node, Ansible, Pigsty services, and L2 VIP traffic |
Barn binds each interface inside the guest by its deterministic MAC address rather than relying on kernel interface names (eth0, enp0s4, etc., which vary across distributions):
- The management NIC gives a new VM immediate outbound internet access, DNS resolution, and a host-loopback SSH forward (
127.0.0.1:<port>) with zero host firewall configuration. - The fixed-IP lab NIC configures the exact IPv4 address from
barn.ymlwith no default gateway and no DNS servers. Duringbarn up, guest bootstrap verifies this interface; if the fixed-IP link has an issue while management SSH is healthy, Barn marks the node ready with aprivate-networkwarning innodes[].warningsso you can inspect the guest and re-runbarn upafter fixing the host bridge or route conflict.
Dual-NIC networking is built into every Barn Linux VM. See Design for an overview of the Linux and macOS runtime architectures.
The inventory owns the /24 address plan
All managed nodes in a deployment share one canonical RFC1918 /24 subnet:
| Address range | Allocation |
|---|---|
.1 |
Host gateway interface on the private lab subnet |
.2–.8 |
Reserved range (available for L2 VIPs; also bounds macOS vmnet DHCP) |
.9–.254 |
Static node addresses declared in barn.yml |
Because cloud-init writes the static inventory address directly from barn.yml (on macOS, socket_vmnet’s DHCP pool ends at .8; on Linux, the barn0 bridge uses static guest addressing), node IPs never depend on a DHCP lease database. Ansible and Pigsty use the exact same IP from the first barn up through every subsequent reboot.
When generating a fresh default template on a host where 10.10.10.0/24 is already in use, barn setup can automatically pick another free RFC1918 /24; explicit -f inventories and edited configurations always keep their declared subnet.
Platform-native backends, identical guest topology
Inside the guest, every distribution sees the exact same two-NIC layout. On the host, Barn integrates with the platform’s native network manager so QEMU always runs as an unprivileged user process:
- macOS (
socket_vmnet): Runs a pinnedsocket_vmnetlaunchd service (hostmode by default, orsharedmode with--mode shared) while QEMU runs under your normal user account. - Linux with active NetworkManager: Creates and manages the
barn0bridge vianmcliand integrates with activefirewalldzones. - Linux with
systemd-networkd: Installs dedicatedbarn0networkd units and uses the distribution’sqemu-bridge-helperfor unprivileged tap attachment. - Linux with inactive
systemd-networkd: Before starting an inactivesystemd-networkdservice, Barn scans existing.networkunits on the host and proceeds only if none of them could claim a physical host interface—preventing a lab setup from disrupting workstation or server networking.
Host networking as a reversible transaction
Because a host bridge or socket_vmnet service persists across CLI runs and requires sudo to install, Barn executes host network setup as a six-step reversible transaction:
- Inspect host state — Checks routing tables, existing interfaces, active network services, file ownership, and prior Barn state.
- Display the plan — Prints the exact files, interfaces, and service changes before requesting privileges.
- Re-verify preconditions — Re-checks for route or interface collisions immediately before applying changes.
- Record root-owned rollback state — Writes
/etc/barn/network.json(or platform equivalent) recording both what Barn created and the prior host state. - Smoke-test unprivileged QEMU attachment — Verifies that an unprivileged QEMU process can attach to the bridge/socket before committing (rolling back automatically if the test fails).
- Commit after readiness — Marks the network installation active only after all interface and service checks pass.
Barn never hijacks an existing non-Barn interface that happens to use .1/24, never overwrites modified files outside its recorded digest, and refuses network uninstall while lab VMs are running.
Recheck shared service ownership
The installation snapshot is not permanent authority over a host-wide service. Uninstall checks current links as well as files: a running link may still depend on systemd-networkd after its .network file has been removed. When other non-loopback links exist or exclusive Barn use cannot be established, uninstall removes Barn-owned resources while preserving the global unit state. The plan exposes preserve_networkd; the applied report explains it in checks.networkd. An administrator can later change the retained unit state after checking the other links. See the hardening record.
Why this split works
Routing outbound internet traffic through QEMU’s user-mode stack trades raw WAN throughput for zero-configuration portability, while keeping all latency-sensitive traffic—host-to-guest psql/SSH, PostgreSQL replication, etcd heartbeats, and local package mirror pulls from the meta node—on the fast, fixed-IP virtual bridge.
Read next: Declarative Does Not Mean Destructive.
2.4 - Declarative Does Not Mean Destructive
This essay explains the Linux VM convergence model in Barn 1.1.0 (macOS guests are managed independently).
A declarative tool compares live state against a configuration file—but in daily development, that file might be temporarily edited for debugging, checked out on another git branch, or missing a YAML group after a merge. If a local VM manager treats an omitted YAML block as permission to destroy a virtual machine, a single typo can wipe out a database node.
Barn enforces a strict asymmetry between creation and destruction:
Desired state authorizes creation and describes drift, but never authorizes destruction by omission.
Root overlays, data disks, SSH host identities, and local databases hold state that takes time to rebuild. When a change requires replacing or deleting a node, the operator must invoke that action explicitly.
From Pigsty inventory to per-node spec hash
When reading barn.yml, Barn extracts the fields in its contract, fills defaults, and normalizes image selectors and architecture into a resolved specification. Exact catalog image digests are resolved separately so that running barn update does not silently invalidate every node’s specification hash. Each node’s hash combines two inputs:
- The deployment envelope shared by all nodes (the
/24subnet, login username,vm_archpolicy, and deployment-level default image selector). - That node’s resolved definition (
nodename, IP, vCPUs, memory, root disk,vm_disks,vm_shares, andvm_alias).
Adding a second node leaves the first node’s hash untouched, and editing upper-layer Pigsty variables (pg_version, node_packages, pg_role) produces zero VM drift.
barn up converges additions and stopped nodes automatically. CPU and memory changes use barn resize; other specification changes and node removals use explicit barn recreate and barn destroy commands. See Daily Operations for the command workflow.
Six explicit barn plan outcomes
barn plan compares the desired inventory against committed deployment and node state and classifies every node into one of six outcomes:
| Outcome | Meaning | Resolution command |
|---|---|---|
create |
Node exists in the inventory with no committed VM state | barn up creates and boots the node |
unchanged |
Resolved definition and runtime policy match committed state | barn up keeps running nodes untouched and starts stopped nodes |
resize |
Only the node’s vm_cpu or vm_mem changed |
barn resize <node> hot-plugs supported increases, otherwise stops and starts the same VM |
recreate |
Node’s image, disk, share, or other hardware definition changed | barn recreate <node> (or --yes in scripts) |
missing |
Committed node is absent or marked vm_skip: true in the inventory |
barn destroy <node>, or restore the node to barn.yml |
| Envelope drift | Deployment subnet, login user, vm_arch, or runtime policy changed |
Whole-deployment barn recreate |
barn plan is completely read-only and prints the exact command needed to resolve any resize, recreate, or missing drift. When you pass explicit node selectors to plan, up, reload, or recreate, Barn scopes the operation to those nodes and retains unselected peers’ applied definitions (see selected-node operations).
Why barn up stops on node drift
Rather than resizing a running VM or silently rebuilding a root disk when vm_image changes, barn up stops with a conflict error whenever an existing node’s specification has drifted. CPU and memory changes go through barn resize <node>, which keeps the VM identity and disks. Every other change requires an explicit barn recreate <node>, which gives you two guarantees:
- Every change that replaces a guest’s root filesystem goes through one visible, confirmed command.
- Barn validates all prerequisites—target QEMU emulator, accelerator policy, UEFI firmware, cached or downloadable image bytes, network backend, shared directory paths, and persistent-disk compatibility—before stopping or deleting the existing VM. If a foreign-architecture emulator is missing or a share path is invalid, the running VM stays untouched.
Why missing nodes block barn up
A node can vanish from barn.yml for plenty of routine reasons that do not mean “delete this database server”:
- You passed a smaller test inventory while debugging a single node.
- You renamed a group or toggled
vm_skip: trueon a host entry. - A branch switch or merge conflict dropped a host stanza.
- You ran a command from a directory with a different
barn.yml.
When committed state contains a node that is absent from the desired inventory, barn up and whole-deployment barn recreate halt before stopping any VM (conflict, reason nodes_removed, exit 4) and name the missing node so you can either restore it to barn.yml or run barn destroy <node> explicitly.
When you do destroy or recreate nodes, data-disk lifetimes follow clear boundaries:
- Ordinary
barn destroyandbarn recreatepreservepersistent: truedata disks on disk. - Whole-deployment
barn destroy --delete-persistentremoves persistent data disks as well, andbarn purge(orbarn destroy --purge) removes all disks, SSH keys, and deployment state in one step. - During
barn uprecovery, Barn formats a data disk that carries no signature and resets an ext4 or XFS filesystem only when the checker reports recognized corruption; a partition table, unsupported features, or an unknown checker failure keeps the data and reports a warning (see Data Disks).
Distinguish role changes from drift
Deleting the control node elects the first remaining node. SpecHash describes its current applied definition; GuestSpecHash describes the guest’s existing readiness marker. A control-role change can therefore make these hashes differ while the VM identity and disks remain valid.
Before marking any node for deletion, Barn must finish the successor’s committed prepare journal. It then atomically publishes the deployment as the role-change commit record and updates the successor’s definition hash. If interrupted between those writes, recovery under the deployment lock accepts only a hash difference exactly explained by flipping Control; unrelated drift still fails. It does not create missing state, rotate the VM UUID, or change disk/process identity. See the hardening record and control recovery tests.
Update the inventory’s control role before the next barn up installs control SSH access. General live role migration would also need old-credential revocation, offline retries, and partial-failure semantics; accepting arbitrary hash changes would conceal that unfinished convergence.
Require evidence of corruption
Filesystem-checker exit codes alone cannot authorize formatting. ext4 codes 4/12 and XFS code 1 must accompany recognized corruption diagnostics. Unsupported features, permissions, I/O or memory failures, partition tables, and unknown diagnostics preserve the disk and produce a guest warning. persistent controls disk retention during destroy/recreate; it does not exempt recognized corruption from the documented reset policy.
The disk recovery tests check whether mkfs actually ran using a sentinel, while physical-host acceptance compares disk contents. This tests the destructive effect as well as the warning text; the dated coverage is in validation history.
Incremental convergence without collateral damage
Because convergence is evaluated per node, barn up scales a 1-node lab out to 4 nodes without restarting the running control node, and barn recreate node-2 replaces node-2 while leaving meta and node-1 online and reattaching node-2’s compatible persistent data disks.
Read next: A PID Is Not a Virtual Machine.
2.5 - A PID Is Not a Virtual Machine
This essay explains Linux runtime process identity and crash recovery in Barn 1.1.0. The separate native Mac lifecycle uses per-machine operation locks and runner protocol 2.
A pidfile records a process ID at the moment it was written. Minutes or days later—especially after a crash or host reboot—that integer alone cannot tell you whether the original QEMU process is still running, whether the OS kernel recycled the PID for an unrelated user process, or which VM node that process belongs to.
Trusting a bare pidfile is how naive process managers end up sending SIGKILL to an unrelated process or deleting a disk that is still attached to a live VM. Barn never authorizes SIGTERM, SIGKILL, or disk deletion from a PID alone; destructive actions proceed only when independent identity checks agree.
QMP is the primary runtime identity
Every Barn Linux VM is assigned a generated UUID and an expected QEMU instance name (barn-<node>). Immediately after spawning QEMU, Barn connects to the node’s QEMU Machine Protocol (QMP) Unix socket and queries both values. A node launch succeeds only after QMP responds with the exact expected name and UUID.
Barn repeats that QMP handshake before every shutdown or status transition:
- Barn queries QMP for the live instance name and UUID; if QMP returns a different identity, Barn aborts without sending any command to that socket.
- To stop a verified VM, Barn first sends a clean guest ACPI
system_powerdownover QMP, then sends QMPquitif the shutdown timeout expires, and falls back to OS signals only when QMP itself is unreachable.
When a command reports an interrupted transition or identity mismatch, start with Troubleshooting rather than deleting files in ~/.barn by hand.
Process four-tuples close the fallback gap
If QEMU crashes, the runtime socket directory is cleared, or a shutdown is interrupted mid-flight, the QMP socket may no longer respond. To handle signal fallback safely without QMP, Barn records a four-element process identity tuple alongside the full typed QEMU invocation when the VM starts:
- Process ID (
PID) - Resolved executable path
- OS process start timestamp
- SHA-256 digest of the observed command-line arguments (
argv)
Before sending SIGTERM, Barn inspects the live OS process table and requires all four fields to match the recorded tuple. If the process does not exit within the SIGTERM grace window, Barn re-reads the process tuple a second time immediately before sending SIGKILL—closing the race window where the original process exits and the kernel reuses its PID during the wait.
If QMP is unreachable and the OS process table shows that the recorded PID now belongs to an unrelated process (or no longer exists), Barn marks the VM stopped without signaling that PID. Conversely, if QMP is still responsive or if the live process tuple cannot be read unambiguously, Barn refuses to send blind signals and stops with a diagnostic error.
Prepare journals track work before state is committed
Creating a VM requires preparing disk overlays and cloud-init seed media on disk before the QEMU process even starts. If the CLI is killed (SIGINT/SIGKILL) or crashes mid-preparation, those files exist on disk before a committed node state record is written.
To recover cleanly without guessing from directory listings, Barn writes a 0600 versioned JSON prepare journal before creating any node file:
- Operation UUID and target VM UUID
- Node name and resolved specification hash
- An ordered log of each created artifact from a strict
(kind, path)allowlist - The typed QEMU invocation once disk preparation finishes
- The committed node-state file path once the transaction completes
During recovery, Barn validates the journal schema, UUIDs, file modes, symlink absence, and directory containment, then rolls back only the exact uncommitted files recorded in the journal.
Narrow rollback and fail-closed teardown
Offline rollback of an uncommitted node runs only when three preconditions hold simultaneously:
- No committed node state file exists for that node.
- Neither the QMP socket nor the
pidfilefrom the journal’s QEMU invocation is present. - Every file in the node directory belongs to the journal and its allowlisted artifact set.
When all three hold, Barn removes the uncommitted artifacts in reverse creation order, deletes the journal, and removes the empty node directory. This lets you edit barn.yml after a failed first barn up and re-run barn up cleanly, while guaranteeing that a stale journal can never roll back an already committed node or delete pre-existing persistent disks.
Ordinary barn destroy applies the same discipline: it verifies path containment, file ownership, process termination, and the exact known artifact list before unlinking files, and removes the node directory only when it is empty (any unexpected file halts directory removal with an error).
Atomic state updates
Every state update writes to a sibling temporary file in the same directory, calls fsync, performs an atomic rename, and fsyncs the parent directory while holding the deployment and node locks. After any crash, on-disk state reflects either the prior committed state or the new committed state, with the prepare journal covering the bounded window in between. Likewise, before an online barn resize hot-plugs CPU or memory over QMP, Barn persists resize_incomplete: true in the node’s state record and clears it only after the QMP mutation and final state write succeed, so barn status (stage: "resize", reason: "resize_incomplete") and subsequent up, start, or resize runs detect and recover an interrupted resize cleanly.
Read next: repo.yaml Is Intent; catalog.json Is Evidence.
2.6 - repo.yaml Is Intent; catalog.json Is Evidence
This essay explains the Linux image repository contract in Barn 1.1.0 (macOS guests manage Apple .ipsw restore images and APFS base images separately via barn mac).
A static VM image repository needs two kinds of metadata: human policy decisions (which version is :stable, which old releases are deprecated, what aliases map to each family) and physical file facts (exact byte size, SHA-256 digest, qcow2 virtual disk size). Maintaining both in a single hand-edited JSON file invites copy-paste checksum errors and buries policy changes inside noisy diffs.
Barn splits those two concerns into two distinct files: repo.yaml for human intent and catalog.json for machine-verified evidence.
repo.yaml: human-authored policy
repo.yaml (schema: 1) is checked into version control and edited by maintainers. It declares:
- Repository
revisionand globaldefaults(image,channel,arch,boot) - Distribution families and their short aliases
- Movable release channels such as
stable - Exact version keys and supported architectures (
amd64,arm64) - Boot mode (
uefiorbios), supportstatus,source_user, and immutable upstream provenance URLs
Crucially, repo.yaml never contains hand-copied byte counts, SHA-256 digests, or virtual disk sizes:
Because repo.yaml stays compact and free of generated hashes, pull requests clearly show channel promotions, deprecations, and provenance updates (see Image Repositories for a complete walkthrough and Linux Images for the current 2026110502 catalog).
Repository authoring and client verification are covered in Linux Images; offline candidate normalization is covered in Image Pipeline.
catalog.json: machine-verified artifact evidence
catalog.json (schema: 3) is generated by barn repo build from repo.yaml and the actual .qcow2 files on disk. For every (image, version, arch) variant, barn repo build:
- Validates
repo.yamlpolicy declarations (boot mode,source_user, support status, and upstream provenance). - Inspects the corresponding
images/<image>-<version>-<arch>.qcow2file viaqemu-img, rejecting backing chains, external data files, encryption, or unknown incompatibleqcow2feature bits. - Computes the exact file size, SHA-256 digest, and
qcow2virtual capacity, and atomically writescatalog.json.
Each artifact is identified by the immutable tuple (image, exact version, architecture) and stored under a human-readable filename:
Predictable filenames make it easy to inspect, mirror, or rsync a repository with standard Unix tools, while catalog.json and its minisign signature provide cryptographic integrity verification.
Three repository commands, three distinct roles
| Command | Role |
|---|---|
barn repo scan /srv/barn |
Read-only audit: reports tracked, missing, untracked, and unsafe files without modifying the repository |
barn repo build /srv/barn |
Strictly validates repo.yaml and every declared .qcow2 file, then atomically writes catalog.json |
barn repo verify /srv/barn |
Rebuilds the catalog in memory and verifies that it matches the published catalog.json byte-for-byte |
barn repo build never modifies repo.yaml or .qcow2 files, and barn repo verify catches any drift between repo.yaml, the .qcow2 files on disk, and catalog.json. When publishing a repository update, always upload the immutable .qcow2 files first, then publish catalog.json and catalog.json.minisig together so clients never see a catalog entry whose image file is not yet available.
Movable selectors, immutable resolved identities
In barn.yml or CLI commands, selectors like d13:stable, el9@9, and el9@9.8 provide convenient moving targets (comparing dot-separated version components as integers so 9.10 sorts after 9.9).
Once barn plan or barn up resolves a selector for a node, Barn records the exact version, architecture, byte size, and SHA-256 digest in committed state. Updating a channel in a future catalog never mutates an already created VM behind your back.
Transport security and catalog signatures
Barn separates transport selection from catalog trust:
- Official and HTTP repositories always require a valid detached signature (
catalog.json.minisig) verified by one of Barn’s embedded production Ed25519 public keys. - Custom local directory and HTTPS repositories explicitly selected via
--repoorBARN_REPOmay use unsigned catalogs when you control the local filesystem or HTTPS endpoint. - Per-repository anti-rollback: Barn tracks the highest accepted catalog
revisionindependently for each repository root, rejecting older revisions (unless--allow-downgradeis passed tobarn image sync) and rejecting different catalog bytes at the same revision number. - Defense in depth on every pull: Even after a catalog is accepted, every downloaded or cached image is checked against its catalog byte size, SHA-256 digest, and
qcow2structural rules before being marked read-only (0444) and used as a backing image for copy-on-write node overlays.
Independent trust domains across releases, apps, and catalogs
Barn keeps its three distribution trust domains distinct:
- CLI release archives and Linux packages are published on GitHub Releases with
checksums.txtSHA-256 manifests and SPDX SBOMs. - Native macOS
Barn.appbundles use Apple Developer ID code signing and notarization (or local ad-hoc signing when built from source). - Linux image catalogs are authenticated via offline Ed25519 Minisign keys (
catalog.json.minisig).
In Barn 1.1.0, the compiled default repository is https://repo.pigsty.io/barn, --mirror selects https://repo.pigsty.cc/barn, and --repo selects any custom root. The two official mirrors can fall back to each other when downloading .qcow2 artifacts—always verifying the exact same catalog byte size and SHA-256 digest—while catalog updates (barn update) and custom --repo repositories always read from their explicitly selected source.
2.7 - From Upstream Images to a Signed Catalog
An image audit answers a narrower question than an image release. It can find a newer upstream file, but Barn may add that file to its signed catalog only after the artifact, runtime profile, and boot path have all been verified. The image reference describes the user-facing catalog; this essay records the reasoning that keeps catalog updates reproducible.
Four meanings of “new”
Image work becomes error-prone when four independent version values are treated as one:
| Value | What it describes | Evidence needed | Can it update the catalog by itself? |
|---|---|---|---|
| OS release | Vendor release or point version, such as Ubuntu 24.04 LTS |
Release metadata and package/build records | No |
| Cloud build | A dated upstream disk build, with a YYYYMMDD identifier |
The dated directory, file name, and checksum record | No; it is a candidate source |
| Barn release | Barn’s normalized version and revision suffix | The image recipe and catalog mapping | No; normalization must be reproducible |
| Runtime support | Whether Barn can boot and operate that artifact | QEMU profile, firmware, network, cloud-init, and SSH evidence | Required |
The suffix in a Barn version is not automatically an OS point version. Debian, Ubuntu, Rocky, and CentOS use different build identifiers, and Barn may add its own normalized revision. Keep those namespaces separate in release notes, source recipes, and catalog entries.
Compare like with like
An update comparison must use the same artifact class as the current catalog entry. A generic cloud disk can be compared with another generic cloud disk; an installer ISO, container archive, daily build, or board-specific image answers a different question. A release page’s newest timestamp is not enough to change a Barn source URL.
Use dated upstream URLs for reproducible inputs. Use a latest page to discover a candidate, then resolve it to the dated build, the exact artifact, and the checksum record. Record the checksum for the selected format: the digest for a qcow2 upload does not stand in for the digest of a raw disk or a compressed archive.
The audit should also keep OS metadata, cloud-image metadata, and Barn’s local pipeline metadata side by side. If the pipeline recipe is older than the embedded catalog, the safe conclusion is that the recipe needs review; replacing the catalog with the older recipe would move the product backwards.
Detect drift between recipes and the catalog
On 2026-11-08, the Debian 12 targets in official-v1.json still selected 20260923.2610.1, while embedded catalog revision 2026110502 selected 20261026.2643.1. The source build was 20261026-2643; the final .1 belonged to Barn’s normalized release. This is a reproducible example of local metadata drift, and a reason to check source recipes before rebuilding a published image.
Keep the upstream input digest separate from the normalized artifact digest. Guest customization changes the disk bytes, so the catalog must hash the final artifact. The existing catalog provenance records independent rebuilds with matching normalization markers but different artifact bytes; it makes no byte-for-byte reproducibility claim. A reproducible recipe and a bit-reproducible output are separate properties.
Architecture discovery needs the same precision. A distribution port or installer ISO is only one part of a future target’s evidence. Barn currently accepts native, amd64, and arm64; new image candidates need the runtime work described in guest architecture contracts.
A catalog entry is a runtime contract
For Barn, an image entry is valid only when these pieces agree:
| Contract part | What must be true |
|---|---|
| Artifact | The exact path, format, byte size, and digest identify one file |
| Guest architecture | The catalog architecture matches the requested guest architecture |
| QEMU profile | Barn selects the target system emulator, machine, CPU policy, and accelerator |
| Firmware | The image has a validated boot path; UEFI uses matching code and a per-VM variable store, while BIOS images use the BIOS path |
| Guest seed | Cloud-init or the image initialization path creates the expected user, keys, hostname, and network |
| Runtime test | A clean temporary BARN_HOME reaches the expected guest architecture and SSH state |
This is why image availability and Barn support are separate statements. The repository contract keeps human policy in repo.yaml and generated file facts in catalog.json; the runtime still needs a target emulator and a cold-boot check. A catalog entry that has a good checksum but cannot reach SSH is not a usable Barn image.
Release gate
Before a formal Barn release:
- Fetch the current signed
catalog.jsonfrom the official image repository and verify its signature. - Compare its revision, default image, and every stable artifact’s path, architecture, digest, and size with the embedded catalog. Update the embedded manifest version and date, and update
repo.yamlwhen the repository source changes. - Run
make catalog-checkto verify both official mirrors, require exact agreement with the embedded bytes, and check every stable artifact’s availability and size. Run the image catalog tests and executebarn updatewith a fresh temporaryBARN_HOME. Usego run ./tools/catalogcheck -verify-byteswhen all stable artifacts also need to be streamed and hashed. - Build and publish only after the catalog gate passes. A release must not carry an artifact that the official repository has removed.
The catalog checker enforces repository agreement. New or rebuilt images also need startup and SSH acceptance for each changed architecture before promotion; see the image pipeline. Never reuse a published catalog revision for different bytes.
Keep the decision with the evidence
The source repository owns the signed catalog, recipes, code, tests, and dated release evidence. The companion site owns the durable explanation of why the fields and gates exist. When an image decision changes, update the design page and the relevant reference or release record together; keep raw directory scrapes and discarded candidates out of the public source tree.
Read next: Image repository contract and Beyond amd64 and arm64.
2.8 - What Barn Can Learn from Vagrant
Barn already creates multi-node Linux labs from Pigsty-compatible inventories, manages their lifecycle, and exposes SSH and structured output. The useful lessons from Vagrant concern what happens between creating a VM and repeating an experiment: run setup steps, move source and build artifacts, record the environment, and return to a known state.
This is a design proposal based on the Barn source reviewed on 2026-11-08. The priorities below are candidates for implementation, with no assigned release or delivery date. Current commands are documented in the CLI reference.
Existing capabilities and actual gaps
| Workflow | Barn today | Design opportunity |
|---|---|---|
| Define a lab | Static inventory, inherited group variables, per-node images/resources/disks, node selection | Reusable recipes; group selection would need its own contract |
| Converge nodes | plan, up, explicit resize, recreate, and destroy |
Preserve the rule that an omitted node never authorizes deletion |
| Configure guests | Manually rerunnable Bash provision, with sudo, timeout, concurrency, and script digest |
Named steps and execution policies attached to the lifecycle |
| Move files | Virtio-9p host shares and OpenSSH connection export | Integrated upload/download and optional one-way rsync |
| Restore an experiment | Persistent disks survive ordinary destroy/recreate | Named cold snapshots covering all participating state |
| Reproduce images | Signed catalogs, exact releases, import, cache, and static repository tools | A portable environment record and capture of a configured VM |
| Reach services | Fixed private addresses and loopback management SSH | User-declared service forwards with explicit bind addresses |
barn provision already reruns a script on demand. Its digest records what ran; it does not make the script idempotent or skip an identical script. Inventory groups already supply variable inheritance, while operation selectors identify nodes. Persistent disks retain data across lifecycle operations but do not provide a point-in-time restore.
These distinctions follow the command definitions, inventory parser, and provision executor. Internal forwarding structures and functions named “snapshot” or “commit” should be evaluated by their exposed behavior before being counted as product features.
Keep the Pigsty integration small
Pigsty needs stable private node addresses, data disks, an administrator account, SSH access, and a way to deliver deployment code. Barn supplies the VM layer; Pigsty and Ansible continue to own database and service configuration.
The first integration should reuse barn ssh-config and preserve its endpoint, key, known-hosts file, and VM identity. A guest’s private IP and its host-side loopback SSH endpoint serve different purposes. Scripts using scp, rsync, or Ansible need the connection parameters as well as the inventory address. When replacing Vagrant-generated SSH fragments, define which tool owns each fragment so an older OpenSSH entry cannot take precedence.
A Vagrant box name also needs an explicit mapping to a Barn image reference. The formats carry different metadata. Multiple distribution families can share one Barn inventory, but the guest architecture is deployment-wide; architecture build matrices should run as separate lab jobs under the current single-deployment model.
This keeps the fixed-IP network and single deployment useful while avoiding a second service orchestrator.
Proposed implementation order
| Priority | Minimum scope | Acceptance condition |
|---|---|---|
| P0: named provisioning | Ordered shell steps, first-run/every-run policy, explicit selection and retry; external Ansible invocation | Retry one failed step without replaying successful steps; report node, step, exit code, and script digest |
| P0: environment record | Exact image release/digest/architecture, catalog revision, resolved VM specifications, and script digests | Rebuild the topology from retained artifacts in a fresh temporary BARN_HOME; image identities match and plan is clean |
| P1: file transfer | Upload/download over the existing SSH contract, then optional one-way rsync | Build on guest-local storage, retrieve artifacts, and verify their hashes |
| P1: cold snapshots | Start with a stopped Linux VM; define root/data disks, backing chains, NVRAM, specification, and identity together | Restore files, mounts, and guest identity to the saved state without changing unselected nodes |
| P1: service forwards | Explicit bind address and host/guest ports, conflict handling, structured reporting | Services remain reachable after restart; LAN exposure requires an explicit address |
| P1: VM capture | Stop, flatten backing dependencies, remove machine/SSH credentials, and emit qcow2 plus metadata | Import into a fresh lab and reach SSH with a new identity |
Vagrant’s named provisioning and run policies, built-in upload, and one-way rsync provide useful reference behavior. Its snapshots depend on provider support, and package documents VirtualBox and Hyper-V support. Barn needs explicit guarantees for its own backends.
An environment record must retain access to the exact image bytes. A numeric version prefix can resolve differently after a catalog update; even a complete version cannot recover a removed artifact. VM disks and exported images belong in artifact storage, while recipes, manifests, and digests can be committed.
Changes that need a separate product decision
Different BARN_HOME roots already separate stored state and runtime sockets. Complete concurrent-project support also needs network, port, and SSH ownership because those host integrations still share resources. More NICs or subnets expand the network validation matrix. Both are later candidates that should be driven by a concrete lab requirement. A Windows backend, a general provider/plugin API, and memory-state suspension each need independent design and maintenance commitments.
Evaluate the first additions against three workflows: a Pigsty multi-node PostgreSQL lab, a distribution/architecture extension-build matrix, and a single development VM with source upload and artifact retrieval. A successful change reduces manual steps in those workflows while preserving explicit destruction, verified image inputs, and stable machine-readable output.
2.9 - Replacing Vagrant with Barn in Pigsty: resources, names, and migration work
Pigsty can use one inventory for Barn and Ansible. The migration needs to preserve topology selection, image matrices, resource scaling, fixed addresses, and SSH workflows while replacing the old scripts’ implicit addressing, naming, and destruction behavior.
Status (2026-11-10): vm_scale, the 1-vCPU / 2-GiB defaults, vm_hosts, barn hosts install -f, and Pigsty’s bin/sandbox-dns are implemented in the local working trees and have passed the offline checks below. They are unreleased. Pigsty’s Makefile still uses Vagrant; the second half of this article describes the remaining work. Released Barn 1.1.0 actually defaults to 2 vCPU / 4 GiB when parsing an inventory and does not support these additions.
What the Vagrant scripts provide
| Entry point | Current design | Migration decision |
|---|---|---|
vagrant/config, vagrant/spec/*.rb |
Combine topology, distribution, scale, provider, and architecture into a Vagrantfile; accept scales of 1–64; preserve per-node images in build profiles | Move topology and resources into Pigsty inventory vm_* fields; retain image selection and scaling, with Barn handling platform backends |
Make aliases such as meta/full/simu |
Configure Pigsty, select a VM spec, and default to EL9; many targets implicitly depend on del |
Follow the earlier proposal to default the new Pigsty entry point to u26, with distribution suffixes; separate creation, recreation, and destruction and inspect configuration changes with plan |
vagrant/ssh |
Combine VM specs and vagrant ssh-config into host SSH configuration |
Use Barn’s automatic SSH integration, preserving keys, management ports, known-hosts files, and instance identity |
vagrant/dns |
Read node names from Ruby specs and assign a fixed list of .pigsty names to the first node |
Extract names from Pigsty inventory and role defaults; preview by default and publish explicitly |
| Provider templates | Disable the default /vagrant share, install an SSH public key, and configure networking and extra disks |
Let Barn initialize guests, users, networks, and disks; choose scp/rsync or vm_shares explicitly for source delivery |
new, del, nuke, suspend |
Recreate, destroy without confirmation, clean up, and suspend guest memory | Map lifecycle operations explicitly and retain destruction scope confirmation; stop/start does not restore guest memory |
bin/boot-pkg, Make copy/sync targets, bin/release |
Distribute packages through meta and distribution aliases; continue shipping Vagrant scripts |
Verify SSH aliases and directories, then migrate the release manifest as well as make up |
Old resource differences need not carry over: meta uses 2 vCPU / 4 GiB; full uses one 2-vCPU / 4-GiB node and three 1-vCPU / 2-GiB nodes; simu mixes several sizes. The new base is 1 vCPU / 2 GiB per node. Database, build, or application tests can raise their base resources or scale explicitly. These VM defaults do not replace module capacity requirements.
Prerequisites such as new: del up and meta9: cmeta del vmeta9 up ssh do not establish a reliable execution order. make -j can configure, destroy, and start concurrently. The new entry point should use a sequential recipe or script that stops at the first failure.
Sources: configuration generator, Vagrant directory, and Pigsty Makefile.
The vm_scale decision
Resolve vm_cpu, vm_mem, and vm_scale independently in this order: host > deepest child group > parent group > all.vars > default. Then multiply once:
Setting only vm_scale: 3 produces 3 vCPU / 6 GiB. Adding vm_cpu: 2 and vm_mem: 4GiB produces 6 vCPU / 12 GiB. A host can set vm_scale: 1 to override its group’s multiplier; scales at different levels do not compound. Disks do not scale. See the configuration reference for units, bounds, and upgrading existing deployments.
The VM parameter design specifies command scope, short flags, environment variables, precedence, and persistence for image, architecture, CPU, memory, scale, and root disk size. The proposal forwards only explicitly supplied Make IMAGE/ARCH/CPU/MEM/SCALE/DISK variables as CLI flags, which outrank corresponding BARN_* environment variables. Template defaults and host exceptions remain in YAML, and ordinary lifecycle commands save no hidden preferences. An IMAGE option or distribution suffix may override images in homogeneous profiles. Build profiles must retain per-node vm_image unless the user explicitly requests one image for all nodes.
DNS is feasible as an explicit operation
Changing the system /etc/hosts still requires root. Extraction, validation, and preview can run as the ordinary user, with only the final controlled write delegated to Barn’s helper. Repeating an unchanged plan needs no sudo. Initial setup is one occasion to publish names; adding a portal or changing a VIP requires another publication.
Pigsty’s bin/sandbox-dns interprets service configuration. Barn accepts generic all.vars.vm_hosts mappings, combines them with node names and aliases, validates them, and replaces the # barn:begin/end block. These mappings stay outside the VM spec hash, so DNS edits do not require VM recreation or restart.
| Source | Host-side destination |
|---|---|
Barn node names and vm_alias; Pigsty nodename and pg_cluster-pg_seq |
Node IP |
infra_portal.*.domain/domains |
The infra configuration’s admin_ip ingress, rather than a backend endpoint that may use 127.0.0.1 |
node_default_etc_hosts, node_etc_hosts, dns_records |
Declared IP with ${admin_ip} expanded; relevant enable switches are honored |
pg_cluster + pg_dns_suffix |
auto uses an enabled PG VIP or the inventory’s unique declared primary; also supports primary, vip, none, and explicit IPs |
node_cluster + vip_dns_suffix |
Enabled node VIP, applying node_id_from_pg cluster identity rules |
minio_node and minio_cluster-minio_seq |
MinIO node IP, expanding MinIO name placeholders |
vm_hosts |
Explicit additional private-network mappings |
With the development commands available, run these from the Pigsty repository:
Set BARN=/absolute/path/to/barn to use a local build. The script first calls barn validate, then creates a temporary inventory with mode 0600 containing only node identities and names. It does not copy Pigsty credentials, keys, or share configuration. Identical mappings deduplicate; conflicting destinations identify their sources, with names compared without case. Records owned by other tools remain intact, and conflicts require an explicit decision about migrating those older records.
Like Barn, the adapter reads a static inventory without running inventory plugins, group_vars, host_vars, or general Jinja expressions. Unresolved templates in relevant fields, wildcards, and addresses outside the inventory subnet fail explicitly. pg_dns_target: primary reflects the declared inventory and does not follow live Patroni failover; an already configured VIP is preferable for an HA entry point. When several infra nodes each resolve i.pigsty to themselves, the host cannot represent all those local views simultaneously. Select one ingress or use distinct domain names first.
If wildcard names and dynamic records become necessary, evaluate domain-specific forwarding to Pigsty DNS. macOS domain resolvers and Linux resolver integration need separate designs. The current phase needs neither a resident DNS process on the host nor a global nameserver change.
Offline audit of 54 configurations
On 2026-11-10, the local candidate ran barn validate --json against all 54 conf/**/*.yml files and extracted their names. 53 passed VM validation; 42 passed DNS extraction. These are static checks. Module deployment and VIP failover still require native acceptance tests.
| Configuration/category | Result and required work |
|---|---|
meta |
1 node, 1 vCPU / 2 GiB; 10 names extracted and an actual hosts preview succeeded |
ha/dual, ha/trio, ha/full |
2/3/4 nodes, totaling 2/3/4 vCPU and 4/6/8 GiB by default; valid VM specs, but PG VIPs occupy reserved addresses |
ha/simu |
20 nodes, 20 vCPU / 40 GiB; reserved PG VIPs, and MinIO expects /data{1...4} without declaring four vm_disks |
demo/minio |
4 nodes with 4×64 GiB data disks each; 21 names extracted; include its .9 node VIP in native network acceptance |
build/oss |
7 nodes with explicit 2 vCPU / 4 GiB each, totaling 14 vCPU / 28 GiB, and explicit per-node images; conflicting host-side destinations for i.pigsty |
build/dev |
7 parseable nodes, but no per-node vm_image, so all would use Barn’s u24 default; distribution SSH aliases also need attention; DNS has the same multi-destination conflict |
demo/demo |
nodename: pigsty.cc violates Barn’s VM naming rules; use a short node name and put the FQDN in vm_alias |
| Other templates with reserved VIPs | demo/debian, demo/el, fat, ha/octo, ha/safe; together with the four HA profiles above, 9 templates are affected |
Barn reserves .1 for the host and .2–.8 for DHCP; static nodes start at .9. Pigsty’s common .2/.3/.4 VIPs must move. None of the current 54 templates uses .100/.101/.102, making them candidates for this sandbox migration. Update explicit DNS records, connection strings, and examples together, and check for collisions with the selected inventory’s nodes and other VIPs. Relaxing hosts validation would not resolve the network conflict.
Barn names nodes using nodename > pg_cluster-pg_seq > node-lastOctet. Pigsty’s node_id_from_pg: true can instead overwrite an explicit nodename in the guest. Preserve old script names such as meta, node-1, and el9 explicitly through inventory names and vm_alias; verify both host SSH aliases and guest hostnames.
Complete the replacement in this order
| Priority | Owner and scope | Acceptance condition |
|---|---|---|
| Implemented, unreleased | Barn resources and hosts foundation; Pigsty name extraction | Checks recorded here; repeat catalog/version release gates before publication |
| P0: inventory corrections | Pigsty conf/ VIPs, simu disks, dev images, short names, and SSH aliases |
Every target profile passes VM and name validation, with expected totals and image matrices |
| P0: switch entry points | Makefile, a common bin/sandbox entry point, and configure integration; default meta/full/simu to u26; map suffixes 8/9/10/12/13/22/24/26 to el8/el9/el10/d12/d13/u22/u24/u26 |
Sequential behavior even with make -j; explicit -f avoids a shadowing barn.yml; profile changes never imply destruction; ordinary up leaves hosts alone |
| P0: SSH and artifact delivery | Use barn ssh-config, migrate aliases in copy/boot-pkg/sync, and reuse dba/UID 88 and verified connection parameters |
Host SSH, scp/rsync, and Ansible access from the control node work; old Vagrant Includes do not win matching |
| P0: native acceptance | macOS arm64 and Linux amd64, covering at least meta, full, simu, and a mixed-image build | 1×/2× resources and resize, idempotent DNS, VIP access/failover, four-disk mounts, stop/start, failure retry, explicit destruction, and a fresh Pigsty deployment |
| P1: release integration | bin/release, installation dependencies, bilingual guides and examples, and retiring Vagrant entry points |
The release archive completes a sandbox workflow with Barn and contains no obsolete entry points in its scripts or guides |
| P1: ongoing usability | Exact environment records, common group selection, upload/download, and measured large-topology startup improvements | Reproducible builds and artifact retrieval, with diagnosable and retryable partial failures |
| P2: demand-driven additions | Cold snapshots, VM export, dynamic DNS/domain forwarding | Define identity, disk, and network recovery contracts for each feature first |
Barn still manages one Linux deployment per user. Profiles describe desired topologies for that deployment. Changing BARN_HOME does not isolate the user’s shared runtime paths or host private network, so it is not a concurrent-environment mechanism. Image defaults belong to separate layers too: the new Pigsty entry point can select u26 while Barn retains its embedded catalog’s u24 default.
Implementation and validation evidence
Resource inheritance and scaling are in inventory.go and inventory_test.go. Hosts merging and privilege boundaries are covered by hostconfig and cmd/barn/hosts_inventory_test.go. Pigsty’s new local files are bin/sandbox-dns and tests/test_sandbox_dns.py, with service rules checked against roles/infra, roles/pgsql, roles/node_id, and roles/minio. These links identify the repositories; the local working trees remain authoritative for the unreleased changes until they are committed and published.
Completed checks include make build, Barn make check, 11 Pigsty extractor unit tests, and the 54-template offline audit. The real command for conf/meta.yml returned applied: false and displayed 10 names. System hosts writes, VM resource changes, VIP failover, and Pigsty playbooks have not been executed; they are assigned to the native acceptance phase above.
2.10 - Pigsty host setup: fixed names and CA trust
Recommend one optional host setup operation: install a fixed set of Pigsty names and trust the lab’s public root CA certificate. Keep one entry address, defaulting to 10.10.10.10, and reuse the same CA across ordinary lab rebuilds. With unchanged names, address and CA trust, setup is a no-op and needs no elevation. This gives the common lab a mostly one-time host integration step.
Status — 2026-11-11: proposal, not implemented. This narrows the first delivery from the earlier full host-DNS design. General inventory extraction, dynamic node/PG/VIP publication, split DNS and a resident DNS service remain optional future work. Their implementation is not a prerequisite for this fixed profile. This review changed documentation only; it did not read a CA private key, install trust, modify system hosts or operate VMs.
Publish a small, stable name profile
Proposed core-v1 contains eight exact names. All point to one explicitly chosen entry IP; version the list so an application being enabled or disabled does not change host configuration.
| Names | Intended use |
|---|---|
i.pigsty |
Pigsty home, monitoring and paths served by the home portal |
sss.pigsty, m.pigsty |
Object-storage service and optional console portal |
adm.pigsty, ddl.pigsty |
Optional pgAdmin and Bytebase portals |
supa.pigsty, api.pigsty, cli.pigsty |
Reserved application/API aliases |
Here *.pigsty describes a family of exact names. /etc/hosts has no wildcard matching. A new arbitrary name such as new-app.pigsty requires a reviewed profile update; a real wildcard resolver remains a separate option. Keep i.pigsty first on the line for stable canonical-name ordering.
The list builds on Pigsty’s default DNS aliases and MinIO service name. Some are deliberately reserved before the application exists. Resolving a name does not install its application, open its port or add it to Nginx. In particular, sss.pigsty may use port 9000, while a portal normally uses 443.
The single-entry profile fits a lab whose listed services are on, or proxied through, that entry. If storage or an application lives elsewhere without such a proxy, use an explicit static mapping for that service through the broader hosts interface. Do not discover another target from backend endpoints or select the first infra node. The address must satisfy Barn’s private IPv4 /24 and .9–.254 rules. Custom subnets use an explicit --ip; this command does not reinterpret admin_ip or Jinja.
Give the user one setup entry point
Put the Pigsty-specific profile in proposed bin/sandbox-host. Barn supplies generic hosts and certificate operations through its existing privileged boundary. All command examples below describe new interfaces.
setup previews by default. The plan shows the exact names/IP, CA subject, validity and SHA-256 fingerprint, trust destination, additions/removals and ownership. --apply authorizes that concrete operation; combine both changes into one setup invocation and reuse one administrator-authentication session where the OS permits. System policy controls the number of authentication prompts. There is no persistent passwordless grant or background process.
core-v1 replaces Barn’s one complete hosts snapshot. If an earlier node/service snapshot exists, show the names being removed before applying; preserve every foreign hosts line. This keeps publication deterministic without silently merging stale entries.
The recommended order is: initialize Pigsty so its CA exists, obtain the public certificate, then perform this host setup once. If the repository is shared with the controller, the public certificate may already be at the shown path. Otherwise copy only ca.crt through the established, host-key-verified SSH connection. Optional --dns-only supports earlier name setup; --ca-only installs trust later. These are separate operations and can require separate authorization. Missing CA input must not silently apply half of a combined request.
status reads ownership and effective hosts/trust state without changing it. status --probe additionally checks system name resolution and the home portal’s HTTPS chain and hostname. Report names, system trust, and portal HTTPS separately: an offline guest can coexist with correctly installed host configuration. A TLS check must use normal certificate verification, without insecure mode or a supplied CA override that bypasses the store being tested.
For noninteractive first trust or CA replacement, require the expected public-certificate fingerprint with proposed --ca-sha256; compare it before elevation. --apply is still required. Authorization uses a nonblocking path and fails cleanly if unavailable. Repeated identical setup requires no authentication. Normal barn setup, up, stop and destroy never implicitly import a CA.
Trust the intended CA and preserve its identity
The source is the lab’s existing files/pki/ca/ca.crt. Pigsty’s CA role creates or reuses this CA on the controller. Keep the controller’s PKI persistent across rebuilds. The host receives only the public certificate; the private key remains with Pigsty.
Accept one bounded PEM/DER self-signed root certificate in the first version. Verify parsing, self-signature, validity, CA basic constraints and certificate-signing key usage. Reject a private-key payload, a leaf certificate, an ambiguous bundle or an unsupported intermediate chain. Identify the CA by SHA-256 of its DER bytes, not the common name pigsty-ca; reformatting PEM must remain a no-op. Bind the parsed certificate and hosts payload to the reviewed plan and verify them again in the privileged helper.
System trust in this CA is broader than the eight names. Neither these hosts entries nor the CA’s display name restrict its signing authority to .pigsty. The setup preview must state that scope. Use TLS-specific trust on macOS where supported; do not disable hostname/expiry checks or silently change other application policies.
Importing the root also leaves server-certificate requirements intact. Pigsty’s Nginx certificate task includes configured portal names in SAN. It does not automatically certify every reserved core alias. Enabling a portal requires its matching virtualhost and a valid SAN-bearing leaf certificate; a wildcard certificate and wildcard DNS are independent choices. A leaf renewal under the same CA needs no host trust change.
Use native trust stores, with explicit application coverage
| Platform | Proposed system adapter | Precise removal |
|---|---|---|
| macOS | System Keychain plus administrator TLS trust, using the supported security operations; select SSL policy, with no allowed-error exceptions |
Remove this certificate’s owned administrator trust, then its owned keychain item, identified by fingerprint; preserve pre-existing certificates/policies |
| Debian / Ubuntu | Own PEM .crt file at /usr/local/share/ca-certificates/barn-pigsty-<sha256>.crt; regenerate with update-ca-certificates |
Remove only the matching owned file and regenerate/verify the store; use the platform’s supported removal refresh |
| RHEL / Rocky / AlmaLinux | Own anchor at /etc/pki/ca-trust/source/anchors/barn-pigsty-<sha256>.crt; run update-ca-trust extract |
Remove only the matching owned anchor, extract again and verify |
| Other Linux trust layout | Return an unsupported-adapter diagnostic and an exportable public certificate | Keep explicit manual trust installation available |
These mechanisms follow Apple trust settings, Ubuntu’s installation/removal procedure and RHEL’s shared trust store. Local macOS security help confirmed administrator-store selection, the SSL policy and SHA-256 item deletion syntax; no mutation command was executed. Linux adapters require capability detection, including installed CA tooling, before a combined apply.
System trust and application trust have separate acceptance results. macOS Chrome supports locally configured SSL trust in the System Keychain; Firefox supports system roots on macOS when its corresponding setting is enabled. Linux Firefox, Chrome, isolated packages and language runtimes need their own verification. The initial tool must report a browser that still rejects the CA, and provide a separate explicit import path; it must not quietly rewrite every browser profile or claim that a successful system update made every browser ready. See Chrome’s local-trust behavior and Certificate Manager and Mozilla’s platform-specific CA integration.
Make repeated setup and cleanup predictable
| Event | Host action and authorization |
|---|---|
| First names + CA setup | Review both, then perform the required privileged writes |
| Same IP, names and effectively trusted CA | No-op; no sudo and no unnecessary trust-store regeneration |
| Guest stop/start/rebuild, same CA retained | No host update |
| Leaf certificate renewal under the same CA | No host update |
| Entry IP or fixed-name profile changes | Explicit hosts update; retain unchanged CA trust |
| New CA fingerprint | Stop ordinary reconciliation; require explicit proposed --replace-ca OLD_SHA256, with the new CA previewed/pinned |
destroy or deletion of BARN_HOME |
Keep integration until explicit removal; ownership remains recoverable outside deployment state |
remove --apply |
Remove owned names and trust only; preserve everything that predated setup |
Keep a root-owned receipt outside BARN_HOME: owner UID, profile version, canonical names/IP, public CA DER/fingerprint, exact trust target, prior existence, and effects this operation owns. An already trusted identical CA is pre-existing, not newly owned. A pre-existing conflicting trust policy needs explicit manual resolution; never broaden it automatically. If another source still trusts a removed certificate, report that remaining trust rather than removing foreign copies.
CA rotation is explicit. Validate the new root, install and verify its trust, then remove the old trust only when the receipt proves ownership and the operator requested replacement. Switching a deployment to a new CA may also require server-certificate rollout; the host command does not perform it. Reuse of a lab CA by another lab must be accounted for before old trust is retired.
Hosts and a trust store do not form one atomic transaction. Preflight both before either write; apply CA trust and then the reviewed hosts snapshot, recording each completed step. On failure, undo only changes newly made by this attempt that still match its receipt. Preserve concurrent edits and report any partial state. For normal removal, delete the owned hosts publication and then owned trust. Never restore an old whole hosts file or whole keychain/trust-settings dump.
Keep the implementation small and reviewable
- Pigsty profile: implement
bin/sandbox-hostwith the fixed list, explicit IP and public CA input. It does not evaluate Ansible, inspect live roles or regenerate CA material. Existing# pigsty dnslines are reported for the explicit migration described in the DNS study. - Barn hosts boundary: reuse the single managed block, canonical records and complete preview. Add a hosts-only consumption path for the existing
all.vars.vm_hostsshape: no fake VM node and no second VM inventory format. Infer the one private/24from the literal addresses and validate its reserved range. This is a required new capability; the audited prototype currently rejects a records-only inventory. Keep ordinary VM inventory validation unchanged. Apply the earlier inventory-source guard so bare node publication cannot erase core names. - Barn trust boundary: add generic CA plan/status/install/remove operations and a combined reviewed operation for the wrapper. Extend the one existing privileged helper with bounded, typed operations and fixed platform destinations. Pigsty domain names remain outside Barn. Complete helper-side scope validation and the host-wide lock before this integration; do not grant permanent sudo access to the present helper.
- Acceptance: require repository checks and real macOS and Linux acceptance before documenting the command as available. The broader exporter and DNS daemon are not release gates for this profile.
| Test area | Required evidence |
|---|---|
| Fixed data | Exact eight-name snapshot, custom IP, reserved-address rejection, casing/conflicts, no inventory or service dependency |
| CA input | Fingerprint identity across PEM/DER, invalid/expired/non-CA/private-key/bundle rejection, missing CA, changed input after preview |
| Repetition and ownership | Second run makes no privileged call; pre-existing trust survives removal; same-CN different-CA does not replace silently; foreign edits are preserved |
| Failure and recovery | Trust-tool failure, hosts conflict/race, interruption between steps, noninteractive authorization failure, reinstall after state removal |
| Real clients | macOS System Keychain and default TLS client; Debian-family and RHEL-family trust stores; system resolver; home portal SAN/chain; supported browsers tested separately without insecure overrides |
| Lifecycle | Leaf renewal and VM restart need no setup; CA replacement and entry-IP change show only the necessary updates; complete removal verifies owned effects are gone |
This revision verified source semantics and official platform mechanisms and ran documentation checks. It did not validate an installed CA adapter or a live HTTPS portal. The product default is settled: fixed names plus native system CA trust, explicit setup, no-op repetition, and owned cleanup. The eventual command release number and each advertised browser/platform combination remain acceptance decisions.
2.11 - VM specification parameters: commands, short flags, environment, and root disks
Image, architecture, CPU, memory, scale, and root disk size form one set of VM specification inputs, with consistent names, value rules, and source tracking. Each command declares which fields it accepts and which changes it can apply; the resolution process has one implementation.
Status (2026-11-11): This approved design is implemented: six CLI/environment inputs, command integration, inventory generation, source tracking, SPEC display, and in-place root growth. These changes remain unreleased. Working-tree inventories default to 1 vCPU / 2 GiB; released Barn 1.1.0 still defaults to 2 vCPU / 4 GiB.
Six parameters with consistent names
| Meaning | Inventory field | Long flag | Short flag | Environment variable | Candidate default |
|---|---|---|---|---|---|
| Image | vm_image |
--image |
-I |
BARN_IMAGE |
Embedded catalog default u24 |
| Architecture | vm_arch |
--arch |
-A |
BARN_ARCH |
native |
| Base CPU | vm_cpu |
--cpu |
-C |
BARN_CPU |
1 |
| Base memory | vm_mem |
--mem |
-M |
BARN_MEM |
2048 MiB |
| Resource multiplier | vm_scale |
--scale |
-S |
BARN_SCALE |
1 |
| System root disk size | vm_disk |
--disk |
-D |
BARN_DISK |
64 GiB |
Uppercase short flags form a consistent specification group and avoid existing lowercase flags: -c for CIDR, -m for setup mode, -s for script/source, and -d for dry-run. Register the six flags only on supporting subcommands, rather than as persistent root flags, so they do not intercept options passed through to SSH.
Use mem consistently with vm_mem and BARN_MEM. The first version needs only one canonical name for each field, without extra aliases such as --memory, --cpus, or --root-disk-size. --image retains its existing family, channel, and version syntax and replaces the inventory’s vm_image/vm_version pair; no separate version flag is needed. Other specification overrides must leave image selection alone.
CPU, memory, scale, disk, and image apply to selected nodes. Architecture remains deployment-wide; selecting nodes cannot create a deployment with mixed guest architectures.
Precedence and calculation order
Resolve each field independently:
Both CLI and environment values are overrides and can replace explicit host values on selected nodes. To retain host exceptions, put default multipliers in all.vars or narrow the node selection. --scale 3 replaces the inventory or environment multiplier; all sources produce one final multiplier. Root disks, data disks, node count, and networking do not scale.
Given inventory values vm_cpu: 2, vm_mem: 4GiB, vm_scale: 2, and vm_disk: 64:
| Override input | Effective CPU | Effective memory | Root disk |
|---|---|---|---|
| None | 4 | 8 GiB | 64 GiB |
--scale 3 |
6 | 12 GiB | 64 GiB |
BARN_SCALE=3 with --scale 1 |
2 | 4 GiB | 64 GiB |
--cpu 4 |
8 | 8 GiB | 64 GiB |
--cpu 4 --mem 8GiB --scale 1 --disk 128GiB |
4 | 8 GiB | 128 GiB |
CLI CPU and memory still specify base values. Corresponding YAML, CLI, and environment inputs therefore participate in the same formula; use --scale 1 to specify exact final resources. plan should show the bases, multiplier, result, and sources so --cpu 4 is not mistaken for an unconditional final count of four CPUs. Source and calculation details stay outside the VM specification hash.
CPU and scale accept only positive integers, and effective CPU cannot exceed 256. Base memory must be at least 512 MiB, with overflow checks for capacity calculations. CLI and environment inputs use the same size parser: bare memory integers mean MiB, and bare disk integers mean GiB. Existing unit syntax such as 4G and 4GiB remains available; examples should prefer explicit units. Invalid values fail without rounding, clipping, or falling back to defaults.
Supported commands
| Command or mode | Accepted specification inputs | Behavior and persistence |
|---|---|---|
init |
All six | Generate an inventory containing bases, scale, and other chosen specifications; -o - writes only to stdout |
setup generating a template |
All six | Use the same template resolution as init, display the preparation plan, then follow the existing host preparation and inventory publication flow |
setup -f or discovered inventory |
No specification overrides | Prepare the host for the existing file; reject explicit specification flags and ignore VM specification environment variables |
validate |
All six | Validate the entire inventory’s effective specifications under these inputs, so the intended startup parameters can be checked consistently |
plan |
All six | Explain sources, calculate resources, check image and root disk constraints, and show changes and follow-up commands |
up |
All six | Create new nodes with the effective specifications; resource changes on existing nodes require resize, and other changes require explicit recreate |
reload |
All six | Share the desired specification with up for new nodes and preflight checks; reject unsupported changes to existing nodes before stopping them |
recreate |
All six | Rebuild explicitly using the effective specifications, retaining existing confirmation, root disk replacement, and persistent disk policies |
resize |
C/M/S/D; retain existing I/A checks |
Apply CPU/memory changes and grow the root disk; never shrink it or change image/architecture |
start/stop/restart/status/destroy/purge |
None | Use applied state; specification environment variables do not affect these operations |
ssh/exec/logs/provision/hosts/network, etc. |
No generic specification inputs | Retain each command’s own operation parameters |
validate gains no node selectors. Other lifecycle commands that support selection override only selected nodes, or all nodes when no selector is given. Initially, the environment variables apply only to the Linux VM entry points listed above. barn mac and independent image management commands retain their own interfaces. For example, image pull -A selects an artifact architecture but does not automatically read this proposal’s VM environment defaults.
resize already exposes I/A, and its tests and reference documentation preserve that interface, so this change should retain it. Keep the existing strict checks: users may use I/A to make the desired specification match the current image and architecture, but cannot change either through resize. Corresponding environment variables receive the same checks. Both --disk/-D and BARN_DISK are now accepted for root growth only.
Determine whether setup generates or reuses an inventory before reading specification environment variables. setup full -C 2 selects template generation; if the destination exists and differs, preserve it and report a conflict. Bare setup that discovers an inventory prepares the host for that file, so an exported BARN_IMAGE cannot silently select another image.
SPEC display contract
Use SPEC as the column heading and a compact effective CPU/memory value such as 1c2g, 2c4g, or 8c16g. Lowercase c means vCPU and g means GiB. The value contains the scaled resources; disk capacity and scale remain separate. Display 1536 MiB as 1.5g and 512 MiB as 0.5g, without rounding. Capacities not divisible by MiB retain their exact byte count with b. Unknown resources display - instead of assumed defaults.
| Location | Meaning of SPEC |
|---|---|
init summary, setup preparation plan, validate |
Effective generated or validated resources |
plan |
Desired resources; changes appear as 2c4g → 4c8g |
status, lifecycle completion results |
Applied CPU/memory specifications |
| Live lifecycle progress | The operation’s target specification; later status events can update it to applied resources |
plan/setup/validate/status show root capacity in a separate ROOT column, with old and desired values for changes; init lists root capacity separately. Lifecycle results without a live table list each node’s SPEC. When scale differs from 1, show bases, multiplier, result, and sources; --verbose also explains calculations with scale 1. JSON retains numeric CPU, byte capacities, and structured resources explanations instead of replacing numbers with abbreviations. Formatting and explanations neither affect VM specification hashes nor become an input syntax.
Root disk size and in-place growth
--disk/-D maps to vm_disk, the absolute target size of the system root disk. Barn also provides a default 128 GiB data disk mounted at /data; this proposal retains that behavior and introduces no multi-disk CLI. With -D 128GiB -S 3, the root disk remains 128 GiB.
During creation and explicit recreation, Capacity must be positive and at least the selected base image’s virtual size; download size is not the lower bound. Planning can use virtual_size from the verified catalog, followed by an actual image check during creation, without downloading the whole image for a read-only validation. If an existing root disk differs, up reports drift rather than modifying it with disk tools. Applying a size change through recreate replaces the original root disk and nonpersistent data disks; that operation is an explicit rebuild and must not be described as growth that preserves data.
Grow an existing root disk with barn resize -D 128GiB meta. The argument is an absolute target; repeating the same target makes no further disk change. Do not accept incremental syntax such as +64GiB or offer in-place shrinking.
Growth requires separate checks of the disk image, guest partition, and filesystem. QEMU requires extending guest partitions and filesystems after increasing image capacity, and warns against using offline qemu-img to modify an image in use. Prefer a recoverable offline flow for the first implementation; evaluate online QMP growth separately. QEMU disk image tools
Barn already writes growpart and resize_rootfs: true when creating guests. Cloud-init defines both modules with frequency always, but they can be disabled and partition layouts impose limits. A reboot alone cannot establish successful root disk growth. Cloud-init Growpart, Resizefs
The implementation persists preflight → image → guest → verified, recording the target before any disk mutation. Retry the same target to continue without shrinking to simulate rollback. Supported roots use 512-byte logical sectors and a last physical GPT or primary MBR partition with ext4/XFS. LVM, encrypted root devices, and other layouts are rejected before image modification. Initially stopped VMs temporarily boot for probing and expansion, then return to stopped. See root growth acceptance for recovery details and native measurements.
Environment variable rules
Environment variables are useful once all six inputs share a resolution process that exposes their sources. They support CI matrices and temporary shell invocations with the same override semantics as CLI flags. They are inputs to the current command and therefore outrank host-level inventory values. Global defaults that must retain node exceptions belong in all.vars.
Invocations:
Parse an environment variable only when the command accepts its field. BARN_CPU=bad barn status must succeed as usual; up with the same environment must report an error naming the variable. If CLI has already overridden the field, skip validation of the shadowed environment value: BARN_CPU=bad barn up -C 2 uses 2.
An empty or whitespace-only environment value means unset, allowing an inherited value to be cleared for one invocation: BARN_SCALE= barn plan uses the inventory multiplier. Explicit --scale=0, --scale=, and invalid nonempty environment values still fail. Help and version commands remain unaffected by specification environment errors. The first version does not automatically read .env, global preference files, or historical parameters, and needs no ambiguous --ignore-env flag; env -u BARN_SCALE explicitly clears one input.
Specification explanations in plan/validate/up should identify sources, for example scale: 3 (env BARN_SCALE). Structured output should provide the same information without dumping unrelated environment values. Suggested apply and failure-retry commands should freeze the effective environment overrides as explicit flags and retain the inventory path, node selectors, and argument values. Environment changes do not create hidden persistent settings.
Like --image, BARN_IMAGE on an inventory containing multiple images replaces the image for every selected node. Leave it unset to retain a build matrix’s per-node image declarations. Existing variables such as BARN_HOME, BARN_REPO, output format, and debugging retain their separate contracts and do not join the scaling formula or specification table.
Inventory, applied state, and persistence
Configuration generation and runtime overrides have separate persistence rules:
initand template-generatingsetupexplicitly write all six chosen values intoall.vars, making defaults visible in the file. Record base CPU/memory and scale; never write multiplied results back as bases.initpreserves existing files by default and replaces them only with explicit--force.- The first interactive
up, if it actually needs to generate a new inventory, uses the same generator and records its chosen specifications in that file. A subsequent ordinaryupcan read the same choices. - With an existing inventory,
up/resize/recreateoverride only the current desired specification without rewriting YAML. Successful operations record the actual effective specification in applied state. Reading the original inventory again shows differences, while commands such asstartretain applied specifications.
For example, the commands below generate four nodes, each with 4 vCPU / 8 GiB and a 128 GiB root disk:
When setup <template> decides whether an existing file can be reused, compare normalized bases and scale as well as final VM specifications. 2×2 and 4×1 produce the same CPU count but different results under a subsequent --scale 3; the generator cannot silently treat them as equivalent template choices. VM drift still compares final specifications, so different expressions of the same effective resources do not require a restart.
Applied state currently retains neither the original bases nor scale. Without a readable inventory, explicit CLI or environment scale must require -f; it must not multiply already-scaled applied resources again. CPU/memory alone can set absolute applied resources using an implicit scale of 1 while retaining the other applied specifications. Internal retries recompute from the same original inputs instead of applying another override to computed results.
Pigsty integration
Pigsty’s Make entry point forwards only explicitly supplied IMAGE/ARCH/CPU/MEM/SCALE/DISK variables as corresponding CLI flags. Omit absent values, do not multiply resources in Make, and never inject SCALE=1. Explicit Make inputs become flags and therefore outrank BARN_* environment variables.
Pigsty’s generator may write u26 as the default image for homogeneous profiles, while Barn’s own default remains u24. Build profiles retain per-node vm_image; do not append a uniform -I automatically. Save profile selections during generation when persistence is intended. Ordinary lifecycle commands retain the temporary override contract described above.
DNS has its own design. Host entry publication and service names do not belong among these six specification inputs.
Implementation and delivery order
Use a small parameter table for names, short flags, environment names, parsing, and scope, plus a typed input structure carrying presence, value, and source. Commands declare accepted fields, supported changes, and whether they generate an inventory through a capability table. A generic configuration framework is unnecessary; individual commands should not independently call os.Getenv or duplicate similar flag registrations.
The shared process is: determine command mode and input source → collect permitted CLI/environment overrides → resolve inventory inheritance → apply fields to selected nodes → calculate effective specifications once → validate → plan or execute. Preserve the strict vm_* namespace. Inventory syntax, field types, and inheritance conflicts must remain valid; CLI overrides cannot bypass unknown fields, malformed YAML, or selector errors. Check the effective CPU limit and memory overflow after applying overrides.
The working tree uses internal/config/resources.go for bases, scale, and sources, and cmd/barn/vm_inputs.go to register the six inputs and parse permitted environment variables. Inventory parsing applies selected-node resource overrides before multiplication; image and architecture retain their existing override semantics. init/setup share template generation, and internal host preparation by up preserves resolved specifications. internal/private/root_resize.go reuses disk.Manager.Grow and holds the deployment lock across offline growth, guest verification, and recovery.
| Phase | Deliverables | Acceptance focus |
|---|---|---|
| A: Unified specification input | Six fields, C/M/S/D long and short flags, environment variables, precedence, provenance, and command capabilities; no growth of existing root disks | Parameter combinations, invalid input, environment shadowing, scope, equivalent outputs, and existing I/A behavior |
| B: Command integration | init, setup, validate, plan, up, reload, recreate; CPU/memory/scale for resize | Bases in generated inventories, existing files unchanged, selected-node scope, idempotent retries, and identical specifications in planning and execution |
| C: In-place root growth | D for resize, change classification, offline disk growth, guest expansion, and recovery from partial failure | No shrink or loss of existing data, retained identity, actual capacity checks, running/stopped state, and repeated execution |
A, B, C, and SPEC display are implemented; root growth has separate ext4/XFS native acceptance. Source regression coverage should also exercise template generation, setup, resize boundaries, and root disk creation.
Acceptance covers internal/config/resources_test.go and cmd/barn/vm_inputs_test.go, along with make build, source make check, and site make check. Build and read-only CLI acceptance use a temporary BARN_HOME; existing VMs and host networking remain untouched. Root growth also has internal/private/root_resize_test.go and native acceptance in an isolated temporary deployment. Current contracts are documented under specification overrides and SPEC display.
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.
2.12 - Host DNS and domain resolution for Pigsty on Barn
Scope narrowed on 2026-11-11: the first delivery is now fixed core names and Pigsty CA trust. This page retains the broader exporter and resolver research; its general inventory and split-DNS work is optional expansion, not a prerequisite for the fixed profile.
Recommend explicit, opt-in managed hosts publication first. Pigsty computes the names and service targets; Barn previews and installs one neutral address/name snapshot through its existing helper. This is worthwhile for opening portals, addressing nodes and connecting to database or VIP names in a static Linux lab. Fixed addresses make repeated synchronization cheap: unchanged records need no privileged operation. Root is still required for every actual system hosts change, including removal. Setup does not grant perpetual update permission, and a cached or passwordless sudo authorization eliminates a password prompt, not privilege. Keep per-command authorization; consider suffix forwarding when a stable guest DNS endpoint and a complete namespace exist. A host daemon needs demonstrated demand beyond these two options.
Status: design dated 2026-11-10; evidence gathering and synthesis crossed into 2026-11-11, Asia/Shanghai. The checked Barn v1.1.0 source at b2cc8da has applied-node hosts publication. vm_hosts, hosts install -f, case normalization, and Pigsty bin/sandbox-dns are dirty, unpublished prototypes audited on those dates. Source selection guards, exporter modes, stronger transactions and DNS adapters below are proposals. Release binaries and real-platform behavior were not validated. The migration article provides broader context; this design specifies the DNS work independently.
Choose the smallest useful publication mechanism
| Alternative | Coverage and daily operation | Privilege and failure behavior | Decision |
|---|---|---|---|
| Managed hosts | Finite short names and FQDNs; explicit complete synchronization; resolution survives guest shutdown, although connections fail | Every changed write/removal is privileged; identical snapshot is a no-op; no resident service | Recommended first implementation, always opt-in |
| Suffix forwarding to guest DNS | Records actually served within an agreed zone; guest-side changes propagate without rewriting the host route | Privileged route installation, endpoint changes and removal; stable routes need no host privilege for record updates; guest DNS becomes a dependency | Optional next step, with separate macOS/Linux adapters and short-name handling |
| Host-local DNS daemon | Can support frequent record changes, wildcard or additional RR types; user-owned data can update independently of fixed system routing | Privileged service/routing setup and changes; daemon identity, ports, startup, crashes and upgrades become maintenance obligations | Defer until measured demand justifies it |
| Zero automatic configuration / manual | IPs, existing DNS or explicitly reviewed application/system overrides | Barn makes no system changes; user owns integration and cleanup | Always available; normal setup and VM lifecycle default to this policy |
With no automation, Barn’s SSH aliases and direct IP access remain usable. Multi-portal Host/TLS names, browser URLs and application settings still need existing DNS or manual overrides. Per-application overrides do not provide system-wide resolution, and manually copied mappings drift as the inventory changes.
The boundary remains one Linux deployment per user, one inventory format and one host-wide Barn block. A different BARN_HOME does not create another hosts namespace. Pigsty owns portal, PostgreSQL, VIP and MinIO meaning; Barn neither discovers Patroni leaders nor learns Pigsty roles. This preserves the Barn contribution contract.
flowchart LR
I["Pigsty inventory + context"] --> P["Pigsty rules + selected view"]
P --> S["all.hosts + vm_hosts snapshot"]
S --> B["Barn offline plan"]
B --> H["Privileged helper"]
H --> F["One managed hosts block"]Define names and views before choosing a resolver
The default proposed export is host-access intent, a useful union of selected portal ingress names, node identities, explicit records and service aliases. It can intentionally contain names absent from guest dnsmasq. Keep separate categories—vm-identity, guest-hosts, infra-dns, portal-access, service-access—and retain their source, view and target type in the export report. A guest-dns view instead reproduces the selected infra DNS output and its task conditions. Shared computation must unify identity and target rules while allowing these different output policies.
This table is the authoritative mapping policy. The source column describes audited Pigsty behavior; the final column is the proposed host-access policy.
| Name source | Audited meaning and destination | Host-access publication policy |
|---|---|---|
Barn node identity and vm_alias |
nodename, otherwise pg_cluster-pg_seq, otherwise node-lastOctet; aliases resolve to the node address |
Include resolved VM names/aliases. Use vm_hosts for new service names so service edits do not change VM specifications. Barn inventory |
node_default_etc_hosts, node_etc_hosts |
Guest hosts entries, gated by node_write_etc_hosts; substitute that node’s effective ${admin_ip} |
Include the selected view’s enabled entries as guest-hosts; parse each physical line separately. Node-name exchange is a separate task context. Node DNS tasks |
dns_records |
Infra hosts files with ${admin_ip} substituted; dns_enabled gates service startup, not all file generation or removal of a running service |
Include declared records with desired-config provenance; never infer DNS liveness from this switch. Guest dnsmasq uses no-hosts, so guest /etc/hosts is not automatically served. Infra tasks, dnsmasq template |
infra_portal.*.domain/domains |
Nginx names, not automatic DNS records. Tasks require a nonempty domain; standard home/path/endpoint templates support aliases |
Map enabled virtualhosts to an explicitly selected ingress, typically its effective admin_ip. Never use the backend endpoint, which may be loopback. Reject unsupported custom-template semantics; omit a domains-only skipped virtualhost with a diagnostic. Portal task |
PG instance pg_cluster-pg_seq |
Inventory address; PG DNS tasks are gated by the PG node’s dns_enabled. Instance names receive no pg_dns_suffix |
Include valid instance aliases as service-access even when guest DNS is disabled, explicitly labelled host convenience. This is not evidence of a deployed DNS record. PG DNS tasks |
PG cluster pg_cluster + pg_dns_suffix |
Suffix concatenates directly, default empty. auto: enabled PG VIP, otherwise declared primary; primary: declared primary; vip: enabled VIP, otherwise no address; none: no address; explicit IP: configured target |
Apply those target rules for service-access, labelling DNS-disabled convenience aliases. Require a unique declared primary where needed. Reject explicit CIDR targets: current role output retains the mask, while the prototype silently strips it. PG VIP branches deliberately strip the VIP mask. PG defaults, PG identity |
Node VIP node_cluster + vip_dns_suffix |
Enabled vip_address, independent of PG VIP; effective node identity rules matter |
Include validated bare VIP addresses. Reject CIDR here pending a shared role/export correction, because the current DNS task writes the value unchanged. Node monitor tasks |
| MinIO member names | Expanded minio_node plus minio_cluster-minio_seq map to member IPs |
Include member identities. Publish a MinIO service name only when declared through dns_records, enabled node hosts, portal policy or explicit vm_hosts; minio_domain alone supplies certificate/client naming, not a DNS record. MinIO identity, MinIO configuration |
Explicit all.vars.vm_hosts |
Prototype-only neutral {address, names} entries |
Include as explicit intent, with the same conflict checks; explicit entries do not silently override contradictory derived records |
Barn VM names are short lowercase labels, at most 63 characters. Pigsty can rename the guest differently: node_id_from_pg: true can override an explicit nodename, while Barn’s fallback does not consult that switch. For matching identities, choose an explicit short nodename and disable Pigsty’s PG-derived override where appropriate; put FQDNs in aliases. See Pigsty node identity.
Multiple infra nodes can legitimately have different admin_ip and i.pigsty views. The host needs one explicit ingress/view, distinct names, or a stable shared entry point. Never choose the first infra node. DNS replicas qualify as redundant upstreams only after their relevant answers and published generation agree.
Export one existing inventory shape, with honest limits
The viable prototype boundary is this minimal projection of the existing inventory format. The .100 target below is illustrative and requires a separately configured, reachable VIP.
vm_hosts is allowed only under all.vars; entries accept exactly address and a nonempty string list names. Provenance stays in a separate report, never extra entry keys. At least one managed host is required: a vm_hosts-only document currently fails with inventory defines no hosts. all.hosts supplies node identity and /24 context. This generated projection is disposable output, not another user-maintained inventory. It can be published before VM creation; it does not prove that nodes or services exist.
The current Barn adapter accepts one canonical RFC1918 IPv4 address per name, inside the managed /24, with usable suffixes .9–.254. .1 belongs to the host; .2–.8 are reserved; .0 and .255 are unusable. Multiple A answers, external destinations, IPv6, wildcards and non-address RR types require an explicit alternative; hosts or DNS capabilities in general do not widen this adapter. Custom private /24s work only after Pigsty’s admin_ip, records and VIPs are adjusted consistently. DNS creates neither routes nor VIPs. Barn network layout
Ship a limited literal mode before general Ansible support
Proposed modes: literal produces desired host-access intent for declared static profiles; rendered computes a selected view using the normal Pigsty controller’s effective variables and role rules; observed explicitly collects a chosen infra node’s deployed DNS data and service observations. Rendered intent can differ from installed files after skipped/failed tasks; observed files can differ from active answers. Reports must state which evidence was collected and when. Neither mode means live Patroni authority.
Phase 1 literal support is deliberately narrow: one YAML source, known defaults kept in their role contexts, unambiguous inheritance, supported standard portal templates and the documented ${admin_ip}/MinIO substitutions. Detect and reject adjacent/controller group_vars or host_vars, multiple sources/plugins, DNS-relevant Jinja/lookups/facts, ambiguous sibling overrides and unsupported task contexts. Never silently ignore a variable source. Parse escaped and block newlines as separate records; reject malformed types and CIDR cases rather than repairing them into different role semantics.
Compute identities and targets across all relevant Pigsty hosts, including vm_skip external primary/infra nodes, before applying Barn’s address boundary. Until that calculation is supported, refuse affected profiles rather than omit their roles. Include all known external addresses in VIP collision checks. An unrelated skipped host is not permission to discard DNS-relevant configuration.
Phase 2 introduces shared Pigsty pure rules plus an effective-variable adapter, with role-render equivalence tests within each selected view. ansible-inventory --list handles inventory variables, not every role default, set_fact, task condition or controller template. General support needs explicit context for those inputs and refusal of unresolved values. Controlled read-only export can run on the normal Pigsty controller and transfer the sanitized projection to the host; host users need not install Ansible. Complete pre-VM export remains limited to profiles whose inputs are available without guest facts. Ansible variable merging
Bind validation and extraction to one input generation, checking relevant source/default files for changes before publication. Emit only node identities and names into a mode-0600 temporary file; never persist full hostvars, credentials or share settings. Proposed --mode, --view and --export options belong to Pigsty, not Barn. Default --view host-access requires an unambiguous ingress; host-access:<infra-node> explicitly selects its view, while guest-dns:<infra-node> selects DNS output. Phase 1 rejects unsupported selections. observed initially supports only guest-dns, without inventing convenience records. --export FILE writes only the projection and is mutually exclusive with applying it. Existing --json retains its Barn-plan meaning.
Make source replacement explicit and preserve service names
| Evidence | What it establishes | What it cannot establish |
|---|---|---|
| Current desired inventory/export | Names to publish, including before VM creation | Applied nodes, successful Pigsty tasks or runtime DNS |
| Barn applied node state | Committed node addresses, names and vm_alias, including stopped nodes |
vm_hosts, portals, service aliases or current leader |
| Installed block and metadata | Current host file contents and the recorded source kind | A current source pathname, live guests or fresh deployed DNS |
The prototype replaces the entire block. Consequently, running bare hosts install after hosts install -f can remove inventory-only service aliases. Proposed minimal guard: store one stable ordinary comment, # source-kind: inventory or # source-kind: applied, inside the existing block. Do not invent a # barn: marker: the current parser rejects unknown markers. This metadata is a CLI source safeguard, independent of OS authorization.
| Installed block | Bare hosts install under the proposal |
Explicit source selection |
|---|---|---|
Absent, legacy unmarked, or applied |
Preserve tagged behavior: derive applied-node snapshot, preview/confirm as usual | -f FILE selects inventory; --from-applied selects applied state |
inventory |
Fail before sudo, including with --yes; explain how to retain or replace services |
-f FILE replaces with a complete inventory snapshot; proposed --from-applied explicitly authorizes node-only replacement, showing removals |
| Unknown/duplicate source metadata or malformed block | Fail with repair guidance | No automatic overwrite |
-f and proposed --from-applied are mutually exclusive. A different -f is itself an explicit source switch. Remember no source pathname, particularly no Pigsty temporary path; introduce no publisher registry or project scopes. Omitted records disappear on the next complete synchronization. vm_hosts: [] leaves that source’s node names/aliases. Failed extraction or an invalid empty inventory must never clear the block; complete removal uses hosts uninstall. Removing a name never destroys a VM.
Fingerprint published canonical records and source kind for semantic no-op detection. Keep input hashes, rules version, selected view and detailed provenance in the unprivileged report, not timestamped block comments. Password, CPU, file-path or irrelevant inventory edits must not force root writes. Still compare actual target bytes: a matching record digest cannot hide a damaged marker or external edit. Adding the source comment to an old block is a one-time privileged migration.
An unmarked prototype block cannot reliably reveal whether it contained inventory services. Upgrade by explicitly reinstalling the complete -f snapshot before using bare install. Require a CLI implementing this guard and its matching helper; the minimum release number remains to be assigned. Old CLIs can overwrite the block despite its comment, so a downgrade is not safe. The wrapper must check required capabilities, not merely a version string.
Preview offline, then authorize the actual change
The audited prototype already has the following interfaces; their existence does not satisfy the implementation gates below.
| Existing interface | Exact behavior |
|---|---|
bin/sandbox-dns -f FILE --json |
Barn plan JSON, not a record export; default file is pigsty.yml |
bin/sandbox-dns -f FILE --apply |
Wrapper previews, then invokes Barn with --yes |
barn hosts install -f FILE --json |
Inventory preview, no application |
barn hosts install -f FILE --yes |
Apply inventory snapshot; Barn has no --apply flag |
barn hosts install -f FILE --json --yes |
Apply and return structured output; JSON does not imply read-only |
barn hosts uninstall --json / --yes |
Preview / explicitly remove the whole Barn block |
Proposed operator workflow after Phase 1 acceptance: preview and explicitly apply from Pigsty, repeating the same process after portal, node or VIP changes. These two command spellings exist in the prototype; the safer semantics are proposed.
For review or controller-to-host transfer, the proposed exporter produces the same disposable projection. Only --mode, --view and --export below are new options.
Returning to node-only publication intentionally removes service aliases. The proposed --from-applied makes that replacement explicit; uninstall already exists and needs neither inventory nor deployment state.
Every applying invocation must build and display its own complete plan before requesting root: added, changed, removed and retained names; old/new addresses; category, source kind and provenance; conflicts; native target; record generation and byte preconditions. Proposed diffs must account for names removed from the previous block even when their old field-level provenance is unavailable. Plans use local files/state only—no DNS lookup, guest connection, downloads or helper installation. An observed export is collected separately before planning. Do not print secrets or full hostvars.
| Proposed outcome contract | Result |
|---|---|
Preview (--json without --yes, or no terminal without --yes) |
Exit 0 on a valid plan, applied: false; no sudo, even if changes exist |
| Unchanged semantic records and valid installed bytes | Exit 0, changed: false, no helper installation or sudo |
Interactive text without --yes |
Show plan and ask; Barn install defaults Yes and uninstall No; wrapper remains preview-only without --apply |
| Explicit apply without a terminal | Use nonblocking authorization; lack of permission is a capability error, nonzero exit, no password wait and no hosts write |
| Invalid input, source switch violation, conflict or stale generation | Nonzero error before write; require a fresh plan, never silently regenerate and commit different data |
| Verified write / post-write failure | Exit 0 only after verification; otherwise report failure and any partial state, without claiming a successful rollback |
Keep Barn’s {plan, applied} report shape. Its current plan has schema, action, target, changed, before/after hashes and optional lines/helper digest; source/diff details are additions. The current Python wrapper does not guarantee JSON errors; structured failure parity is a Phase 1 task.
Strengthen the existing privileged boundary
Current CLI planning limits normal edits to its block. The fixed helper validates the native target, caller/staging ownership, permissions and hashes, uses atomic replacement and verifies resulting bytes plus uid/gid/mode. It does not independently prove that the staged file changes only the Barn block, and no transaction lock spans check and rename. Passing tests do not cover those gaps. A caller-supplied digest proves byte identity, not allowed scope; atomic rename does not provide compare-and-swap. Tagged helper implementation
Proposed application keeps the same boundary: the helper acquires a root-owned host-wide cooperative lock, rereads the target, checks the reviewed digest, validates bounded records/source metadata and conflicts, reconstructs the permitted replacement itself, then compares, writes atomically and verifies. Uninstall may only remove that block. Preserve outside bytes, with the documented first-append newline exception; test additional filesystem metadata before promising its preservation. Retain stale-byte checks because unrelated root editors need not honor Barn’s lock. Freeze the generated source through application; the wrapper must bind its displayed plan to the apply request, or let one Barn invocation display and apply the authoritative plan, rather than discard a preview between subprocesses.
Per-command sudo remains primary. Setup installs a root-owned, digest-matched helper; actual writes still execute with privilege. --yes bypasses Barn confirmation only. Current noninteractive authorization uses sudo -n, while an interactive invocation can ask for authentication. Persistent narrow sudo authorization is optional future work after helper-side scope validation and locking; never authorize arbitrary configuration includes or expand to unrestricted sudo. A helper-only rule also needs a scoped capability probe: today’s sudo -v/true probes can reject such a rule. Sudo behavior, sudoers command arguments
Own cleanup separately from VM lifecycle
| Event | Proposed ownership and residual state |
|---|---|
First setup / boot / up |
Prepare networking/helper as appropriate; offer the explicit DNS workflow. Publish only on opt-in; inventory intent may precede VMs. No implicit hosts synchronization |
| Repeat sync / guest provisioning | Pigsty regenerates the complete selected view; Barn compares and publishes. Provisioning does not silently elevate the host |
stop, start or restart |
Keep names. Report that a resolved address may be offline; no root request just for lifecycle |
Node removal, destroy or deployment purge |
Keep the installed block; display lingering names and the explicit cleanup command. With a desired source, report its diff; with only applied state, diagnose absent nodes without guessing service intent |
hosts uninstall |
Remove only the Barn block; preserve current outside content, VMs, disks and guest DNS |
Delete BARN_HOME / reinstall tool |
Cleanup remains possible from the native block. Reinstall a compatible CLI/helper and run hosts uninstall; neither deleted state nor remembered source is required |
| Uninstall Barn software | Explain that removing binaries leaves host records. Offer cleanup first as a separate explicit operation; retain a documented compatible-reinstall or administrator block-edit recovery path |
Proposed diagnostics compare source kind, canonical record generation and current bytes, rather than merely noticing a begin marker. They distinguish stale desired exports, unavailable applied state and stopped guests. A fresh publication is not a service health check.
Old Pigsty Vagrant DNS uses per-line # pigsty dns comments, not a paired block, and maps a fixed alias list to the first VM. Barn must preserve these foreign lines: same-address duplicates remain; different addresses conflict. Migration first inventories the marked lines, compares them against the complete new view and highlights old-only names. An explicit Pigsty migration step or administrator then removes only reviewed old records, with a fresh file comparison, before Barn publishes. Do not delete by .pigsty suffix or reuse the old script’s broad substring deletion. Barn’s narrow helper must not acquire Vagrant-line ownership. Verify duplicates are gone so later Barn uninstall really removes the intended names.
Treat service reachability and HA as separate contracts
pg_primary_ip is the inventory’s unique declared primary, including relevant external hosts; it is not the current Patroni leader. The audited failover callback does not update DNS. Direct PostgreSQL connections to that address retain static-node semantics. HAProxy role ports at that same address can route to the current backend role, but the proxy node’s address must itself remain reachable. A reachable VIP or redundant proxy entry point provides a stable publication target; vip-manager can move the PG VIP according to the DCS leader. DNS server redundancy, correct service target and successful client reconnection need separate tests; existing TCP connections do not migrate when DNS changes.
Nine audited configuration paths have reserved PG VIP conflicts: demo/debian, demo/el, fat, ha/dual, ha/full, ha/octo, ha/safe, ha/simu, ha/trio. The current pigsty.yml separately has .2 conflicts. build/dev and build/oss need explicit host-facing i.pigsty selection; demo/demo needs a short Barn name instead of its FQDN nodename. Correct all dependent records, URLs and connection settings together.
Candidate .100/.101/.102 addresses had zero literal matches in raw and YAML-decoded scans of 54 configuration paths, current pigsty.yml and 7 role defaults: 62 paths total. This only supports allocation within that audited corpus. It is not an IPAM reservation or a check of DHCP leases, ARP, external nodes, generated expressions or the live network. VIP collision and failover tests remain required on each target platform; relaxing Barn’s reserved-address validator cannot solve a network conflict.
Upgrade to split DNS only with a complete namespace
Keep legacy .pigsty unchanged by default. For an optional new zone, prefer the narrowly scoped pigsty.test; .test is reserved for testing, whereas .pigsty has no special-use reservation. Avoid .local, which belongs to mDNS. Changing zones also requires coordinated records, portal URLs and certificates; a routing rule alone cannot rename an application. IANA registry, RFC 6761, RFC 6762
Suffix forwarding does not solve current short names. Neither /etc/resolver/pigsty nor Linux ~pigsty automatically sends bare meta, pg-meta or pg-meta-1 to Pigsty. The audited cluster suffix defaults empty; instance records have no suffix. Before enabling a route, publish corresponding node/instance FQDNs and, for clusters, an explicit pg_dns_suffix, such as .pigsty.test. Keep finite hosts aliases for short-name compatibility by default. Search-domain installation is a separate opt-in and works only when the completed FQDN actually exists. A local high-port daemon has exactly the same routing prerequisite. systemd routing versus search
macOS uses one owned resolver file
Proposed configuration, not installed:
macOS supports domain-specific resolvers and explicit ports. The filename supplies the domain only when domain is absent; longest suffix matching applies. The proposal uses an explicit domain and no automatic search list. Refuse an existing same-zone route or owned-path collision, including VPN configuration. Create only the missing owned file/directory with appropriate root ownership; keep backups and staging content outside the resolver directory. Verify activation with scutil --dns, dscacheutil and system getaddrinfo(). Direct dig @server tests the DNS server, not macOS native routing. Reinitialization and cache convergence need target-version acceptance. Apple resolver manual, Apple configd parser
Installation, endpoint changes and removal require privilege; guest record updates with an unchanged route do not. Uninstall deletes only the still-matching Barn file and verifies route disappearance. Preserve VPN/default resolvers and later user edits; never restore a whole old DNS configuration.
Linux must detect the resolver and its owner
Detection is read-only: inspect resolved’s running state, NSS hosts: ordering, the /etc/resolv.conf target and the effective NetworkManager DNS plugin. A file listing upstreams directly can bypass resolved’s per-link routing. An active NetworkManager alone proves no split-DNS capability. Probe supported versions/features rather than changing the host’s DNS stack. resolved client paths, NetworkManager plugins
| Supported adapter, proposed | Persistent installation and activation | Removal and limits |
|---|---|---|
| networkd + resolved, on an exclusively Barn-owned link | Owned drop-in for the verified Barn .network: DNS=10.10.10.10, Domains=~pigsty.test, DNSDefaultRoute=no; reload/reconfigure only that link |
Remove matching own drop-in, reread effective state; preserve other files and settings |
| NetworkManager + resolved | Address the Barn profile by UUID; set its DNS and ipv4.dns-search=~pigsty.test, reapply and verify resolved |
Restore only owned properties still matching applied values; never delete a shared profile or change global plugin |
| NetworkManager with existing dnsmasq plugin | Use the Barn profile’s conditional forwarding; custom-port support requires separate detection | Same profile ownership rules; do not enable a new global plugin automatically |
| Runtime resolved | Only an active, exclusively Barn-owned non-loopback link; explicitly report lack of reboot persistence | Whole-link revert only when all affected configuration is demonstrably Barn-owned; never on shared/VPN/physical links |
| Unsupported resolver or ambiguous ownership | Explain the missing capability and retain hosts/manual mode | No global nameserver or /etc/resolv.conf takeover |
Never generate ~.. DNSDefaultRoute=no cannot cancel a catch-all domain. Preserve priorities and DHCP/VPN configuration; do not use negative NetworkManager priorities to win unrelated zones. Port 53 is the initial guest-forwarding baseline. Do not copy newer resolved custom-port syntax blindly into older NM profiles. The loopback interface is not a general resolved routing scope; a localhost daemon needs a supported routing mechanism. Last-guest shutdown can remove barn0 carrier or the link itself, invalidating its DNS scope and allowing queries to follow default routes. Strict private-zone non-leakage therefore needs a stable route, server-side negative policy and real tests. networkd DNS settings, NM priority/search, resolvectl reset scope, resolved link eligibility
Both platforms need a root-owned journal outside deployment state recording owned paths/profile UUID, prior existence, applied values and metadata. Prepare and verify upstream UDP/TCP service before enabling routes; compare the reviewed configuration again before writing; verify native resolution afterward. Roll back only still-owned deltas, reporting partial state on drift or interruption. A stored ifindex is not a durable identity. Switching from hosts requires removing overlapping Barn records explicitly and reporting foreign hosts overrides. NM profile persistence and runtime activation must both be checked. nmcli persistence and reapply
A daemon must earn its lifecycle cost
A future daemon should bind loopback only, preferably a supported high port; accept bounded records from the intended user; validate a complete generation before atomic activation; retain the last good generation on bad input. Fixed root-owned service/routing configuration must not execute user-writable programs or arbitrary includes as root. Installation, service upgrades and routing changes remain privileged. User agents end at logout; a system service adds installation and maintenance. Test startup order, restart backoff, port occupation and process identity. Remove routes and verify removal before stopping an intentionally uninstalled daemon. A crash should be diagnosable without silently forwarding private names publicly. Apple high-port implementation
Hosts has no wildcard behavior. Future wildcard DNS should cover only a deliberate shared ingress subzone, not all Pigsty names. Define NXDOMAIN for unknown names and NODATA for absent record types; dnsmasq domain matching and local-zone policy require careful configuration. Support UDP and TCP. An AAAA answer and the transport used to query DNS are independent: do not invent unreachable IPv6 addresses or ::1. Barn’s present export remains IPv4-only; dual-stack records require a new adapter contract. dnsmasq manual, RFC 3596, RFC 7766
System, application and negative caches can retain older results. Browser/SDK DoH may bypass native routing. Existing hosts entries can mask DNS, subject to the actual NSS path; VPNs can change both route reachability and zone selection. Test these paths and report measured convergence instead of promising instant updates or flushing every cache on every sync. DoH client policy
Evidence establishes a prototype, not platform acceptance
| Recorded offline evidence | Result and interpretation |
|---|---|
| Barn hosts/config/helper/CLI tests | 16 top-level tests passed; temporary-file writes and preview coverage, including malformed blocks, conflicts, reserved addresses, helper pairing and no-deployment inventory preview |
| Pigsty extractor tests | 11 passed; application subprocesses mocked, sanitized projection and preview behavior covered |
| Adversarial fixtures | 21 observations reproduced, including four Ansible inventory comparisons; these expose gaps, not 21 correctness passes |
| Configuration corpus | 54 paths, including a symlink alias; 53 Barn-VM-valid, 42 extractor-accepted. Parser acceptance does not certify role equivalence or deployment success |
| Actual prototype preview | conf/meta.yml produced 10 names and applied: false; native hosts bytes remained unchanged |
The adversarial evidence found ignored group_vars/host_vars, different sibling precedence, multiline/escaped-newline misaddressing, skipped virtualhosts exported, external-role omission and incomplete address collision checks. It also exposed mixed-view ambiguity, unrepresentable multiple addresses and incorrectly normalized explicit CIDR targets. PG aliases under dns_enabled: false require the explicit host-convenience policy above; their existence alone is not a defect. Silent wrong addresses and claims of deployed-DNS equivalence are defects.
Offline review retained fixtures, per-template results, commands and source fingerprints. No private configuration or native-hosts hashes are reproduced here. Barn DNS-related files matched those fingerprints during synthesis even though concurrent work moved local HEAD to 158786a1fee011edf3a1673400b8dd40ba9a7261; Pigsty’s checked baseline is 1ee6597dd806605b600e90cf01948277748b45f3. Unpublished evidence lives in Barn internal/hostconfig, internal/config, cmd/barn/hosts_inventory_test.go, and Pigsty bin/sandbox-dns / tests/test_sandbox_dns.py; these are paths, not claims that public baseline links contain the prototypes.
To reproduce the targeted tests with the same candidate, first set DNS_AUDIT_DIR to the absolute path of an existing disposable directory outside the checkout; the commands use it for all caches and state. These are reproduction instructions, not additional checks performed during this documentation synthesis:
The recorded environment used Go 1.27.3 on darwin/arm64, Python 3.14, PyYAML 6.0.3 and Ansible 2.21.5 for inventory comparisons. Reproduce corpus counts by enumerating sorted conf/**/*.yml paths including symlinks, running candidate barn validate --json, then the same extractor. Promote the saved adversarial fixtures into repository tests before release. No sudo, system configuration changes, VM operations or playbooks were executed in this research. Real resolver activation, VPN behavior, cache timing and HA remain pending; the main documentation workflow runs the site check separately.
Implement in stages with explicit acceptance gates
| Phase and owner | Work checklist | Acceptance gate |
|---|---|---|
| 0 — Pigsty semantics | Separate output views; fix silent parser/override/virtualhost errors; handle or reject external roles; settle CIDR rules; correct reserved VIPs, multi-infra selection and VM identities | Every supported static profile has reviewed expected records and provenance; unsupported inputs fail visibly; 21 adversarial observations become expected-result regressions |
| 1 — Pigsty exporter + Barn hosts | Limited literal export; exact existing projection; source-kind guard and explicit applied replacement; full offline diff; frozen input; structured failures; helper scope validation and cooperative lock | Inventory services survive attempted bare install; no-op needs no sudo; every actual write verifies; targeted tests and required repository checks pass |
| 2 — integration and HA | Shared pure rules/effective-variable adapter; controller-side rendered/observed export; lifecycle diagnostics, migration and reinstall cleanup; real platform validation | Per-view role equivalence and supported-profile coverage; host access and VIP failover on macOS arm64 and Linux amd64, extending to other advertised hosts |
| 3 — optional split DNS | FQDN publication, stable endpoint, capability detection, ownership journal and platform adapters | Suffix isolation, persistence, rollback and failure matrix pass; short names retain their stated contract |
| Demand gate — local daemon | Measure update frequency or RR requirements unsatisfied above; define service identity and lifecycle | Demonstrated benefit exceeds service maintenance; outage, logout, port and upgrade tests pass |
| Required test area | Concrete acceptance cases |
|---|---|
| Semantics | Overrides, sibling groups, defaults/templates, multiline data, portal conditions, PG target branches, MinIO service-name origin, vm_skip, multi-infra and multi-address refusals |
| Source and file safety | Inventory → bare rejection; explicit applied replacement/removals; legacy migration; case conflicts, trailing dots, duplicate/malformed markers; block-external edits; missing state; crash/race/stale-plan and helper scope tests |
| Idempotence | Repeated snapshots; irrelevant credential/resource edits; generation drift; source-mode-only migration; no root prompt on no-op; noninteractive permission failure |
| Native access and HA | Portal Host/TLS routing, node/PG short names and FQDNs, PG direct versus proxy ports, VIP reachability/failover, external-address collision, new versus existing connections |
| Resolver integration | Native macOS lookup; Linux NSS/stub/bypass paths; UDP/TCP, A/AAAA/NODATA/NXDOMAIN, zone apex/subdomains, public/VPN isolation and identical upstream views |
| Persistence and recovery | Reboot, logout, guest/DNS outage, last guest stopped, interface recreation, DHCP renewal, VPN reconnect, caches/DoH, partial apply, foreign edits, uninstall and reinstall |
Keep remaining choices bounded with defaults
| Product question | Recommended default |
|---|---|
| Enable host changes automatically? | No; explicit Pigsty preview/apply, lifecycle diagnostics only |
| Which view/source? | Literal desired host-access for supported profiles; rendered/observed are explicit, labelled alternatives; no inferred deployed authority |
| Multiple infra or external endpoints? | Reject ambiguity; explicitly choose ingress/view. Out-of-subnet addresses use manual/existing DNS until a separate capability is designed |
| Source migration/version? | Preserve bare legacy/applied behavior; guard inventory blocks; capability-check the first release carrying the guard and matching helper; no safe-downgrade promise |
| Name canonicalization? | Lowercase ASCII for comparison/output; deduplicate equal mappings, reject conflicting addresses; keep a node’s canonical name first where present. Reject trailing-dot input in Phase 1 and detect outside equivalent spellings conservatively; accept no silent normalization divergence |
| Namespace and short names? | Keep .pigsty; optional pigsty.test for new routing; explicit hosts short aliases, no automatic search domain or certificate rename |
| Privilege and cache policy? | Per-call sudo; no persistent grant or automatic global cache flush. Measure convergence before setting a documented target |
| VIP and service lifecycle? | .100/.101/.102 are audited-template candidates only; verify allocation. Retain names across stop/destroy; explicit cleanup survives state deletion |
| General exporter and daemon timing? | Ship a strict useful subset first; expand through per-view equivalence gates. Defer a daemon until demand and maintenance ownership are established |
2.13 - In-place root growth: recovery and 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
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.
2.14 - Barn 1.2.0 release readiness and acceptance
Status, 2026-11-12: Barn 1.2.0 is published as stable/latest. Its final acceptance is recorded below; the candidate sections preserve the earlier review and its original scope.
Final release
Tag v1.2.0 points to 90f615804b823b4eb491101e642073dcd37a9700. Source/Mac CI and complete packaging CI passed that exact commit before tagging. All 437 reviewed build inputs match the candidate manifest. The signed catalog gate passed again before formal packaging.
The release used the documented local signing path because GitHub signing secrets remain unconfigured. Apple accepted notarization 165b486b-5055-4eca-a595-54bed6bdd6c1. make release-local passed archive, DEB/RPM, SPDX, helper-pairing, timestamp, and package/archive checks. Downloaded CLI/helper/app bytes passed Developer ID, staple, and Gatekeeper checks. All 20 draft attachments and all 20 anonymous public downloads match the verified sizes and SHA-256 digests.
The final downloaded archives passed 18 Linux/KVM operations and all 15 native Mac lifecycle phases. Additional native UI checks confirmed desktop login, two-way plain-text clipboard transfer, a closed window leaving the VM and SSH running, and normal shutdown. Isolated 1.1.0 → 1.2.0 upgrade and repeat installation passed. Both darwin/arm64 and linux/amd64 passed public latest and pinned installation. The Homebrew update covers four platforms; real 1.1.0 → 1.2.0 upgrade, brew test, style, and strict online audit passed.
Three-platform Homebrew CI passed e633aee on Linux, Apple Silicon, and Intel macOS, covering syntax, style, updater tests, readall, and audit. Intel setup previously built OpenSSL while automatically migrating the runner’s preinstalled Azure CLI. Limiting setup to metadata refresh resolved that delay; all subsequent formula checks remained enabled.
The final Mac run retained an initial Apple HTTPS TLS timeout. The identical request subsequently returned HTTP 200; the harness resumed after rechecking both guest identities, disk sentinels, and the base digest, then completed the remaining phases. The test VM was stopped after GUI acceptance. GitHub direct-download timeouts were resolved using the hosts’ existing proxies; the public downloads used no authentication. Failure logs were retained.
The homepage keeps its explicitly dated 1.1.0 recording and explains the 1.2.0 default/output changes. A fresh four-node first-run recording needs free .10–.13 addresses; existing host VMs occupied them during this release. Release identity and public-channel evidence are also listed in Validation History.
The 1.2.0 site update at b9e6498 passed site CI and became the active Cloudflare production deployment. The production build checked 303 HTML pages; 19 public checks covered bilingual release, installation and command pages, canonical URLs, sitemaps, search indexes, robots, and a real 404. The final browser connection timed out, so visual evidence retains the earlier local spot-check scope.
Candidate identity
This table records the state at the start of preparation, before the final release commits.
| Item | Reviewed state |
|---|---|
| Source repository | Local main at 6ab0cd5, three commits ahead of fetched origin/main at b2cc8da; additional hosts changes remain uncommitted |
| Documentation repository | Local main at bc7968a, three commits ahead of fetched origin/main at d392cd5; the command reference and corrections remain uncommitted |
| Snapshot | .goreleaser-preflight-1.2.0-20261112, version 1.1.1-next, binary commit uncommitted |
| Source inputs | SHA-256 9a4d4dd378d4b5762a4d06bd8e19830a09e0919e808f5fffc4c154fbdf55e01a for the manifest of 437 source, test, native, and packaging files |
| Catalog | Revision 2026110502, default u24:stable on the native architecture |
The snapshot version follows the existing incpatch rule from the last tag, v1.1.0. Formal 1.2.0 assets were subsequently rebuilt from the final v1.2.0 commit. Snapshot binaries deliberately identify the modified tree as uncommitted.
Issues resolved during preparation
- The default shell selected GoReleaser 2.18.3 while the source requires 2.20.1. The validation run used the already downloaded pinned tools, and
packaging/check-toolchain.sh allconfirmed every version before packaging. - The changelog and release-note drafts omitted the Linux default change from 2 vCPUs / 4 GiB to 1 vCPU / 2 GiB. They now explain how to preserve the earlier defaults with explicit
vm_cpu: 2andvm_mem: 4GiB. Both binaries were checked against implicit and explicit inventories. - The release drafts now include
all.vars.vm_hostsandhosts install -f. Their implementation and tests subsequently entered commitad74cc9. The candidate’s inventory-only preview was exercised before VM creation. - Root-level
.idea/and the localbarn.ymlare now ignored and were retained on disk. Existing source edits and historical-document deletions were preserved.
Completed gates
| Gate | Evidence |
|---|---|
| Source | make check passed: unit/race tests, static analysis, four-target builds, installer and image-pipeline boundaries, and licenses |
| Native components | make mac-native-test passed bridge, image, menu, and exit-prompt tests |
| Catalog | Both official mirrors passed signature verification, activation in new data roots, embedded-byte comparison, and size/availability checks for 17 stable artifacts each |
| Packaging | make release-snapshot passed with BARN_MAC_REQUIRE_PAYLOAD=1 and the freshly built native payload; archives, Linux DEB/RPM, SPDX, checksums, helper pairing, timestamps, and package/archive parity were verified |
| Linux runtime | 18 operations passed on Ubuntu 26.04.1 / x86_64 with KVM, using the extracted snapshot archive |
| Mac runtime | All 15 automated lifecycle phases passed on macOS 27.0.1 / arm64 with Xcode 27.2, using the extracted snapshot archive |
| Upgrade | A VM created by the published 1.1.0 archive retained its process and identity when adopted by the candidate; root growth and teardown passed |
| Documentation | Production make check and a separate build including drafts passed internal-link and asset checks for 301 and 303 HTML pages; browser spot checks covered the Chinese command index, English lifecycle reference, and Chinese readiness record |
The catalog bytes retain SHA-256 d41e46d20bc57cb1b071e3919a27a8f7f703df2addca879970a528e7ab004065, so no embedded catalog update was needed. The catalog gate did not stream every artifact to recompute payload digests; the Linux acceptance image downloads did verify their selected payloads. govulncheck found no reachable vulnerabilities; its unused-module report remains documented in the candidate release notes. Native offline image mutation was skipped by the ordinary source suite; the image pipeline was unchanged in this release candidate.
Linux acceptance
Two isolated guests used Debian 13 20261103.2651.1 and Rocky Linux 9 9.8.20260525.2, with 1 vCPU / 1 GiB, 12 GiB roots, and persistent 1 GiB XFS data disks. The host’s prepared Barn network was reused.
- A running ext4 root grew to 14 GiB and stayed running. Repeating the target preserved its QEMU PID; a 13 GiB shrink was rejected before mutation.
- A stopped XFS root grew to 14 GiB through temporary boots and returned to stopped. Repeating the target preserved its root-image modification time.
- A deliberately failing
resize2fsleft a recoverableguestjournal for a 16 GiB target. Restoring the tool and retrying completed filesystem growth. - Killing the CLI after
qemu-imgenlarged the image left theimagejournal at an 18 GiB target. Retry completed growth and restored running state. - VM UUID, generation, disk serial, partition identity, filesystem UUID, SSH host key, and root/data-disk sentinels survived growth and recovery. Serial stop (
-p 1) and unrestricted start (-p 0) passed. - Test nodes and their explicitly selected persistent disks were destroyed. The host’s SSH configuration and
/etc/hostsmatched their original bytes afterward.
The initial harness attempt created a group-writable image-cache directory, which Barn rejected before creating any guest. The fixture was corrected to use private permissions, and its failed evidence was retained. This was a harness setup error.
A separate upgrade run created a guest with the published 1.1.0 archive (SHA-256 09fa37b35ac15763320898bdf81fe0051e83f6b8f3a4898fb01149e6c2611926). The candidate read its applied 2 vCPU / 4 GiB state. Adding explicit CPU/memory values to the inventory let up preserve its running PID and VM UUID; resize -D 14GiB then grew the old 12 GiB root while retaining machine ID, host key, generation, and a data sentinel. Teardown restored the host SSH configuration. An initial harness command assumed that Linux ssh quoted a sh -c argument; the documented raw-SSH contract requires shell quoting. The resumed test used an explicit quoted SSH command and retained the first failure log.
Implementation evidence is in internal/private/root_resize.go, internal/private/root_resize_guest.py, internal/private/root_resize_test.go, internal/private/concurrency_test.go, and cmd/barn/lifecycle_parallel_test.go. The earlier arm64 ext4/XFS acceptance retains its own date and measurements. MBR layouts and native Linux/arm64 hosts still have no new physical-host replay in this round.
Mac acceptance
The test root used an APFS clone of the prepared macOS 27.0 base (26A428). Its configuration disabled automatic host SSH integration. The 15-phase harness covered two independent VMs, guest account policy, private networks, DNS/HTTPS, remote argument and exit-code preservation, shared-folder writes, stop/start, CPU/memory reconfiguration, the two-VM host limit, recreate identity rotation, base-image immutability, repeated up, and destruction of one VM while preserving its peer. The survivor was stopped after the automated run.
The packaged app also opened the survivor’s desktop, and visual inspection confirmed automatic login to the macOS Finder desktop. The VM was then shut down normally. This check did not establish the close-window behavior or clipboard transfer. The snapshot used ad-hoc signing; the final Developer ID/notarized release subsequently passed Gatekeeper, installation, window-close, and clipboard checks as recorded above.
Release execution
- Source changes were committed in
ad74cc9and90f6158; the command reference and documentation review were separately committed in24ff1e2. Both repositories were pushed and their CI passed. - Source and packaging CI passed
90f6158, thenv1.2.0was tagged and pushed. - The catalog gate passed, and the local Mac release procedure produced matching signed/notarized assets.
make release-localverified the complete release. - Final archive acceptance and all 20 draft re-downloads passed before stable/latest publication. All 20 public downloads and both platform installers passed afterward.
- Homebrew and the bilingual guides were updated to 1.2.0, including default resources, command options, upgrade notes, and release evidence.
This release used an existing local Developer ID identity and stored notary profile. No keys were exported or signing credentials changed.
2.15 - Running Barn from Windows
Barn’s current host profiles and release binaries cover macOS and Linux on amd64 and arm64. There is no native Windows Barn release. A Windows user can operate Barn through a Linux host; WSL2 and a nested Linux VM are candidates for bringing that host onto the Windows machine.
This assessment was reviewed on 2026-11-08. The WSL2 and Hyper-V paths below still require a complete Barn lifecycle test before they can become supported installation guides. The current platform contract is in platforms and limits and internal/platform.
Choose where Barn runs
| Host environment | What it reuses | What must be established |
|---|---|---|
| Remote Linux/KVM host | Existing Linux Barn binary and host integration | SSH access, suitable resources, and the normal Linux host checks |
| WSL2 distribution | Linux binary, QEMU, OpenSSH, and Linux filesystem | Nested KVM, systemd, bridge helper, private network, and Windows-to-guest access |
| Full Linux VM on Hyper-V | A conventional Linux installation and network manager | Nested virtualization, /dev/kvm, resources for both VM layers, and access from Windows |
| Native Windows/QEMU | QEMU’s WHPX accelerator | A new Barn host backend, network integration, installer, and acceptance matrix |
For a local experiment, WSL2 is the first candidate because it can reuse the Linux implementation. A remote Linux/KVM host remains the direct path when nesting is unavailable or when performance matters. Docker Desktop adds a VM/container boundary around the host functions Barn needs; it is outside this proposed installation path.
WSL2 has two separate configuration scopes
WSL2-wide VM options belong in %UserProfile%\.wslconfig on Windows. Per-distribution boot settings, including systemd, belong in /etc/wsl.conf inside Linux. Microsoft’s configuration reference describes the distinction and the nestedVirtualization option. A configured option still needs a usable KVM device and a working launch on the actual machine.
Keep BARN_HOME, cached images, and guest disks in the WSL Linux filesystem, such as the default ~/.barn. Microsoft’s filesystem guidance recommends Linux filesystem storage for Linux workloads. The outer WSL virtual disk stores the Linux filesystem; Barn’s inner qcow2 files remain separate guest disks managed by Barn.
PowerShell can invoke the Linux CLI through wsl.exe, but the state, SSH configuration, host shares, and private bridge still belong to that Linux environment.
Acceptance before installation advice
A WSL2 or Hyper-V Linux candidate should pass these checks in order:
- Confirm the Linux init/network manager works and the current user can access
/dev/kvm. Start a small QEMU probe with KVM; checking only the device path is insufficient. - Run
barn setup --dry-runto inspect dependencies and network changes. Then verify the installedqemu-bridge-helper, its distribution ownership and permissions, and thebarn0bridge using Barn’s normal Linux checks. - In a disposable lab, boot a native-architecture guest and reach SSH. Verify outbound traffic, host-to-guest access, and communication between two guests on the private subnet.
- Test the actual Windows access path. Check the management endpoint and any required service separately from reachability of the private subnet.
- Stop and start the guests, restart the outer Linux environment, and verify recovery, persistent disks, and cleanup of Barn-owned network resources.
Windows-to-WSL localhost forwarding or mirrored networking does not establish routing to the nested guest subnet by itself. Record the chosen route or tunnel and its firewall requirements with the test result. For a full Hyper-V Linux VM, nested virtualization also depends on the outer host configuration.
Barn selects TCG for foreign guest architectures and specific image compatibility cases. It does not silently substitute TCG when a normal native KVM launch fails. A WSL installation without working native acceleration therefore cannot be advertised as a normal Linux Barn host.
A native Windows backend is a separate implementation
QEMU exposes hardware acceleration through WHPX. Barn would still need to implement the host contracts around that accelerator:
| Area | Current dependency | Windows work |
|---|---|---|
| Platform and packaging | Darwin/Linux profiles and release targets | Windows profile, dependency detection, binaries, and installer |
| Process identity and recovery | POSIX process evidence and signals | Windows process handles, identity checks, cancellation, and recovery |
| State and locks | Unix filesystem metadata and flock |
Equivalent locking, permissions, and atomic state handling |
| QMP and host shares | Unix sockets, inherited directory descriptors, Virtio-9p | Supported transports and validated sharing semantics |
| Private network | barn0, bridge helper, and Linux network manager |
A Windows backend with explicit privilege and firewall ownership |
The implementation boundaries are visible in internal/process, internal/lock, internal/hostshare, and internal/network. A wrapper around the existing binary cannot supply those contracts.
Vagrant illustrates why provider-specific behavior must stay explicit: its Hyper-V limitations state that Vagrantfile network settings are ignored and the user selects an existing virtual switch. Barn’s fixed-address lab needs its own network guarantee before Windows can join the supported host matrix.
2.16 - Beyond amd64 and arm64
Barn currently supports amd64 and arm64 Linux guests. QEMU can emulate more architectures, but each additional Barn target needs an image and a complete boot-and-operation contract. This design assessment, reviewed on 2026-11-08, separates the existing runtime policy from possible future targets.
Hardware acceleration follows the host ISA
For Barn’s current 64-bit guest targets, native virtualization uses the host’s instruction set: HVF on macOS and KVM on Linux. QEMU TCG translates instructions for a foreign guest architecture. Rosetta can translate Intel Linux applications inside an Arm Linux VM; it does not provide an Intel Linux VM on Apple Silicon. See QEMU’s emulation overview and Apple’s Linux binary translation guide.
| Host | amd64 guest | arm64 guest | Current native release evidence |
|---|---|---|---|
| macOS arm64 | TCG | HVF, subject to image compatibility | Tier1 |
| macOS amd64 | HVF | TCG | Tier2 |
| Linux amd64 | KVM | TCG | Tier1 |
| Linux arm64 | TCG | KVM | Tier2 |
Barn’s tiers describe release evidence. Native Darwin arm64 and Linux amd64 have the primary live acceptance coverage; Darwin amd64 and Linux arm64 remain Tier2 until equivalent physical-host evidence is recorded. All TCG profiles are Tier2. A binary that cross-compiles is not sufficient evidence to promote a runtime tier.
There are two image-specific restrictions: stock EL8 arm64 uses TCG on Apple Silicon because of its 64 KiB translation-granule requirement, and EL7 is limited to native Linux/amd64. An unexpected failure of native acceleration fails the operation rather than silently selecting TCG. The authoritative policy is in platform.go and selectRuntime.
Keep the complete emulated boot profile
The existing arm64 TCG profile uses qemu-system-aarch64, the virt machine, and cortex-a72. Barn’s arm64 images require UEFI, with a matching firmware code image and a writable per-VM variable store. The target emulator and firmware must be installed even when the host architecture differs; Linux setup currently plans native-host dependencies.
For amd64 guests on arm64 hosts, Barn explicitly uses tcg,thread=single to preserve the stronger x86 memory-ordering contract. Other emulated profiles use tcg,thread=multi. Increasing the vCPU count therefore does not imply more translation threads in every configuration.
QEMU’s virt documentation lists cortex-a72 as a 64-bit CPU and warns that the max model can change across QEMU versions. A prior Barn investigation observed a firmware hang with one max configuration and a successful boot with cortex-a72 plus explicit pflash code/vars. Because the firmware arguments also changed, that observation cannot isolate the CPU model as the cause. It supports retaining the tested profile while investigating any change with a controlled comparison.
Keep guest architecture explicit in inventory and catalog metadata. qcow2 metadata describes the disk container, not the guest instruction set. Selecting a foreign image requires both the correct image architecture and a matching runtime profile.
Each new architecture needs different boot assumptions
The following are QEMU integration candidates, not supported Barn values:
| Candidate | QEMU target and machine | Boot contract to establish |
|---|---|---|
ppc64le |
qemu-system-ppc64, pseries |
Little-endian guest; SLOF or a validated VOF/direct-kernel path; pSeries console and devices |
s390x |
qemu-system-s390x, s390-ccw-virtio |
CCW/virtio devices and explicit bootindex; replace PC-style boot assumptions |
riscv64 |
qemu-system-riscv64, virt |
OpenSBI/U-Boot or a validated EFI path; device tree, CPU extensions, and serial console |
loongarch64 |
qemu-system-loongarch64, virt |
A validated CPU model such as la464, matching EDK2 firmware, and compatible guest image |
The machine and boot differences are documented by QEMU for pSeries, s390x boot devices, RISC-V virt, and LoongArch virt. All four candidates would use TCG on Barn’s current amd64/arm64 hosts. Native Linux hosts for those instruction sets would be a separate host-support project.
Distribution support has several levels: a released CPU port, an installer, a root filesystem, and a generic cloud disk. A candidate must identify which one is available, its immutable source and checksum, and whether its bootloader and cloud-init configuration match the selected QEMU machine. Do not synthesize a download URL by replacing the architecture in another file name. The image evidence workflow applies before any candidate enters the catalog.
Implementation boundary
Adding a name to the architecture parser would leave the rest of the product incomplete. A target needs coordinated changes in:
- Platform and image schemas: canonical architecture names, aliases, validation, catalog entries, and release tier. Existing catalog readers reject unknown fields and unsupported architectures, so the rollout also needs an explicit compatibility policy for older clients.
- QEMU invocation: executable, machine, CPU, firmware/boot method, console, disk and NIC transports, and QMP identity.
- Guest initialization: seed delivery, user and key setup, interface naming, data-disk detection, and filesystem support.
- Host preparation: dependency packages, minimum QEMU capabilities, preflight diagnostics, and available firmware.
- Lifecycle operations: cold boot, SSH readiness, stop/start, resize capabilities, persistent disks, recreation, cancellation, and interrupted-operation recovery.
The existing entry points are internal/platform, internal/qemu, internal/cloudinit, internal/setup, and the image pipeline.
Acceptance for a new target
Start with one generic cloud image per candidate. Record its source and digest, QEMU version, full launch profile, and firmware. In a fresh temporary data root, verify cold boot, the expected uname -m, SSH identity, outbound traffic, host access, peer communication, data mounts, and restart. Then test recreate/destroy boundaries, preserved disks, and recovery after cancellation. Unsupported optional features need explicit diagnostics.
The research suggests evaluating ppc64le and s390x first, followed by riscv64 and then loongarch64. This order is an engineering hypothesis based on image availability and boot integration work, not a committed roadmap; a better validated image or a concrete user workload can change it.
Measure functional compatibility and performance separately. TCG is useful for installation, packaging, and architecture checks; database throughput and compilation benchmarks need measurements on representative native hardware before informing capacity decisions.
2.17 - Evolving the CLI Without Breaking Its Contracts
Barn already uses Cobra. The useful part of the 2026-11-04 CLI proposal is its separation of command syntax, application orchestration, and observable behavior. This essay retains that design, checked against v1.1.0 on 2026-11-09. The package split and broader command reorganization remain proposals; current usage belongs in the CLI reference.
Keep the existing boundaries
cmd/barn/command_*.go defines the command families. Each execution constructs a fresh Cobra tree, avoiding flag values leaking between calls. Command handlers collect a commandOutcome; a shared renderer handles text or structured results, while typed failures retain their exit code, reason, recovery hint, and any partial result. Linux lifecycle, image verification, and native Mac behavior remain in their domain packages.
These boundaries already protect several contracts:
| Contract | What a refactor must preserve |
|---|---|
| Deployment selection | One Linux deployment under BARN_HOME; named Mac VMs remain under its mac/ subtree |
| Configuration source | Desired-state commands resolve inventory; start, stop, status, and access use applied state |
| Machine-readable output | Existing payload fields and types, JSON/YAML parity, and useful partial results on failure |
| Interactive access | Real terminal descriptors, child-process signals, and remote exit status |
| Help and completion | Existing aliases, local flags, platform scope, and offline completion |
Moving a command must not turn an invalid inventory into a fallback to old state, reinterpret init -o FILE as an output format, or collapse up, restart, reload, and recreate into one operation. Their differences describe which configuration is read and which resources may change.
Resolve presentation without parsing twice
The current prepareOutput adapter scans presentation options before Cobra, using commandFlagValues to distinguish flags from values. A future simplification should let the same command tree drive normal parsing, help, completion, and exported command descriptions. It still needs a narrow way to choose error presentation when parsing itself fails.
The invariant is explicit CLI value → selected environment default → built-in default. Validate the selected value after resolving precedence. Barn 1.0.0 already fixed valid CLI overrides being rejected by an invalid environment default. Preserve that fix, explicit boolean false, and values that resemble flags: -o --json may name a file, while anything after the remote -- boundary belongs to the guest command.
Human help remains text with --json --help. Bare barn prints full help in text mode and retains actions[] in structured mode. A proposed machine-readable help schema would be a separate, explicit interface generated from the command tree.
Separate results from streams
Single-result JSON/YAML commands emit one result document on stdout. Progress, warnings, and diagnostics use stderr. Preserve command-specific payloads such as .nodes and .results; wrapping every response in a new data object would break existing automation.
SSH and followed logs need streaming paths. They must retain empty arguments, quoting, a second --, cancellation, and the guest’s nonzero exit code. Structured interactive SSH sends session output to stderr and the final report to stdout; followed logs emit NDJSON or a YAML document stream. A renderer cannot safely buffer an interactive session into an ordinary result object. Deprecation notices must also stay out of structured stdout.
Move responsibility before changing behavior
The proposed structure has three layers:
| Layer | Responsibility |
|---|---|
cmd/barn |
Process entry, signals, and exit status |
Proposed internal/cli |
Cobra tree, argument validation, presentation, and stream wiring |
| Application orchestration and existing domain packages | Typed requests and reports, lifecycle work, network setup, images, and Mac VMs |
Inject the streams, environment reader, prompter, and runners actually needed by each operation. Keep domain services independent of Cobra and avoid a generic dispatcher that erases request types. Confirmation and SSH must use the injected input stream throughout; setting a Cobra input stream while reading global os.Stdin elsewhere leaves tests and embedded calls inconsistent.
The proposal moves side effects after validation. In the locked Cobra 1.10.2, pre-run hooks precede required-flag and flag-group validation, so they should perform only normalization and checks. Current lifecycle commands intentionally record a begin event before some configuration checks to make early failures traceable; changing that ordering needs an explicit audit-policy decision. A new --dry-run option needs a real read-only planner with the same scope as execution; calling a mutator and suppressing its final write is insufficient.
Confirmation, permission, and execution are separate facts. Existing network and hosts commands can successfully return a plan with applied=false; automation checks that field or passes --yes. Confirmation never bypasses identity, ownership, or integrity checks. Likewise, status can reconcile interrupted runtime records, so a query-like command name alone does not establish a read-only contract.
Reconcile the proposal with shipped behavior
The original proposal contains both durable contracts and dated product choices. At the 2026-11-09 review:
1.0.0already includes full bare-command help, structuredactions[], confirmation aliases on applicable destructive commands, and the output-environment precedence fix.- CLI code still lives in
cmd/barn/. The proposedinternal/cliextraction,--format,image update, andssh-config show/install/uninstallcommand tree have not landed. - Current Barn
1.1.0adds--image/-Iand--arch/-Aoverrides to applicable inventory operations, unifies result and live-progress tables (freezing in-place TTY redraws before prompts or errors), standardizes plain ASCII terminal output (->,:,;,[DONE]/[FAILED]/[CANCELLED]), makesbarn purgeexecute immediately by default with--askfor interactive confirmation, and upgrades YAML parsing togo.yaml.in/yaml/v4.
Further behavior changes need their own decision and migration notes.
Validate the contract at each step
First extract responsibilities with command behavior and state formats unchanged. Then add compatible entry points, followed by separately reviewed changes to defaults or selection scope. Compare old and new executables sequentially against controlled fixtures; help, completion, and invalid-input tests should check for unwanted files, subprocesses, or network access as well as output.
Regression coverage must include CLI/environment precedence, stdout/stderr separation, partial failures, remote argument boundaries, real PTY signals, and confirmation scope. Reuse cli_test.go, output_test.go, remote_command_test.go, and signal_test.go. Moving tests into a new package also requires updating CI package paths and build targets. VM or host-network behavior changes need the corresponding physical-host acceptance.
2.18 - The Lifetime of a Native Mac VM
barn mac manages named macOS VMs through Apple’s Virtualization.framework, independently of the Linux inventory. Introduced in 0.9.0, it uses the native host app Barn.app, included in Apple Silicon release archives since 0.9.2. This essay distills the named-machine implementation record and earlier Mac reviews, with the current implementation checked on 2026-11-08. Dated evidence is summarized in the validation history; commands and defaults are in the Mac reference.
A machine owns its network
The early design had two fixed slots and a privileged network helper. The 2026-09-29 redesign replaced that model with named machines and a private /24 NAT network created inside each runner process. The guest receives .10 on its own subnet. Two is the simultaneous-running limit, shared with other macOS virtualization tools and restore installation; stored machines are not restricted to two fixed names.
Network ownership now follows VM lifetime. Starting the runner creates the network, and stopping it releases the network. There is no separate root network daemon to install or reconcile for barn mac; the earlier helper protocol is historical context only.
This topology also supports first-contact SSH trust. On an uninitialized VM, Barn checks the runner’s identity before and after connecting to the reserved address, authenticates the guest, and pins its host key under the instance UUID. The guest is the only peer besides the trusted host on that runner’s private network. Later connections require the pinned key; changing the subnet keeps the instance identity, while recreation creates a new one. See readySSH.
Locks protect different lifetimes
The acquisition order is machine operation → shared state → native runner:
| Lock | What it protects |
|---|---|
| Machine operation | Serializes lifecycle changes to one named VM |
| Shared state | Coordinates image and machine records used across operations |
| Runner lifetime | Establishes whether the native process still owns the VM |
Runtime-only changes can use the machine lock, so preparing an image does not prevent an unrelated VM from shutting down. A stop request can interrupt that machine’s SSH-readiness wait through an operation nonce, then take the operation lock; cancellation does not grant two commands simultaneous write access. The rules live in operations.go and readiness_interrupt.go.
A successful shutdown request begins a transition. Barn waits for evidence that the runner has released the VM before treating it as stopped. SSH disconnection or an unavailable RPC socket alone cannot establish that fact. When runtime evidence is ambiguous, status reports unknown and destructive work stops. This protects disks still attached to a live VM; see status.go.
Publish a base only after restore stops
Apple’s installer completion callback can arrive while the restore VM is still running. Barn therefore waits for the installer to finish, verifies or completes the restore VM’s stop, and only then publishes a read-only base with ready=true and first_boot=false. A timeout or uncertain state prevents publication. See finishRestoreMachine.
New machines clone that base and receive their own identities and credentials. Cache reuse also checks compatibility: Apple’s online metadata may select a different hardware model for the same build, so Barn validates the saved model’s hash and current host support before reusing an existing ready base. A matching build label alone is insufficient.
Recreation adds another reference lifetime. An unfinished recreate journal may still need the old machine’s base even after the replacement is ready. Image pruning includes those journal references and refuses ambiguous records. A diagnostic list can report a bad record and continue; pruning needs a complete reference set before deleting anything. The 1.0 hardening record records this recovery decision.
Keep guest credentials within one boundary
Each machine stores its GUI password beside its SSH private key, both protected by private file permissions. The key already grants passwordless sudo inside that guest. Moving only the GUI password into Keychain would not strengthen that local access boundary, but would add signing-identity changes, locked-keychain failures, and credential migration to normal app upgrades.
Barn therefore removed Keychain storage for guest passwords; secret.go states this rationale. Developer ID signing and notarization still use their own Keychain/profile configuration. Readiness checks preserve existing SSH material, and app replacement preserves machine identity and host pins. Credentials change through explicit machine recreation, not merely because the executable was rebuilt.
Validate transitions and recovery
The historical tests exposed failures that successful startup alone would miss: first SSH can need a bounded wait; shutdown must be observed to completion; replacing an app must retain credentials; and a failed recreation must keep the base needed to recover. The validation history preserves the dates, candidate identities, reused-base conditions, and later release checks.
For a change to these boundaries, use the Mac release acceptance procedure and the existing live harness. Record whether the run includes a fresh IPSW download, prepared-base reuse, GUI/clipboard checks, or only core lifecycle steps. Unit tests cover lock cancellation and metadata failures; a physical Apple Silicon host supplies the runtime evidence for restore, networking, SSH, and shutdown.