Skip to content

This is the multi-page printable view of this section. .

Return to the regular view of this page.

Design Notes

Architectural decisions, trade-offs, and implementation boundaries behind Barn.

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.

1 - Main and command reference review

Git state, version boundaries, command coverage, and validation evidence from the 2026-11-11 Barn 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 check passed 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-check activated both official signed catalogs in fresh temporary roots, matched embedded revision 2026110502 byte-for-byte, and checked availability/size for all 17 stable artifacts on each mirror. The catalog SHA-256 was d41e46d20bc57cb1b071e3919a27a8f7f703df2addca879970a528e7ab004065. 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, and plan with both builds in isolated temporary BARN_HOME directories. 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 check passed, 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 - Why Barn Has No Projects

Why Barn replaced per-directory project state with one owner-scoped deployment driven by one Pigsty inventory.

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 /24 bridge?

A single local Pigsty lab needs none of that machinery, so we removed the project layer entirely.

Note

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.yml leaves the applied state in ~/.barn intact (barn plan and barn up can 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.

3 - Why Every Barn Node Has Two NICs

Why Barn separates management egress from the fixed-address network used by the host, peers, Ansible, and Pigsty.

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.yml with no default gateway and no DNS servers. During barn 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 a private-network warning in nodes[].warnings so you can inspect the guest and re-run barn up after fixing the host bridge or route conflict.
Note

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 pinned socket_vmnet launchd service (host mode by default, or shared mode with --mode shared) while QEMU runs under your normal user account.
  • Linux with active NetworkManager: Creates and manages the barn0 bridge via nmcli and integrates with active firewalld zones.
  • Linux with systemd-networkd: Installs dedicated barn0 networkd units and uses the distribution’s qemu-bridge-helper for unprivileged tap attachment.
  • Linux with inactive systemd-networkd: Before starting an inactive systemd-networkd service, Barn scans existing .network units 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:

  1. Inspect host state — Checks routing tables, existing interfaces, active network services, file ownership, and prior Barn state.
  2. Display the plan — Prints the exact files, interfaces, and service changes before requesting privileges.
  3. Re-verify preconditions — Re-checks for route or interface collisions immediately before applying changes.
  4. Record root-owned rollback state — Writes /etc/barn/network.json (or platform equivalent) recording both what Barn created and the prior host state.
  5. 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).
  6. 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.

4 - Declarative Does Not Mean Destructive

How Barn uses per-node hashes and explicit operations so missing configuration can never authorize deletion.

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 /24 subnet, login username, vm_arch policy, and deployment-level default image selector).
  • That node’s resolved definition (nodename, IP, vCPUs, memory, root disk, vm_disks, vm_shares, and vm_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.

Note

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:

  1. Every change that replaces a guest’s root filesystem goes through one visible, confirmed command.
  2. 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: true on 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 destroy and barn recreate preserve persistent: true data disks on disk.
  • Whole-deployment barn destroy --delete-persistent removes persistent data disks as well, and barn purge (or barn destroy --purge) removes all disks, SSH keys, and deployment state in one step.
  • During barn up recovery, 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.

5 - A PID Is Not a Virtual Machine

Why Barn combines QMP identity, process evidence, typed invocations, and journals before it signals or deletes anything.

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:

  1. 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.
  2. To stop a verified VM, Barn first sends a clean guest ACPI system_powerdown over QMP, then sends QMP quit if the shutdown timeout expires, and falls back to OS signals only when QMP itself is unreachable.
Note

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:

  1. No committed node state file exists for that node.
  2. Neither the QMP socket nor the pidfile from the journal’s QEMU invocation is present.
  3. 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.

6 - repo.yaml Is Intent; catalog.json Is Evidence

Why Barn separates human-authored image policy from generated metadata that must match the actual qcow2 artifacts.

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 revision and global defaults (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 (uefi or bios), support status, source_user, and immutable upstream provenance URLs

Crucially, repo.yaml never contains hand-copied byte counts, SHA-256 digests, or virtual disk sizes:

repo.yaml
defaults: { image: d13, channel: stable, arch: native, boot: uefi }
images:
  d13:
    aliases: [debian13, debian, trixie]
    channels: { stable: "20260810.2566.0" }
    versions:
      "20260810.2566.0":
        status: supported
        variants:
          amd64: {}
          arm64: {}

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).

Note

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:

  1. Validates repo.yaml policy declarations (boot mode, source_user, support status, and upstream provenance).
  2. Inspects the corresponding images/<image>-<version>-<arch>.qcow2 file via qemu-img, rejecting backing chains, external data files, encryption, or unknown incompatible qcow2 feature bits.
  3. Computes the exact file size, SHA-256 digest, and qcow2 virtual capacity, and atomically writes catalog.json.

Each artifact is identified by the immutable tuple (image, exact version, architecture) and stored under a human-readable filename:

images/d13-20260810.2566.0-arm64.qcow2

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 --repo or BARN_REPO may use unsigned catalogs when you control the local filesystem or HTTPS endpoint.
  • Per-repository anti-rollback: Barn tracks the highest accepted catalog revision independently for each repository root, rejecting older revisions (unless --allow-downgrade is passed to barn 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 qcow2 structural 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:

  1. CLI release archives and Linux packages are published on GitHub Releases with checksums.txt SHA-256 manifests and SPDX SBOMs.
  2. Native macOS Barn.app bundles use Apple Developer ID code signing and notarization (or local ad-hoc signing when built from source).
  3. 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.

7 - From Upstream Images to a Signed Catalog

How Barn separates upstream release metadata, catalog evidence, runtime support, and release acceptance.

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:

  1. Fetch the current signed catalog.json from the official image repository and verify its signature.
  2. 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.yaml when the repository source changes.
  3. Run make catalog-check to 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 execute barn update with a fresh temporary BARN_HOME. Use go run ./tools/catalogcheck -verify-bytes when all stable artifacts also need to be streamed and hashed.
  4. 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.

8 - What Barn Can Learn from Vagrant

A proposal for repeatable provisioning, environment records, file transfer, and recovery in Pigsty labs.

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.

9 - Replacing Vagrant with Barn in Pigsty: resources, names, and migration work

An audit of Pigsty’s Vagrant workflow, the vm_scale and local hosts contracts, and an ordered migration plan based on 54 inventory templates.

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:

Effective CPU    = base vm_cpu (default 1) × vm_scale (default 1)
Effective memory = base vm_mem (default 2048 MiB) × vm_scale (default 1)

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:

Pigsty repository
# Requires Python 3, PyYAML, and a Barn build with hosts install -f
bin/sandbox-dns -f conf/meta.yml          # preview by default
bin/sandbox-dns -f conf/meta.yml --json
bin/sandbox-dns -f conf/meta.yml --apply  # Barn requests sudo when necessary
barn hosts uninstall --yes

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.

10 - Pigsty host setup: fixed names and CA trust

A small, explicit macOS and Linux setup operation for stable Pigsty portal names and the lab’s root CA.

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
Proposed core-v1 hosts payload
10.10.10.10 i.pigsty sss.pigsty m.pigsty adm.pigsty ddl.pigsty supa.pigsty api.pigsty cli.pigsty

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.

Proposed setup, inspection and removal
bin/sandbox-host setup --ip 10.10.10.10 --ca files/pki/ca/ca.crt
bin/sandbox-host setup --ip 10.10.10.10 --ca files/pki/ca/ca.crt --apply
bin/sandbox-host status
bin/sandbox-host status --probe
bin/sandbox-host remove --apply

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

  1. Pigsty profile: implement bin/sandbox-host with the fixed list, explicit IP and public CA input. It does not evaluate Ansible, inspect live roles or regenerate CA material. Existing # pigsty dns lines are reported for the explicit migration described in the DNS study.
  2. 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_hosts shape: no fake VM node and no second VM inventory format. Infer the one private /24 from 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.
  3. 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.
  4. 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.

11 - VM specification parameters: commands, short flags, environment, and root disks

A unified CLI, environment, inventory precedence, command scope, and persistence contract for image, architecture, CPU, memory, scale, and root disk size.

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:

Explicit CLI > corresponding nonempty environment variable > host > deepest child group > parent group > all.vars > default

Effective CPU    = selected vm_cpu × selected vm_scale
Effective memory = selected vm_mem × selected vm_scale
Root disk size   = selected vm_disk

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:

BARN_IMAGE=u26 BARN_SCALE=2 barn plan -f pigsty.yml
BARN_IMAGE=u26 BARN_SCALE=2 barn up -f pigsty.yml
BARN_SCALE=2 barn plan -f pigsty.yml -S 3  # The final multiplier is 3

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:

  • init and template-generating setup explicitly write all six chosen values into all.vars, making defaults visible in the file. Record base CPU/memory and scale; never write multiplied results back as bases. init preserves 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 ordinary up can read the same choices.
  • With an existing inventory, up/resize/recreate override 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 as start retain applied specifications.

For example, the commands below generate four nodes, each with 4 vCPU / 8 GiB and a 128 GiB root disk:

barn init full -I u26 -A arm64 -C 2 -M 4GiB -S 2 -D 128GiB
barn plan -f barn.yml
barn up -f barn.yml

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.

12 - Host DNS and domain resolution for Pigsty on Barn

An explicit hosts workflow, Pigsty-owned name semantics, source replacement safeguards, and an optional split-DNS upgrade path for macOS and Linux.
Note

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.

host-names.yml — prototype-compatible shape
all:
  hosts:
    10.10.10.10: {nodename: meta}
  vars:
    vm_hosts:
      - address: 10.10.10.10
        names: [i.pigsty, meta]
      - address: 10.10.10.100
        names: [pg-meta]

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.

Proposed routine workflow
bin/sandbox-dns -f pigsty.yml --json
bin/sandbox-dns -f pigsty.yml --apply

For review or controller-to-host transfer, the proposed exporter produces the same disposable projection. Only --mode, --view and --export below are new options.

Proposed explicit export workflow
bin/sandbox-dns -f pigsty.yml --mode literal --view host-access --export host-names.yml
barn hosts install -f host-names.yml --json
barn hosts install -f host-names.yml --yes

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.

Proposed source switch, followed by independent cleanup examples
barn hosts install --from-applied --json
barn hosts install --from-applied --yes
barn hosts uninstall --json
barn hosts uninstall --yes

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:

/private/etc/resolver/barn.pigsty.test
domain pigsty.test
nameserver 10.10.10.10
port 53

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:

Barn checkout — offline reproduction
export GOTOOLCHAIN=local GOPROXY=off GOSUMDB=off GOENV=off GOWORK=off
export GOFLAGS=-mod=readonly
export GOCACHE="${DNS_AUDIT_DIR:?}/go-cache"
export GOTMPDIR="${DNS_AUDIT_DIR:?}/go-tmp" TMPDIR="${DNS_AUDIT_DIR:?}/tmp"
export BARN_HOME="${DNS_AUDIT_DIR:?}/barn-home"
mkdir -p "$GOCACHE" "$GOTMPDIR" "$TMPDIR" "$BARN_HOME"
go test -count=1 -v \
  -run '^(TestReconcile.*|TestApplyHelper.*|TestInstalledHelperValidation.*|TestCompanionHelperDigest.*|TestHelper.*|TestParseInventoryVMHosts.*|TestHostsInventoryPreviewNeedsNoDeployment)$' \
  ./internal/hostconfig ./internal/config ./cmd/barn-hosts-helper ./cmd/barn
Pigsty checkout — offline reproduction
python3 -B -m unittest discover -s tests -p test_sandbox_dns.py -v

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

13 - In-place root growth: recovery and native acceptance

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

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

Supported scope

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

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

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

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

Recovery protocol

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

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

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

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

Native acceptance

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

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

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

Code and checks

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

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

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

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

14 - Barn 1.2.0 release readiness and acceptance

Release gates, upgrade compatibility, packaging, and physical-host acceptance for Barn 1.2.0.

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 all confirmed 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: 2 and vm_mem: 4GiB. Both binaries were checked against implicit and explicit inventories.
  • The release drafts now include all.vars.vm_hosts and hosts install -f. Their implementation and tests subsequently entered commit ad74cc9. The candidate’s inventory-only preview was exercised before VM creation.
  • Root-level .idea/ and the local barn.yml are 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 resize2fs left a recoverable guest journal for a 16 GiB target. Restoring the tool and retrying completed filesystem growth.
  • Killing the CLI after qemu-img enlarged the image left the image journal 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/hosts matched 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

  1. Source changes were committed in ad74cc9 and 90f6158; the command reference and documentation review were separately committed in 24ff1e2. Both repositories were pushed and their CI passed.
  2. Source and packaging CI passed 90f6158, then v1.2.0 was tagged and pushed.
  3. The catalog gate passed, and the local Mac release procedure produced matching signed/notarized assets. make release-local verified the complete release.
  4. Final archive acceptance and all 20 draft re-downloads passed before stable/latest publication. All 20 public downloads and both platform installers passed afterward.
  5. 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.

15 - Running Barn from Windows

Why a Linux host remains necessary, what WSL2 must prove, and what a native Windows backend would require.

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:

  1. 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.
  2. Run barn setup --dry-run to inspect dependencies and network changes. Then verify the installed qemu-bridge-helper, its distribution ownership and permissions, and the barn0 bridge using Barn’s normal Linux checks.
  3. 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.
  4. Test the actual Windows access path. Check the management endpoint and any required service separately from reachability of the private subnet.
  5. 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.

16 - Beyond amd64 and arm64

How guest architecture selects an emulator, machine, firmware, devices, and acceptance tests in Barn.

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.

17 - Evolving the CLI Without Breaking Its Contracts

What to preserve when separating Barn’s command tree, application orchestration, and output handling.

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.0 already includes full bare-command help, structured actions[], confirmation aliases on applicable destructive commands, and the output-environment precedence fix.
  • CLI code still lives in cmd/barn/. The proposed internal/cli extraction, --format, image update, and ssh-config show/install/uninstall command tree have not landed.
  • Current Barn 1.1.0 adds --image/-I and --arch/-A overrides 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]), makes barn purge execute immediately by default with --ask for interactive confirmation, and upgrades YAML parsing to go.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.

18 - The Lifetime of a Native Mac VM

Why Barn uses named Mac machines, process-owned networks, narrow locks, and explicit recovery evidence.

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.