This is the multi-page printable view of this section. .
Barn documentation
-
1: Start
- 1.1: Quick Start
- 1.2: Daily Operations
- 1.3: Troubleshooting
- 1.4: Automation and Guest Scripts
- 1.5: Storage, Files, and Service Access
- 1.6: macOS Virtual Machines
- 1.7: Image Repositories
- 1.8: Build from Source
- 1.9: Uninstall and Clean Up
-
2: Reference
- 2.1: Configuration
- 2.2: CLI
- 2.3: Mac Commands
- 2.4: Images
- 2.5: Image Pipeline
-
3: About Barn
- 3.1: Design
- 3.2: Status
- 3.3: Engineering
Barn turns one Pigsty-compatible Inventory into fixed-IP QEMU virtual
machines. It manages one deployment per Unix user; state lives under
~/.barn, so lifecycle and SSH commands work from any directory.
Choose the shortest path for your task:
- Start — boot the first lab with two commands, then operate and troubleshoot it.
- Reference — exact Inventory fields, commands, flags, output, and exit codes.
- About — design, native validation, limits, and release gates.
Start with the Quick Start to prepare the Barn 0.9.0 release candidate
from source. Once the CLI is ready, run barn up to start the lab, then barn ssh to connect.
Barn 0.9.0 is not yet published. See release and validation status.
1 - Start
With Barn installed, new users can start a test lab with the Quick Start:
When no inventory or deployment exists, interactive up creates the default
inventory. It can prepare missing host dependencies and networking. Repeat barn up to retry unfinished guest setup without
restarting healthy VMs. For unattended setup, run barn setup --yes before
barn up.
Package availability is recorded on Status; developers and source reviewers can use Build from Source.
Everything else is separated by task:
- Daily Operations — status, access, start/stop, changes, scale-in, and destroy.
- Troubleshooting — diagnostics and common fixes.
- Image Repositories — choose images, use mirrors, import, and prune the cache.
- Build from Source — developer builds, checks, and local PATH setup.
- Uninstall and Clean Up — remove the deployment, images, networking, and state.
- Automation and Guest Scripts — unattended setup, JSON acceptance, repeatable guest commands, and the Pigsty handoff.
- Storage and Access — disk retention, file transfer, SSH tunnels, and Linux directory sharing.
- macOS Virtual Machines — unreleased
barn mac: macOS 27 guests on Apple Silicon with desktop, SSH, shared folders, and clipboard.
1.1 - Quick Start
Install
Install the current Barn 0.9.0 development version with Homebrew:
The Homebrew formula builds the CLI and hosts-file helper from the main branch. You can also build from source manually.
Release packages
The 0.9.0 release packages are not published yet. Once available, the user-scoped installer supports macOS and Linux on arm64 and amd64, verifies the archive checksum, and needs no sudo to install:
The default installation directory is ~/.local/bin; add the same PATH line
to your shell configuration. A release build should report 0.9.0. GitHub
excludes prereleases from /releases/latest, so specify BARN_VERSION=0.9.0.
See Download and PATH problems if downloads fail.
DEB and RPM packages will also be available with the release. The examples
below use amd64; use the corresponding linux_arm64 asset on ARM64 Linux.
The host requirements below apply to Linux guests. macOS guests use the independent barn mac command.
Host requirements
| Host | Native acceleration | Minimum QEMU |
|---|---|---|
| macOS arm64 / amd64 | HVF | 8.2.1 |
| Linux amd64 / arm64 | KVM | 6.2 |
The host also needs qemu-img, OpenSSH, and firmware for the selected guest.
Interactive up can prepare missing dependencies through Homebrew on macOS
or apt/dnf on supported Linux distributions, and install the fixed-IP network.
Host package and network changes may require sudo; run Barn itself as your
normal user. Linux needs usable KVM and NetworkManager or systemd-networkd.
The dated native validation covers macOS arm64 and Ubuntu amd64; other build
platforms have narrower evidence. See Status.
Boot the first lab
For a first deployment, open a terminal in an empty directory:
Use exit to return from the guest to your host terminal before running more
Barn commands.
When no inventory or applied deployment exists, interactive up creates
barn.yml with one meta node. It prepares missing host dependencies and
networking, downloads and verifies the image, starts QEMU, and waits for
management SSH. Host changes are displayed; sudo may ask for your password.
To review the full host plan before applying it, use barn setup --dry-run.
A new directory is not a new lab. State lives in $BARN_HOME (default
~/.barn). If a deployment already exists and no inventory is found,
up continues that deployment. Use barn status to inspect it first.
The default template resolves to:
| Setting | Default |
|---|---|
| Node / fixed IP | meta / 10.10.10.10 |
| Guest image | Ubuntu 24.04, u24:stable, native host architecture |
| Login | dba, with SSH key authentication |
| CPU / memory | 2 vCPUs / 4 GiB per node |
| Root / data disk | 64 GiB root + 128 GiB at /data, not persistent |
Disk sizes are virtual capacities; qcow2 files grow as data is written.
A four-node lab uses 8 vCPUs and 16 GiB of guest memory, in addition to host
resources. Use barn plan to inspect totals before starting.
A fresh, unedited built-in template on the default subnet may be moved to an available private /24
when setup finds a subnet conflict. An existing template is backed up as
barn.yml.before-network-change. Check the resulting barn.yml and
barn status for actual addresses; explicit -f files, edited templates,
and existing deployments keep their selected subnet.
A healthy first start ends with a result such as:
barn ssh selects the control node, meta in this template. You can also
name it or run a command directly:
st is the alias of status; its running state describes the VM
process, not a fresh guest-readiness check.
Continue interrupted setup
Repeat barn up to continue interrupted work, retry unfinished guest setup,
or update older guest helpers. Healthy running VMs keep their process and
root disk. A guest with usable management SSH can finish with limitations,
such as a read-only share or unavailable private networking. Review those
messages; automation should inspect nodes[].warnings and nodes[].repairs
in barn up --json, as these limitations still return exit 0.
Data disks are disposable test storage. up can reset an unrecognized or
confirmed damaged filesystem, including a persistent disk, and reports
discarded data. persistent retains disks across destroy/recreate; it does
not protect corrupt contents during recovery. See Data disks.
--no-wait skips guest readiness, recovery, and metadata refresh; a later
barn up completes them. Image downloads support retries and resumption.
Use barn up --mirror to prefer the official China repository; see
Image Repositories for image selection and fallback behavior.
Choose an inventory before booting
This is an alternative to the automatic first run above. In a fresh lab directory, generate and inspect the configuration before starting:
For the Catalog images used here, init, validate, and plan do not require
QEMU or host-network setup. Planning a registered local-* image does require
qemu-img to validate its cached bytes.
The default meta inventory is:
There are four built-in templates:
| Template | Nodes | Default addresses |
|---|---|---|
meta |
1 | 10.10.10.10 |
dual |
2 | 10.10.10.10–10.10.10.11 |
trio |
3 | 10.10.10.10–10.10.10.12 |
full |
4 | 10.10.10.10–10.10.10.13 |
For example, barn init full writes four nodes;
barn init full -c 10.20.30.0/24 selects another subnet. Existing files are
preserved unless --force is explicit. Set vm_cpu, vm_mem, vm_image,
and other fields before the first up; see Configuration.
Prepare the host explicitly
setup prepares dependencies and networking without starting VMs:
Unlike the preparation performed by up, standalone setup asks for
confirmation before applying a mutating plan. It reuses the discovered
inventory, or generates meta if no file exists. The optional /etc/hosts
helper is installed only when barn hosts install --yes needs it;
ordinary startup and barn ssh do not need that integration.
Downloads honor HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, and NO_PROXY,
including lowercase forms. For unattended first setup in an empty directory:
setup --yes can generate the inventory itself; a separate init is needed
only when you want to edit it first. Automation still needs credentials for
any required sudo operation; --yes does not supply them. The
automation guide shows how to retain command results and
check guest limitations before proceeding.
Use an existing Pigsty inventory
Barn reads the documented VM, naming, and login fields and preserves other Pigsty settings. This starts the virtual machines; installing PostgreSQL or other Pigsty services is a separate Pigsty operation. The built-in templates describe VM topology and do not include a complete Pigsty service configuration. See Automation and Guest Scripts for the handoff, and Storage and Access for file transfer and service connections.
Scale and operate
To expand the default one-node lab, preserve the existing settings and add
three hosts to barn.yml:
This example assumes the default subnet; if setup chose another, use that
subnet for every address, including admin_ip. Do not overwrite a customized
inventory with init --force to expand it.
With only these additions, the plan lists three nodes to create. up creates
them, keeps a running meta process, and refreshes guest hosts and control-node
SSH entries. A healthy result is 4 nodes ready. The embedded 0.9.0 Catalog
resolves u24:stable to u24@20260926.0.0; a manually updated Catalog may
resolve another version, which appears in plan and status.
Changing CPU, memory, or other consumed VM fields requires an explicit
barn recreate <node>. Removing a YAML entry never deletes its VM. Stop
and resume the lab without recreating disks:
When finished, destroy the deployment:
On a terminal, type destroy to confirm. Root and non-persistent data disks
are deleted; cached images, keys, declared persistent disks, and host networking
remain. See Uninstall and Clean Up for complete disposal, or
Daily Operations for restart, logs, explicit changes, and scale-in.
barn update refreshes the image Catalog. To install the Barn application,
use Homebrew or the source build described at the top; release packages
will be available after 0.9.0 is published.
1.2 - Daily Operations
This guide describes the Barn 0.9.0 release candidate. See Status for the publication and validation boundary.
Inspect and access
Applied state is under ~/.barn by default (BARN_HOME overrides it); these
commands work from any directory. Changing the working directory does not create
a separate deployment.
Status shows images and resources; --verbose adds architecture, accelerator,
SSH ports, and PID. TCG is marked in ordinary output, and a degraded node does
not hide its peers.
barn up rebuilds the default SSH aliases from the complete applied
deployment after the selected VMs are started, so a scoped up never drops
unselected peers and plain ssh meta just works; barn ssh-config --install
rewrites it by hand if you ever need to.
plan, up, reload, and recreate prefer -f, then a discovered
Inventory, then the applied spec when no file exists. validate always needs
a file.
Repeat up to retry unfinished guest setup and refresh older guest helpers
without restarting healthy VMs. Optional limitations appear in the result;
JSON/YAML expose nodes[].warnings and nodes[].repairs. Unusable test data
filesystems may be reset, including persistent disks; see
Data disks.
Stop and start
start powers on stopped VMs and re-checks readiness of running ones. Both
start and restart use applied state and refresh SSH aliases, including any
reassigned automatic ports. reload reads the Inventory and checks drift and startup dependencies before
stopping selected nodes and following the full up path.
Starting commands also refresh Barn hosts and control-node SSH entries in
running guests. --no-wait skips readiness, guest recovery, and this refresh; run up later
to finish them.
Change the deployment
recreate and destroy ask you to type the confirmation word on a terminal;
--force skips that prompt and is required without a terminal.
plan can inspect Catalog-backed images before host setup and shows images,
total resources, change reasons, and disk effects. Planning an imported
local-* image also validates its cache and requires qemu-img. CPU/memory changes still require recreate: root and ephemeral
data disks are replaced, while persistent disks are kept. A selected recreate
blocked by unselected peer changes refuses before deletion and names the nodes
that need attention.
Inventory changes appear in these fields:
| Field | Meaning | Action |
|---|---|---|
create |
desired node has no state | barn up |
recreate |
VM definition changed | barn recreate <node> |
missing |
stateful node left the file | restore it, or destroy it explicitly |
Deleting YAML never deletes a VM. Unconsumed Pigsty changes produce
action:none; native naming and node-admin fields are consumed even though
they do not begin with vm_. Successful recreate refreshes the complete SSH
fragment as well.
Concurrent commands (0.9 candidate)
Deployment mutations wait behind another Barn operation for up to ten
minutes, bounded by the command’s own deadline. The waiting message identifies
the command, PID, and start time. A lock timeout returns exit 4, JSON
error: conflict, and reason: deployment_busy; retry after the holder finishes.
The lock is released automatically when the holding process exits. Do not
delete a lock file to interrupt a live operation.
status, ssh, exec, ssh-config, and the deployment-state read for hosts
do not queue behind this lock. They use the published state; status adds a
note when another command owns the deployment and does not reconcile its
transitions. A VM still starting may therefore be unavailable to SSH.
See Troubleshooting for interrupted operations and Automation for scriptable results.
Destroy
--delete-persistent and --purge are valid only for whole-deployment
destroy, not with node selectors. --purge removes persistent disks, keys,
and deployment state; images remain cached. Node destroy refreshes the SSH
fragment for remaining peers, while whole destroy removes the default Barn
SSH integration. Host network removal is separate and refuses while a VM is attached.
barn purge is the concise disposable-lab path. It is
equivalent to destroy --force --purge for an existing deployment, accepts no
node selectors, and is
idempotent when no deployment exists. It keeps the image cache and host
network, and it does not bypass process, ownership, or path-integrity checks.
0.9 candidate: plain destroy succeeds when no deployment exists. If
deployment state is gone but owned persistent disks remain, use purge;
destroy --delete-persistent or destroy --purge points to that command.
The old rm alias has been removed; spell out purge.
See Image Repositories for image selection, mirrors, and cache pruning, or Uninstall and Clean Up to remove host state.
1.3 - Troubleshooting
This page describes the Barn 0.9.0 release candidate. Check barn version
before applying version-specific guidance.
Start with diagnostics (status may reconcile interrupted runtime state):
Download and PATH problems
The installer uses GitHub Release assets; --mirror selects the Barn image
repository and does not redirect installer downloads. If your network needs a
proxy, set HTTPS_PROXY or ALL_PROXY in the terminal to your existing proxy’s
address. A macOS system proxy setting alone does not configure these environment
variables for command-line tools.
The user-scoped installer defaults to ~/.local/bin. If barn is missing or
reports an older version after installation, check which executable is selected:
For Homebrew or native packages, use that channel’s executable instead. Keep the
CLI and its packaged barn-hosts-helper from the same release together.
No inventory found
Interactive up can create the first default inventory when no deployment
exists. For an explicit configuration, run plan, up, or validate beside
barn.yml/pigsty.yml, pass -f /path/to/file, or run barn init to
write one. Once state exists, plan, up, reload, and recreate can fall
back to its applied spec. Status, start, stop, SSH, and destroy always use
applied state. If status reports no deployment state found, the selected
BARN_HOME has no applied deployment; it may be fresh or previously purged.
Setup needs sudo
The line before the prompt names the exact host mutation. Barn attaches an
interactive terminal directly to sudo when the privileged step begins.
--yes accepts the setup plan; it does not bypass sudo authentication.
Automation needs an existing credential or a suitable NOPASSWD policy. Use
barn setup --dry-run to inspect the plan first.
On macOS, setup prepares the pinned socket_vmnet source before requesting
administrator authentication. A download failure therefore does not require a
password. 0.9 candidate: the setup plan spells out sudo use and the
socket_vmnet source; if an automatically selected subnet changes after the
first confirmation, setup asks again unless --yes was supplied.
Native acceleration or compatibility runtime is unavailable
Native paths require HVF on macOS or KVM on Linux. TCG is selected only for an
explicit foreign vm_arch or a built-in image/host compatibility rule; an
arbitrary native failure never falls back. Homebrew QEMU contains both system
emulators. Linux setup installs only the native family, so a foreign Guest also
requires its matching qemu-system-* binary and firmware.
plan resolves Catalog-backed images and their intended runtime without QEMU
installed; imported local-* images still need qemu-img and a valid cache.
up and recreate
check the selected emulator and firmware before changing VM resources.
Performance results from TCG are not meaningful.
Network is partial or invalid
An intact but inactive Barn network can be restored by interactive up.
For partial or invalid installations, do not delete host files by hand; review
the owned cleanup plan:
Without --yes, JSON output only plans removal. The network plan may still
need sudo to read protected ownership state. Apply the reviewed plan with
barn network uninstall --yes. 0.9 candidate: ordinary terminal output
asks [y/N] and can apply removal immediately after confirmation. A failed Linux
bridge smoke test rolls the install back automatically; an explicit
automatic rollback failed message means manual inspection is required.
On macOS, a missing or restrictive root-owned /var/log/barn-vmnet is
repairable. Read the finding from network status: the expected directory is
root:wheel 0755. barn setup repairs a recognized installation; follow the
exact diagnostic command if repairing manually. A symlink, wrong owner, or
group/world-writable directory is not repaired automatically. Do not diagnose
a route conflict from the bridge name alone.
Linux bridge helper fails
Debian/Ubuntu uses root:<caller-accessible-group> 4750; the caller does not
have to belong to kvm when /dev/kvm access comes from a desktop ACL.
Plan reports recreate or missing
recreate means the node’s definition changed: review it with barn plan,
then run barn recreate <node>. On a terminal the command asks you to type
recreate; without a terminal it requires --force. missing is only a report: restore the
host entry or run barn destroy <node>.
A node did not become ready
Management SSH and guest instance identity are required for readiness. If a node cannot be created, started, or reached, a node-level partial result reports the node and stage and exits 5. A command-wide failure, such as a missing host capability or an inventory conflict, uses its own exit class. Read its logs:
Data disks, shares, hostnames, guest hosts, control-node SSH, and private-network
setup run independently. Failure of one does not prevent management SSH or the
other stages. A usable guest returns 0 with specific limitations; JSON/YAML
expose them as nodes[].warnings. Internet access is not a readiness requirement.
| Limitation | Next action |
|---|---|
| Data disk unavailable | Correct a missing device, probe, tool, busy mount, or I/O problem, then run up |
| Shared directory is read-only | Correct host permissions, then run up to retry writes |
| Guest hosts or control-node SSH incomplete | Run up to refresh the managed files |
| Private interface unavailable | Check barn network status, then run up; management SSH can still work |
Repeat up after fixing the underlying issue. It retries unfinished stages,
upgrades old guest helpers in place, and skips healthy work without restarting
running VMs. Unrecognized or confirmed damaged test data filesystems are reset
automatically, including persistent disks; the result reports discarded data.
Failed probes, busy mounts, and I/O failures do not trigger formatting. See
Data disks.
A repeated up can also clean recognized leftovers from interrupted preparation.
--rollback removes failed prepare artifacts in the same run and lists them in
rolled_back. --no-wait returns once QEMU is running and skips readiness,
guest recovery, and metadata refresh; a later up completes them.
SSH fails
Startup restores a missing deployment public key from the intact original
private key.
If the private key is missing, restore that same key from backup; Barn refuses
to generate a replacement identity for existing VMs. This host-side recovery is separate from a missing guest key in a control node.
up now checks that installed guest key as well: a missing copy is a
control-ssh limitation, not a management SSH failure. Restoring the original
guest key and running up clears the limitation. Automatic private-key reinjection into existing guests is not supported.
Check barn status, barn ssh-config, and the serial log. Barn’s own
SSH uses a loopback management port; direct Ansible traffic uses the fixed IP.
If another process occupies a stopped VM’s automatically allocated management
port, the next start selects a free port and refreshes its SSH aliases. Running
VM ports stay unchanged. SSH host-key trust is scoped to the VM instance UUID,
so recreating a VM does not require deleting unrelated known-host entries.
A changed key for the same instance still fails verification.
doctor excludes fixed IPs reserved by the applied deployment from its generic
eligibility scan; up and start still reject a new or stopped node address
that already accepts SSH.
0.9 candidate: a symlinked or hard-linked ~/.ssh/config is not rewritten.
Barn publishes its fragment and shows the Include line to add through your
dotfile manager. If ssh meta fails while barn ssh meta works, check that
include before changing guest keys. Near-miss node names in ssh/exec are rejected with a suggestion when they
contain a digit or - and match the typo heuristic; use -- when
explicitly separating a node selector from its remote command.
Catalog or image verification fails
The current binary embeds active and standby Catalog public keys. Unknown
signers, version rollback/equivocation, artifact size/SHA mismatch, and unsafe
qcow2 structure are distinct integrity failures. Use a correctly signed
repository or barn image import --sha256 ...; do not copy bytes directly
into ~/.barn/images.
A command was killed
First check whether another Barn command is still running. In the 0.9
candidate, status reads published state without waiting and reports a
note while another command holds the deployment lock. Wait for that command
to finish before treating its in-progress state as an interruption.
When no operation holds the lock, run barn status. A provably live or dead
runtime is reconciled using its recorded identity; an ambiguous process remains
blocked. Never kill an unknown PID based only on a state file.
The 0.9 candidate additionally handles these recovery cases:
| Interrupted operation | Recovery |
|---|---|
| Host reboot or recycled QEMU PID | status recognizes a provably unrelated PID and marks the old VM stopped; use start |
stop while QEMU kept running |
status restores running state; repeat stop if shutdown is still intended |
First up failed during preparation |
Correct the inventory and repeat up -f /path/to/barn.yml; only journaled unfinished artifacts are rolled back |
destroy stopped midway |
Repeat the same explicit destroy scope; interrupted transitions and previously retained persistent disks can be resumed |
If recovery still fails, retain the state and logs; do not delete node directories or rewrite PIDs to imitate a successful recovery.
If a recorded QEMU process still exists but its QMP socket is absent, preserve
the evidence and inspect serial/QEMU logs before using stop to converge it.
Do not delete runtime sockets or state files by hand.
0.9 candidate: the generic error envelope uses a stable class in error,
with optional reason, next, and external-program details in command.
Some commands return their own diagnostic reports. Read the cause and proposed
next step; do not parse human text as an API. See Automation
for exit codes and result handling. Event and QEMU logs use readable records;
--verbose adds QEMU arguments when those are needed.
For a bug report include the exact command and exit code, barn version,
the three JSON reports above, host OS/architecture, and QEMU version.
1.4 - Automation and Guest Scripts
These examples target the Barn 0.9.0 release candidate. Run host commands as the Unix user who owns the deployment.
Use the same inventory and BARN_HOME on every invocation; a new working
directory does not create an independent lab.
Prepare a predictable lab
For a first lab, create and review the inventory before starting automation:
Keep the selected inventory in version control. Set vm_image explicitly;
use a version such as vm_image: u24@20260911.0.0 when new nodes must use the
same base after a Catalog update. Explicit -f also prevents first-run setup
from automatically moving an untouched default template to another subnet.
After reviewing the host plan, prepare the machine once:
--yes accepts the setup plan; it does not grant sudo credentials. An
unattended runner must already have the required host dependencies, network,
and privilege policy. Noninteractive up does not perform the interactive
first-run host preparation. up has no --yes flag.
For a custom repository, use the same --repo value for setup and up, and
explicitly activate its Catalog with barn update --repo URL before
planning. See Image Repositories.
Check more than the exit code
Barn writes structured results to stdout and diagnostics to stderr. Preserve
both outputs and the command’s exit code; a later shell command must not
overwrite the status you intend to inspect. This Bash example also uses jq:
The jq check deliberately asks for every returned node to be ready without
limitations or reported repairs. A usable guest with a failed optional disk,
share, or peer-SSH step can return exit 0 and list nodes[].warnings.
nodes[].repairs can report a filesystem reset that discarded test data.
Choose the acceptance policy your workload needs instead of silently ignoring
these fields. Top-level warnings can describe optional SSH integration or
metadata-refresh failures. A failed jq -e returns nonzero to the caller.
Do not use --no-wait when the next step requires guest readiness. A
status --json snapshot reports VM state and cached warnings; it is not a new
guest readiness test. Run up to complete setup, then run an application check
inside the guest when your workflow depends on a service.
Failures and version boundaries
Use CLI exit codes together with the payload
for the command you ran. Partial operations can retain successful nodes;
inspect nodes and/or failures when present before retrying.
The 0.9 candidate moves some failures to different classes. For example,
a missing first inventory is usage/2 and an unknown image is usage/2;
recreate_required and nodes_removed are reason values under conflict/4.
It also uses bounded lock waiting and deployment_busy on timeout.
Not every failing command returns the generic error/message envelope:
doctor, network status, provision, and SSH execution can return their own
reports. ssh/exec pass through the remote exit status, including 255 from
OpenSSH. In structured execution output, inspect success, exit_code,
stdout, and stderr; do not interpret a remote status as a Barn class.
Run one command or a script
Use an explicit node and -- to separate it from the remote command:
For multiple guests, save a local Bash script as check-lab.sh:
Then run it on selected nodes:
With no selectors, provision targets all committed nodes; they must be
running. It does not create or start VMs. The local script must be a nonempty,
regular, non-symlink file of at most 4 MiB. Barn streams one verified snapshot
to guest Bash, records its SHA-256, and does not save the script as a guest file.
The script need not be executable on the host.
Execution is serial by default, with --parallel from 1 to 4. --timeout
defaults to one hour, applies to the whole operation, and cannot exceed 24 hours.
--sudo runs through guest sudo -n, so it cannot prompt for a password.
Results contain results[], per-node stdout/stderr and exit codes, and
successful/failed counts. A partial run can leave successful changes in
place. Write scripts so that running them again is safe; Barn does not roll
back guest commands or automatically rerun them on the next up.
A provision run with successful and failed targets exits 5. With one failing
target, a positive remote status other than 255 is passed through. Other
all-failed runs exit 1; inspect results[].exit_code for the guest/SSH details.
Use the lab with Pigsty
Barn and Pigsty can read the same pigsty.yml, but barn init dual only
creates VM topology. It does not configure a PostgreSQL cluster. Start with
the service inventory appropriate to your Pigsty checkout and review it:
Barn prepares the guest administrator and control-node SSH access. Deploy services through Pigsty after checking the inventory and guest connectivity. The validation record distinguishes Ansible connectivity checks from a complete Pigsty installation. For host-side file transfer or port tunneling, see Storage and Access.
1.5 - Storage, Files, and Service Access
This guide uses the Barn 0.9.0 release candidate interface. Start with the Quick Start before running the
guest-side checks. The examples use node meta and the default subnet; retain
your actual names and addresses when adapting an existing inventory.
Choose disks before creating the VM
Every node has a 64 GiB root disk and, by default, one 128 GiB non-persistent
data disk at /data. vm_disk sets the root disk size in GiB. vm_disks
replaces the entire data-disk list; vm_disks: [] disables extra data disks.
For a new single-node lab, save this as storage.yml:
Then review and create it:
size: 64 means 64 GiB; size: 64GiB is also valid for a data disk. These are
virtual capacities, not immediately allocated host space. Monitor host free
space as the qcow2 files grow. fs: auto prefers XFS if the guest provides
mkfs.xfs, otherwise ext4. A successful VM process start alone does not prove
that either data disk mounted; check the guest result and warnings.
This is a first-creation example. If the same node already exists with another
definition, up reports drift. Review plan and back up needed data before
explicitly using recreate; that replaces the root and non-persistent disks.
What survives each operation
| Operation | Root and non-persistent disks | Persistent disks |
|---|---|---|
stop then start, or restart |
retained | retained |
Healthy repeated up |
retained | retained |
Compatible recreate |
replaced | retained and reattached |
Ordinary destroy |
deleted | retained |
Whole-deployment destroy --delete-persistent |
deleted | deleted, including retained disks |
Whole-deployment purge |
deleted | deleted, including retained disks |
Persistence is based on disk identity and a compatible specification. Keep the node, mount path, size, and filesystem definition consistent when reusing a retained disk. It is not an automatic resize, rename, filesystem conversion, backup, or snapshot facility. Barn rejects incompatible retained disks; do not edit state files to force attachment.
A persistent disk is still disposable test storage. During guest recovery,
up may reset an unrecognized or confirmed damaged filesystem and report the
discarded contents. Persistence only controls VM destruction/recreation.
Missing devices, failed probes, busy mounts, and I/O failures do not authorize
formatting. Copy valuable data elsewhere before testing failure recovery.
A freshly formatted data filesystem is owned by root. Use guest sudo for this write check, or deliberately prepare permissions for your application. To demonstrate normal restart retention on the created lab:
This checks a VM stop/start, not persistence after a physical-host reboot. See Status for the native validation boundary.
Copy files with the managed SSH connection
Generate a standalone OpenSSH configuration from the running deployment:
The generated fragment selects the current loopback SSH port, deployment key,
and instance host-key identity. Regenerate it after recreation or a management
port change. barn ssh-config --install is optional when you want these
aliases available in your normal SSH configuration; -F works without that
integration. The exported file references your deployment key; it does not
embed or export the private key.
Reach a service inside the guest
From the host, a service listening on the guest’s fixed IP can be reached on
that IP if its guest firewall and service configuration allow it. For example,
a PostgreSQL server on 10.10.10.10:5432 is a separate service you must install;
Barn does not install PostgreSQL merely by booting a VM.
To reach a service listening only on the guest’s loopback address, use OpenSSH with the generated configuration:
Keep that host terminal open, then connect a local client to
127.0.0.1:15432. The guest service must already be listening on port 5432.
Ctrl-C closes the tunnel. Choose another local port if 15432 is occupied.
The explicit loopback bind keeps this example local to your host.
The inventory has no vm_ports or vm_forwards setting; an unknown vm_*
key is rejected. Use the fixed-IP network or OpenSSH forwarding. Management
SSH uses a separate loopback connection and can remain available while the
fixed-IP network has a reported limitation.
Host directory sharing on Linux
For a Linux host, a read-only share can be added before the first up:
Put this under the intended host or all.vars. Replace the host path with an
existing real directory owned by your Barn user and accessible to that user.
Read-only shares also require this ownership. Host paths must be
absolute, cannot pass through symlinks, and cannot overlap the Barn data root.
An explicit read-only share is a useful starting point for source files.
For writable shares, guest-user permissions also matter; Barn can fall back
to read-only and report a limitation without changing host ownership.
Do not add vm_shares to a macOS lab using the currently documented runtime.
The tested macOS/QEMU path cannot reopen the secure directory descriptor and
the affected node cannot start. Use SSH file transfer there. Restoring a missing
host directory or mount lets you retry up; Barn never creates an empty
replacement source. Changing an existing node’s share definition requires
explicit recreation. See Configuration for
the full disk and share constraints.
1.6 - macOS Virtual Machines
Barn 0.9.0 release candidate; unreleased. See
Status for current validation and release checks.
Use barn mac --help from the binary you run.
barn mac creates and runs macOS virtual machines on an Apple Silicon Mac.
Each machine is a clean, disposable macOS with an administrator account,
passwordless sudo, pinned SSH keys and a fixed address — for testing, building
and reproducing macOS-specific behavior. It uses Apple’s Virtualization
framework directly and needs no administrator access.
Mac machines are separate from the Linux lab. They never read barn.yml,
never join a Pigsty inventory, and keep their files under $BARN_HOME/mac
(default ~/.barn/mac). Linux destroy and purge leave them alone.
Requirements
- An Apple Silicon Mac running macOS 27 or later, with a user logged in to its desktop. Guests also run macOS 27.
- About 65 GiB free for the first machine: the 25 GiB restore image from Apple (kept until you prune it), the 27 GiB installed base, and room to start. Each machine then grows with its own changes up to its disk capacity, 100 GiB by default.
- Xcode 27 to build the Mac component from source, until a release includes it.
- No sudo. Every machine, its network and its desktop run as your user.
Apple allows two macOS virtual machines running at a time on one Mac, including those of other tools and macOS installation itself. You can create more machines and start any two.
Build the Mac component
From a Barn source checkout that contains barn mac:
bin/mac holds the CLI, Barn Mac.app (the native component that runs the
machines and their desktops) and the guide; keep them together. The build is
signed ad hoc for local use. doctor checks macOS, the component, and free
disk space:
Create your first machine
On a Mac without a prepared macOS, up first shows what it needs and asks:
After you confirm, Barn:
- Downloads the restore image from Apple only and verifies it against Apple’s published SHA-256. An interrupted download resumes where it stopped.
- Installs macOS once into an unbooted base. Installation uses one of the two macOS VM slots while it runs.
- Creates
mac1as a copy-on-write clone of the base, boots it, creates your account, and waits until SSH and sudo work.
Every later machine reuses the base and is ready in tens of seconds; a machine created from a prepared base on the validation host was ready in 22 seconds.
If you already have Apple’s restore image, pass it instead of downloading. On the same APFS volume Barn clones it without copying; elsewhere it verifies and uses the file where it is:
Without a terminal, for example in a script, up needs --yes to download:
it refuses rather than silently fetching 25 GiB. barn mac setup prepares
the base ahead of time without creating a machine.
Work in the machine
Shell and commands
The account has your macOS user name (choose another with --user when
creating the machine) and passwordless sudo. ssh passes a command line to the
guest shell like plain ssh; exec keeps argument boundaries. Both return the
guest’s exit status, and --json records stdout, stderr and the exit code:
The JSON above is shortened; the Mac reference lists every field.
Desktop
The desktop opens in a native window sized to your screen. Resizing the window
changes the guest resolution, and View → Enter Full Screen works as usual.
Closing the window keeps the machine running; open brings it back, and starts
a stopped machine first. Keyboard shortcuts go to the guest while its window is
focused, so the host commands live in the menu bar:
| Menu | Action |
|---|---|
| Machine → Share Clipboard | turn clipboard sharing on or off for this session |
| Machine → Restart… | restart macOS in the guest |
| Machine → Shut Down… | shut down normally, like barn mac stop |
| Window → Keep Running in Background | hide the window; the machine keeps running |
| Barn Mac → Quit Barn Mac… | choose to keep the machine running or shut it down |
The login password, needed for the lock screen and administrator prompts in the desktop, is random per machine. Copy it without printing it:
Clipboard
Plain text follows your focus. What you copied on the Mac is available in the guest when you click into its window, and what you copy in the guest comes back when you switch to another app. It travels over the machine’s own SSH connection; nothing is installed in the guest. Items that password managers mark as concealed never leave the Mac. Images and files are not shared.
Turn it off for a machine with barn mac configure mac1 --clipboard off;
the setting applies from the machine’s next start.
Shared folders
Share Mac folders when creating a machine. The guest mounts them under
/Volumes/My Shared Files/<name>:
The name defaults to the folder’s last path component; :ro makes a share
read-only. A share must be an existing directory, not a symlink; Barn never
creates or deletes shared folders. To change shares later, stop the machine
and use configure:
macOS guests can show stale file contents for a short while after the Mac
changes a shared file. Use SSH or exec when you need an immediately
consistent view.
SSH from other tools
When the first machine becomes ready, Barn adds one marked Include to
~/.ssh/config, so ssh mac1, scp, rsync, and editors with Remote-SSH
reach every machine by name, with its own key and pinned host key:
Lifecycle commands keep the entries current. A ~/.ssh/config managed by a
dotfile tool through a link is never edited; Barn prints the Include line
to add instead.
Several machines
Give each machine a name. Creation options apply only to a new machine:
Names use lowercase letters, digits and inner hyphens and start with a letter.
A command without a name acts on the only machine, or on mac1, and asks you
to choose when that is ambiguous. DISK shows the space the machine uses now
and its capacity. Capacity belongs to the base: a --disk other than the
prepared base’s installs another base first, which needs the restore image
again.
Each machine has its own private network: mac1 gets 10.10.20.10, later
machines the next free /24, avoiding your LAN, VPNs and the Linux lab.
Machines reach the internet and the Mac, but not each other. With two machines
running, a third is refused before anything is created, naming a machine to
stop:
Everyday lifecycle
stop shuts down normally. A machine still running after two minutes is
powered off, and the result says so. stop --force powers off at once, like
holding a power button; unsaved work in the guest is lost.
start --recovery boots macOS Recovery and shows its desktop.
up never reconfigures an existing machine. If you pass an option that
differs, it refuses and names the command to use:
configure changes CPUs, memory, shared folders and the network while the
machine is stopped, and clipboard sharing at any time. Changes apply at the next
start:
recreate replaces a machine with a fresh macOS from the base, keeping its
name, account, resources, shared folders and address. destroy deletes
machines. Both describe what they delete and ask you to type the command name;
--force confirms without a terminal.
| Operation | Guest disk and apps | Settings, address, account |
|---|---|---|
stop/start, restart, repeated up |
kept | kept |
configure |
kept | changed as requested |
recreate |
replaced with a fresh macOS | kept; new password and SSH keys |
destroy |
deleted | deleted |
The shared base is never changed by any of these, and is kept when machines are destroyed.
macOS versions and disk space
Updates are explicit. barn mac image update asks Apple for the newest
macOS 27, downloads it after you confirm, and makes it the base for new
machines. Existing machines keep their macOS until you run
barn mac recreate NAME --update. up and start never change a machine’s
macOS.
image prune lists bases that no machine uses and that are not the default,
and deletes them with --yes; --installers adds downloaded restore images.
APFS clones share blocks, so ON DISK and machine disk figures are not
exclusive usage and should not be added up.
Troubleshooting
Start with barn mac doctor; it checks the host, the component, the base
and every machine, and prints a next: command for each failure.
barn mac logs [name] shows the machine’s runtime log: startup, network,
shutdown and Apple Virtualization errors.
| Symptom | What to do |
|---|---|
network … overlaps route … on start |
A VPN or another tool now uses that subnet. Run barn mac configure NAME --subnet auto. |
macOS allows 2 macOS virtual machines at a time |
Stop one of the named machines, or quit another tool’s macOS VM. |
ssh mac1 from a third-party client says “No route to host” |
macOS Local Network privacy blocks that app from private networks. Allow it in System Settings → Privacy & Security → Local Network, or use /usr/bin/ssh. barn mac ssh and exec always use Apple’s tools and are not affected. |
the Barn Mac component is not installed or speaks protocol … |
Keep barn and Barn Mac.app from the same build together; rebuild with make mac-build. |
| Starting fails from an SSH session to the Mac | Run barn mac in a terminal of the Mac’s desktop session: machines need the logged-in user’s graphical session. |
Apple Account sign-in inside a virtual machine is unreliable, and USB devices, snapshots and suspending a machine are not supported.
Clean up
Destroying the last machine also removes its entries from ~/.ssh/config. The
default base stays for new machines; to remove every Mac file including it,
destroy all machines and then delete $BARN_HOME/mac (default
~/.barn/mac). Outside that directory Barn writes only its
~/.ssh/config entries, the desktop window positions in
~/Library/Preferences/io.pgsty.barn.mac-runner.plist, and a short runtime
directory under /tmp. Nothing needs sudo.
1.7 - Image Repositories
Normal use needs no image command first: barn up resolves u24:stable for
the native host architecture and pulls the resulting immutable version.
Barn uses the Catalog embedded in the installed build until you run
barn update, which fetches, verifies, and activates the repository’s current
Catalog. Nothing refreshes it automatically; image sync explicitly activates
an exact URL or file for recovery.
Choose an image
Inspect the available aliases:
Built-in families are el7, el8, el9, el10, d12, d13, u22,
u24, and u26. A bare name selects stable; name:channel selects a
channel. An exact name@version key wins; a shorter numeric selector chooses
the newest matching version on dot-component boundaries:
Here 9.7 selects the newest 9.7.x build; 9 selects the newest 9.x release.
Use vm_image: el9:stable and remove vm_version when the repository’s movable
stable channel is the intended policy. A separate vm_version cannot be
combined with :channel or @version in vm_image.
Run barn plan after editing the inventory. Changing an existing node’s image
request requires an explicit barn recreate <node>; up reports the definition
drift instead of rebuilding it. Updating the Catalog alone does not change
existing nodes or their stored base-image identity. Newly created or explicitly
recreated nodes resolve the selector against the active Catalog.
For a reproducible lab, pin the complete version shown by image info, rather
than a movable channel or a numeric prefix:
Quote numeric vm_version values in YAML so their original text is preserved.
Built-in versions are supported except deprecated compatibility images:
EOL el7, EL9 9.3/9.6, and EL10 10.0.
Use a mirror
Released builds use https://repo.pigsty.io/barn by default. Select the
official China repository for one command with long-only --mirror, or name a
custom root with --repo:
Or set the default repository for the current shell:
Selection precedence is --repo, --mirror, BARN_REPO, then the global
default. --mirror resolves to https://repo.pigsty.cc/barn and both
official roots retain canonical signed-Catalog trust. BARN_REPO may also be
an absolute local directory. Explicit local and HTTPS repositories may use an
unsigned Catalog; HTTP repositories require a Catalog signed by a trusted key.
Artifact size, SHA-256, and qcow2 structure are always verified.
Image downloads retry transient failures and resume interrupted transfers. The two official repositories can fall back to one another if the selected endpoint cannot supply an image; the same Catalog size and digest must still match. Custom repositories remain exclusive. Catalog upstream URLs are provenance, never an alternate download source.
This fallback concerns image artifacts. barn update fetches the selected
repository’s Catalog; image sync reads the exact URL or file you supply.
Neither command upgrades the Barn executable. The active Catalog is scoped
to the selected repository. A new --repo uses the embedded Catalog until you
activate that root’s Catalog; changing the download source alone does not make
custom aliases appear.
Build a static repository
A repository is an ordinary directory that can be copied with rsync or served
by a static HTTP server:
repo.yaml is the only human-maintained source. For the single arm64 image
shown above, a minimal complete source is:
Place your independently verified, cloud-init-capable image at
/srv/barn/images/d13-1-arm64.qcow2. Use amd64 in both the filename and
variant for an x86 guest, and set source_user to the image’s source identity.
The repository root must be an absolute, non-symlink directory that is not
writable by group or others. Generate catalog.json locally:
Scan is read-only. Build never changes repo.yaml or image bytes; it performs a
full qemu-img check and materializes file names, SHA-256, artifact size, and
virtual size. build and verify require local qemu-img; scan does not.
Build on a machine with QEMU, then publish immutable QCOW files first and
catalog.json with its matching signature last. The local/HTTPS example may
remain unsigned; plain HTTP and official repositories require a trusted
signature. Increase revision whenever Catalog contents change.
Activate and inspect this local repository before creating VMs:
Use the same --repo /srv/barn for plan, up, and recreate, or export
BARN_REPO=/srv/barn. In the Inventory select vm_image: d13@1 and
vm_arch: arm64; importing a Catalog does not rewrite Inventory defaults.
barn image reset --repo /srv/barn restores the embedded Catalog for that
root while preserving its anti-rollback history.
Import and prune
For a single custom image on the host’s native architecture, import it with
an independently obtained digest. --sha256 is optional in the CLI but is
recommended when you have a trusted digest:
Custom aliases must begin with local-; --name, --boot, and --source-user
are required together. Import checks the qcow2 and copies it into Barn’s
cache without preparing its guest software. The image must already support
Barn’s cloud-init bootstrap. Named imports record the host architecture, so
use a static repository for foreign-architecture images. Select the alias with
vm_image: local-mybase, then run barn plan.
Prune protects every image in the selected active Catalog, every applied node image, and every registered local alias. It therefore does not empty the cache merely because all VMs were destroyed. Inspect candidates before deletion:
See Images for signatures, rollback protection, cache layout, architecture, and TCG rules. See the Image Pipeline for preparing image candidates and the separate checks required before publication.
1.8 - Build from Source
Barn 0.9.0 is an unreleased candidate. Use this page to build and check the source. Installation commands for the eventual release are in the Quick Start.
Choose the source
Clone the Barn repository:
Do not assume a v0.9.0 tag exists before publication. Check the source identity
and working-tree changes first; go.mod must name github.com/pgsty/barn and
the command source must live in cmd/barn:
Build
The reviewed candidate tree pins Go 1.27.1 in go.mod and
packaging/toolchain.env. You also need Git, Make, Bash, and standard build
tools. QEMU and privileged network setup are needed to run VMs, not to compile
the CLI. From the selected checkout:
make build writes the matching barn and barn-hosts-helper binaries
under the Git-ignored bin/ directory. Do not mix the two binaries across
commits or releases. Development builds report dev by default; their commit
field identifies a clean source revision or says uncommitted for a dirty
checkout. The version string alone does not prove candidate behavior.
Keep the inventory in a separate lab directory. This does not isolate Barn
state: if you already have a deployment, inspect it before running up:
Complete checks
The complete checks also need Python 3, jq, a working C toolchain for race
tests, and the pinned quality tools. Install the versions used by the reviewed
candidate’s CI (check CONTRIBUTING.md and packaging/toolchain.env again when
using another revision):
Ensure the Go tools installation directory (GOBIN, or $(go env GOPATH)/bin
when unset) is in PATH. Before submitting a source change, run:
This gate includes module verification, shell syntax, maintenance ownership, unit and race tests, Vet, Staticcheck, four-target dead-code checks, errcheck, vulnerability checks, cross-builds, image-pipeline and installer tests, and dependency-license verification. CI separately checks formatting, whitespace, pinned tool versions, and GoReleaser configuration. Changes to packaging also need a verified packaging snapshot.
A passing source gate is not package publication or a native VM lifecycle
replay; make image-pipeline-native-test is a separate native image gate.
That gate requires the explicit image inputs documented at the top of
tests/image-pipeline-native-test.sh; it does not download a test image.
See Engineering for release tooling, dependency licenses, and validation boundaries.
1.9 - Uninstall and Clean Up
This page deletes VMs and local data. Inspect the current state and stop if any Barn VM must remain:
1. Remove the deployment
Delete nodes, persistent disks, keys, and deployment state:
This whole-deployment command asks for no confirmation. The image cache and
host network remain. Use the granular,
confirmed barn destroy command instead when preserving persistent disks or
removing selected nodes.
2. Remove optional integrations
Whole-deployment destroy already removes the default barn SSH integration.
If you installed a custom fragment name or /etc/hosts entries:
Without --yes, the --json command only shows the marker-owned plan. Apply
the --yes command after checking its target. In Barn 0.9.0, ordinary terminal output asks [y/N] and applies removal
after confirmation. Barn reads the hosts plan without sudo; applying
the change still needs privilege.
3. Remove cached images
Prune removes unreferenced cached images and stale staging files. It protects images referenced by deployment state, the active Catalog, and registered local aliases, so it is not a complete cache wipe. The optional final state-directory cleanup below removes the remaining cache too.
4. Uninstall host networking
The first JSON command only shows the owned removal plan, although sudo may be needed to read protected network state. Uninstall refuses while any VM remains attached. The network is shared across users; removing your deployment does not establish that another user’s VMs have stopped.
5. Clean up a source setup
Network uninstall preserves the independently useful hosts helper. Only after confirming Barn is no longer needed, remove these exact paths:
With the default state directory, and only after every earlier step succeeds, remove the remaining state:
This snippet stops if BARN_HOME is set or the default path is a symlink.
Review a custom state directory separately; never substitute $HOME, /, a
workspace root, or an unverified path. After deletion, avoid running lifecycle
commands just to check that the directory is gone: they may recreate lock
directories.
QEMU may be shared by other tools, so keep it by default. On macOS, remove it only when nothing else needs it:
Verify network removal and the default state directory:
An uninstalled network is expected to report an absent/not-ready finding;
inspect the result rather than requiring a zero exit code. Do not use
bridge100 disappearing as proof: macOS chooses its bridge name and may use
other vmnet bridges for unrelated software.
Remove an Archive, Homebrew, DEB, or RPM binary through its installation
channel. The bin/ directory from a source build is only a checkout artifact,
separate from the host state above.
2 - Reference
This reference describes the Barn 0.9.0 release candidate. Check
barn version before scripting against these contracts. See the release status.
- Configuration — discovery, accepted variables, defaults, disks, shares, naming, and drift.
- CLI — commands, important flags, output modes, and exit codes.
- Mac Commands — the unreleased
barn mac: commands, JSON results, and failure reasons. - Images — signed catalogs, aliases, cache layout, pulls, imports, and pruning.
- Image Pipeline — candidate validation and offline normalization.
Barn exposes no supported Go library API. Packages under internal/ are
implementation details.
2.1 - Configuration
This reference describes the Barn 0.9.0 release candidate. Check
barn version before scripting against these contracts. See the release status.
Discovery
Configuration lookup order is explicit -f, then barn.yml,
barn.yaml, pigsty.yml, and pigsty.yaml in the current directory. Every
name uses the same Pigsty-compatible YAML Inventory format.
For plan, up, reload, and recreate, absence of a file falls back to
the applied spec when a deployment exists. validate has no fallback. A
configuration must be a regular non-symlink file no larger than 4 MiB. The first
existing discovery candidate wins; if it is invalid, Barn reports the error
instead of trying the next filename. Renaming a file or moving directories does
not create another deployment; applied state lives in BARN_HOME.
A complete inventory
This creates two managed definitions; the control node is meta. Save it as
barn.yml, then inspect it without starting VMs:
The JSON validation result includes valid, source, spec_hash, and resolved.
In the 0.9 candidate, validate also resolves catalog image references and
accepts --repo; an unreadable catalog adds a warning instead of a false success
claim about images, and local-* image-byte checks remain the responsibility of
up. Validation does not prove host resources, share access, networking, image
bytes, or guest readiness. It never starts VMs or downloads images.
What Barn reads
Barn reads host IPs, nodename, admin_ip, pg_cluster, pg_seq,
node_admin_username, node_admin_uid, and the documented vm_* variables.
admin_ip is read from all.vars; it selects the control node, or the first
managed host is used. All nodes must resolve the same login username. For the
default dba user, an explicit node_admin_uid must be 88. For a custom user,
node_admin_uid is validated as an integer but does not set the guest UID;
Barn does not expose a general UID customization contract.
Everything else is opaque and cannot create drift. This means unconsumed
fields such as pg_role, pg_version, repo_*, and node_packages, not all
possible pg_* or node_* names.
Inside this namespace validation is strict: unknown vm_* names, wrong
types, Jinja expressions, invalid addresses, and conflicting sibling-group
values are errors. Inheritance is all.vars → deeper children.<group>.vars →
host variables. Host values replace an entire list such as vm_disks; lists are
not appended. Different values inherited from groups at the same depth must be
resolved with a host-level override. YAML anchors and merge keys are supported;
explicit keys win, and the first mapping in a merge sequence wins. Duplicate
mapping keys and multiple YAML documents are errors.
Host keys must be IPv4 addresses even for vm_skip: true entries. Skipped hosts
do not consume the 20-node budget or determine the managed subnet, but the
inventory must still contain at least one managed host. Skipping an already
applied node marks it as removed; it does not destroy that VM.
The 0.9 candidate improves errors with the offending rule, value, and line
where available, and suggests a nearby vm_* spelling. Those diagnostics do
not add new inventory variables.
VM variables
| Variable | Default | Meaning |
|---|---|---|
vm_skip |
false |
do not virtualize this real/external host |
vm_image |
u24 |
image family, channel reference, or image@version selector |
vm_version |
unset | newest numeric version matching this prefix, such as 9 or 9.7 |
vm_arch |
native |
deployment-wide Guest architecture: native, amd64, or arm64 |
vm_cpu |
2 |
vCPU count |
vm_mem |
4096 |
MiB integer, or a size such as 8GiB |
vm_disk |
64 |
root disk: GiB integer or an explicit size such as 64GiB |
vm_disks |
[{path: /data}] |
extra disks (one 128 GiB non-persistent disk at /data by default) |
vm_alias |
[] |
guest /etc/hosts, SSH-config, and optional host aliases |
vm_shares |
[] |
QEMU 9p host-directory shares |
An empty host entry is a complete VM. A deployment contains 1–20 managed
hosts; vm_cpu accepts 1–256 and memory must be at least 512 MiB.
Bare integer memory is MiB; bare integer disk sizes are GiB. Explicit size
strings accept positive integers plus B, KiB, MiB, GiB, TiB, KB,
MB, GB, or TB (case-sensitive). 8GiB is valid; 8G, 1.5GiB, and the
quoted unitless string "8192" are not. Root/data disk sizes must be positive;
up also checks the root disk against the selected base image’s virtual size.
Omitting vm_image selects Ubuntu 24.04. To use Debian 13, set
vm_image: d13 under all.vars. Inspect barn plan before applying
changes to an existing Barn inventory.
vm_version keeps short version intent separate from the image family:
An exact Catalog version wins first. Otherwise Barn matches only on a dot
component boundary and selects the numerically newest match: 9.7 resolves to
the newest 9.7.* build, while 9 resolves to the newest 9.x release. Numeric
components are compared as integers, so 9.10 sorts after 9.9. Do not combine
vm_version with a vm_image that already contains :channel or @version.
vm_arch is stricter than ordinary per-host VM fields: when present it must
resolve to one value on every managed host, so define it once in all.vars.
Changing it is a deployment-envelope change and requires whole-deployment
recreation. Linux setup installs only the native emulator; a foreign
architecture also needs its matching qemu-system-* binary and firmware.
Data disks
path is the disk identity and mount point. fs is auto (the default), xfs,
or ext4. A blank auto disk is formatted XFS when the guest has mkfs.xfs
and ext4 otherwise, which matches what the Vagrant flow did. Explicit xfs and
ext4 never fall back. Healthy existing filesystems are reused.
persistent: true keeps the disk across an ordinary destroy; vm_disks: []
means no extra disk. size defaults to 128 GiB for each entry; integer sizes
are GiB and explicit size strings are accepted.
Use clean absolute mount paths such as /data or /data/pg. The derived disk
identity trims leading/trailing / and replaces inner / separators with - and must match [a-z][a-z0-9-]{0,31};
identities and mount paths must be unique within the node. /, system paths
such as /etc, /usr, /root, and /var/lib/barn, and their overlapping
parents/children are refused. Changing a persistent disk’s identity or declaration
can require explicit migration; persistent does not mean every new definition
can automatically reuse the old disk.
Data disks are disposable test storage. up resets unrecognized or confirmed
damaged filesystems to the configured type and reports that old data was
discarded. This includes persistent disks: persistence controls destroy/recreate,
not retention of corrupt contents. Probe errors, missing devices, busy mounts,
and backend I/O failures do not authorize formatting. Root disks and host
shares are outside this recovery path.
Shares
macOS limitation: Barn’s guarded directory
sharing is not supported on macOS; a node with vm_shares cannot start. The
candidate also warns during validate and plan. Omit shares in new macOS labs; full macOS sharing support remains pending.
Changing an existing node’s shares requires recreate, which replaces the root
disk. Preserve needed data before considering that operation. Barn does not
fall back to unchecked host paths.
readonly defaults to true; each node accepts at most eight shares. Host and
guest paths must be clean absolute paths; ~ and relative paths are not expanded.
Host directories must already exist, be caller-owned, have no symlink path
components, and not overlap BARN_HOME. On Linux, prefer the real path (realpath /path/to/source). The
0.9 candidate includes that replacement path in a symlink diagnostic.
Within one node, host sources and guest targets must not overlap. Across nodes,
overlapping host sources are allowed only when all are read-only. Guest targets
must not overlap data-disk mounts, reserved system paths, or the login user’s
.ssh directory. Shares are for trusted development files, not PostgreSQL data. If a requested writable share
cannot support guest writes, Barn tries read-only access and reports the
limitation. Correct permissions and repeat up to retry; Barn does not
recursively change the ownership of host files.
A missing source fails only its node during
up or start. Other selected nodes continue. Restore the original directory or
its host mount and retry that node; Barn never creates an empty replacement.
Restart, reload and recreate validate sources before stopping existing nodes.
Names and addresses
Node name order: nodename, then <pg_cluster>-<pg_seq>, then
node-<last-octet>. Names must be unique, 1–63 lowercase letters/digits/hyphens,
and may not begin or end with -. An explicit non-empty nodename takes
precedence, so unrelated pg_cluster or pg_seq values need not derive a name.
vm_alias is a list of lowercase DNS-style names; aliases must not duplicate a
node name or any other alias in the deployment.
All managed hosts must be in one RFC1918 /24: .1 is the host, .2–.8
are reserved, and nodes use .9–.254.
Inside the guest the fixed-IP interface is the one carrying the inventory
address (ip -br addr); its name is not a Barn contract.
Drift
Barn hashes each resolved node. Added hosts are created by up; selected
existing stopped nodes are started; running peers keep their processes while unfinished guest setup is retried. Changed VM
definitions require per-node recreate; removed hosts are reported but never
destroyed. Deployment architecture, user, or subnet changes require whole-deployment
recreation. Image selectors are resolved to exact image identities by plan/up;
review the plan after a catalog update, even if the inventory text is unchanged.
Changing a field used to derive a node name appears as a missing
old node plus a new node, so prefer stable explicit nodename values.
2.2 - CLI
This reference describes the Barn 0.9.0 release candidate. Check
barn version before scripting against these contracts. See the release status.
The installed binary is the authoritative reference for its own version. Every visible command includes its operational boundary and copyable examples:
Bare barn prints a short welcome with next commands, exits 0, and exposes
actions[] in JSON/YAML. A bare namespace
such as barn image still exits 2 (human help in text mode, a structured usage
error in JSON/YAML). Explicit --help always renders human help and exits 0.
Use barn --version or barn version to inspect the build identity.
Commands
| Area | Commands |
|---|---|
| Prepare | setup, init, validate, doctor |
| Lifecycle | plan, up, start, stop, restart, reload, recreate, status, destroy, purge |
| Access | ssh, exec, logs, provision, ssh-config, hosts install/uninstall |
| Images | update, image list/info/pull/import/sync/prune/reset, repo scan/build/verify |
| Host network | network status/install/uninstall |
| macOS guests (unreleased) | mac …; see Mac Commands |
| Misc | version, completion |
No command refreshes the Catalog implicitly. update fetches the configured
repository’s Catalog, verifies it, and activates it; image sync is the
explicit recovery path for an exact URL or file. Ordinary commands use the
active local Catalog. Neither operation updates the Barn executable.
Frequently used commands have scoped aliases:
| Command | Aliases | Command | Aliases |
|---|---|---|---|
setup |
s |
validate |
v |
plan |
pl |
recreate |
rc |
status |
st |
destroy |
de |
ssh-config |
sc |
image |
images, im |
doctor |
dt |
network |
n, net |
exec / logs |
ex / l |
version |
ver |
up, ssh, init, start, stop, restart, reload, provision,
hosts, and completion have no aliases. Use the explicit barn purge spelling; rm is not a command alias.
Inside a namespace, hosts and
network use i/u for install/uninstall; network status uses st; and
image maps list=ls, info=in, pull=p, prune=pr, sync=sy,
and import=i.
Barn manages one deployment in the selected BARN_HOME (default
~/.barn), independently of the inventory directory. Commands using applied
state work from any directory. Configuration selection
is command-scoped; -f is deliberately not a global flag:
| Commands | Desired-state source |
|---|---|
setup [template] |
explicit -f, otherwise discovery, otherwise generate meta; template and -f are exclusive |
init [template] |
generate a new inventory; read no desired state; --force explicitly replaces the output |
validate |
explicit -f, then discovery; never applied state |
plan, up, reload, recreate |
explicit -f, then discovery, then the applied resolved specification |
| other lifecycle/access commands | no desired-state inventory; they use applied state |
When neither an inventory nor an applied deployment exists, interactive up
can generate the default inventory. It can also prepare missing host dependencies
and restore an intact inactive Barn network. This implicit preparation accepts
the setup plan; sudo may still request credentials. Use setup --dry-run to
review the host plan first. Scripts should run setup --yes explicitly; init
is optional when no inventory exists.
Important flags
| Flag | Meaning |
|---|---|
--json, --yaml |
machine-readable stdout; progress remains on stderr; intentionally no shorthand |
-v, --verbose |
bounded diagnostics on stderr |
-c, --cidr |
select the RFC1918 /24 for generated init/setup templates or host-network inspection/install |
-f, --file |
select an Inventory for commands that read desired state |
-r, --repo |
select a repository where exposed; overrides --mirror and BARN_REPO; validate gains this flag in the 0.9 candidate |
--mirror |
use the China official repository for setup, Catalog, and image-resolving lifecycle commands |
-m, --mode |
select host or shared where the command exposes the macOS network mode |
-d, --dry-run |
show a setup/image plan without changing state |
-y, --yes |
apply a displayed host/setup/image plan |
--force (init, destroy, recreate) |
overwrite generated output or skip the typed confirmation; long-only because -f selects the Inventory |
-n, --no-wait |
return once QEMU is running, without readiness or guest recovery checks |
--rollback (up, reload) |
remove the prepare artifacts of nodes that failed to prepare in this run |
--delete-persistent |
during whole destroy, also delete retained data disks; invalid with node selectors |
--purge |
whole-deployment disposal: delete disks, keys, and deployment state; keep images |
Flags belong to commands; the following table lists the less obvious scopes:
| Command | Local controls |
|---|---|
init |
--output/-o (default ./barn.yml; - prints), --cidr/-c, --force |
plan |
--file/-f, --repo/-r; no --mirror or --dry-run |
start, restart |
--no-wait/-n; no --file or repository selection |
provision |
required --script/-s; --sudo uses guest sudo -n; --parallel/-p is 1–4 (default 1); --timeout/-t is positive, at most 24h (default 1h) |
ssh-config |
--install/-i or --remove (exclusive); --name defaults to barn; removal accepts no nodes and requires no deployment |
logs |
--source/-s serial|qemu|events (default serial); --follow/-f; events accept no node |
image info, image pull |
optional image selector, --arch/-a amd64|arm64, --repo/-r; only pull accepts --mirror |
image import |
--sha256/-s; --name local-* also requires --boot/-b bios|uefi and --source-user/-u |
image prune |
--dry-run/-d or --yes/-y (exclusive); --repo/-r |
image sync |
URL or path, --repo/-r, and explicit --allow-downgrade |
network install |
--cidr/-c (default 10.10.10.0/24), --mode/-m (default host), --yes/-y; macOS-only --archive/-a, --interface-id/-i |
network status |
optional --cidr/-c; no --file |
network uninstall, hosts install/uninstall |
--yes/-y |
setup --dry-run and setup --yes are mutually exclusive. --cidr rebases a
generated template; it does not rewrite an explicitly selected inventory.
validate --repo is available in the 0.9 candidate and has no --mirror;
use --repo https://repo.pigsty.cc/barn when checking that catalog.
In the 0.9 candidate, network and hosts install/uninstall show a plan and
ask on a terminal (install defaults to yes, uninstall to no); without a terminal
they only show the plan unless --yes is supplied. For a fresh
macOS network, use setup; the candidate’s network install directs you there
before asking for sudo. --yes accepts Barn’s plan but cannot supply a sudo password.
Rare, selection, or safety-widening controls such as --mirror, --force,
--rollback, --remove, --allow-downgrade, --sudo,
--delete-persistent, and --purge are
long-only. On commands that read an Inventory, -f always selects a file;
logs -f retains the conventional --follow. -n always means --no-wait,
and -d always means a dry run.
With a deployment, barn purge performs the same disposal as
barn destroy --force --purge, without confirmation. It accepts no nodes or Inventory, removes the
complete deployment plus persistent disks, keys, state, and the default SSH
fragment, and keeps images and the host network. With no deployment it succeeds
without changing the image cache, and can remove provably owned retained disks.
Missing state never authorizes deletion of residual node artifacts whose identity
cannot be proven. In the 0.9 candidate, plain whole-deployment destroy
without a deployment also succeeds; destroy --force --delete-persistent and
destroy --force --purge without state instead fail with a hint to use purge.
Structured failures (0.9 candidate)
The following unified failure contract describes the Barn 0.9.0 release candidate.
Ordinary failures print error: <message> on stderr, the failing program’s last
stderr lines when an external tool failed, and a next: line when there is one
clear action. SSH child exit failures are silent because the child has its own
output. If a failing command supplies no richer typed result, structured mode
writes a generic failure object before returning the exit code:
error (the class below), message, and where they apply a stable reason,
next, operation_id, and command (name, argv, exit_status, signal, timed_out,
stderr) for a failed external program. Existing typed failure results are
never followed by a second JSON/YAML document. The closed generic error classes
are listed below. recreate_required and nodes_removed are now reason values
under error: "conflict", and the old resource_conflict class is now resource.
A nonzero exit does not guarantee that stdout has this generic envelope.
doctor, network, provision, lifecycle operations, and remote commands can
return their own report schemas. SSH child exits are internally classified as
remote_exit, but their public result has fields such as success, exit_code,
stdout, and stderr; its optional error is not the generic class contract.
Always preserve the process exit code and interpret the payload for that command.
Lifecycle results
plan is read-only and returns success even when its action is recreate or
blocked-removal; automation must inspect the action and create, recreate,
start, missing, and blocked fields. For catalog images, plans read local
configuration and catalog data without requiring QEMU or host networking.
A registered local-* image also undergoes cache validation and requires
qemu-img. Plans download no images. They show exact images, total resources, change reasons, and disk effects. The 0.9 candidate
also lists each data disk, including the implicit 128 GiB /data. up checks
host capabilities and address availability before applying changes. up creates missing nodes, starts stopped ones,
re-checks readiness of running ones, and rewrites the SSH client configuration
Barn installed from the complete applied deployment. recreate performs the
same full refresh; node destroy removes stale entries, and whole destroy
removes that configuration. start powers on stopped nodes and re-checks
readiness of running ones; start and restart also refresh SSH aliases.
Destructive drift returns a conflict that names the next commands:
barn plan, then barn recreate <node> or barn destroy <node>. On a
terminal those commands ask you to type the confirmation word; --force is for
scripts. If VM lifecycle succeeds but the SSH client configuration cannot be
written, the command reports a warning and remains successful; barn ssh
still works. Structured output carries integration warnings in warnings[].
The 0.9 candidate leaves symlinked or hard-linked ~/.ssh/config untouched,
publishes its fragment, and reports the Include line to add manually.
Guest management SSH is the readiness boundary. Optional setup failures are
reported in nodes[].warnings; completed recovery actions, including data resets,
are in nodes[].repairs. A usable guest with these limitations exits 0. Repeat
up to retry unfinished stages without restarting running VMs. Inspect the
warning fields when automation requires every configured feature.
A lifecycle batch with isolated node failures can exit 5 even when every selected
node failed; it reports
N of M node(s) failed: <node> (<stage>: <error>); .... Common stages include prepare,
start, readiness, bootstrap, guest-setup, stop, and status; readiness
or bootstrap failures add
run \barn logs . Structured output carries failures[]withnode, stage, and error(and optionalreasonin the **0.9 candidate**), plusrolled_backwhen–rollback` removed the prepare artifacts of nodes that never committed. See
A node did not become ready.
status shows node, state, IP, exact image, and CPU/memory. Use --verbose for
SSH ports, architecture, accelerator, and PID. TCG is marked in ordinary text
as well. One degraded node does not hide its peers; status exits 5 and retains
per-node errors and failures[]. Running means the VM process is running;
status does not claim to have checked guest readiness.
Starting commands also refresh Barn hosts and control-node SSH entries in
running guests. Stopped guests catch up when started. --no-wait skips guest
readiness, guest recovery, and that refresh; a later up completes them. Selected recreate
refuses remaining peer drift before stopping or deleting disks; select the
required nodes together as directed.
The control guest’s Barn-managed SSH entries accept replacement host keys without recording them in known_hosts, so recreated lab nodes remain reachable. User-added SSH entries are preserved.
Recovery
up and start isolate missing host-share failures by node; up also
continues existing stopped peers when a new node fails to prepare. Partial
results keep exit code 5 and preserve successful nodes. Retry hints retain the
inventory, repository and applicable flags; a start retry remains start.
Setup and its lifecycle retry share one operation_id. Even before deployment
state exists, barn logs --source events --json can read the bounded phase
trace after failed setup. Setup traces omit command arguments and authentication
data; retain the command output for its detailed cause. setup --dry-run writes
no trace. Successful destroy --delete-persistent and purge summaries describe
the final deletion/retention result; purge leaves the image cache and host network.
Owned persistent disks left by a previously removed node no longer block
destroying the remaining nodes. Ordinary destroy retains those disks; explicit
persistent deletion or purge is still required to remove them.
On macOS, fresh network setup finishes Homebrew discovery/installation or the pinned-archive download before requesting administrator authentication. Homebrew can invalidate an earlier sudo credential; the new order avoids that failure without widening the privileged operation. Failed downloads do not prompt.
Interrupted operations (0.9 candidate)
The candidate recognizes a recorded QEMU PID reused by an unrelated process as
a stopped node. An interrupted stop whose VM still runs is reconciled to running;
other unfinished transitions name the command that can finish them. destroy
settles interrupted transitions itself. A failed first up can be retried after
editing the inventory because its uncommitted artifacts are rolled back from the
journal.
Logs and environment
logs defaults to the guest serial console; --source qemu reads QEMU diagnostics,
and --source events reads the deployment-wide bounded event log. With --follow,
text output streams bytes and JSON emits NDJSON records (YAML emits a document
stream). The 0.9 candidate renders ordinary event/QEMU log reads as readable
records, showing a QEMU argv only with --verbose.
| Environment variable | Purpose |
|---|---|
BARN_HOME |
absolute private state directory; default ~/.barn; not a symlink or broad directory such as your home |
BARN_REPO |
repository default; overridden by --mirror, then --repo where exposed |
BARN_OUTPUT |
text, json, or yaml; presentation flags override it |
BARN_VERBOSE |
boolean diagnostic default; presentation flags override it |
BARN_VMNET_ARCHIVE |
absolute path to the pinned socket_vmnet archive for macOS setup; digest checks still apply |
NO_COLOR |
non-empty disables color |
SSH passthrough and completion
barn ssh [node] [--] [command ...] opens a session or runs an optional
command. barn exec [node] [--] <command ...> requires a command and passes
through its exit status. Presentation flags before -- belong to Barn;
ssh arguments after -- are joined with spaces and interpreted by the remote
shell, like plain SSH. exec preserves argument boundaries; explicitly use
sh -c when you need shell expansion or pipelines. A single command string
retains the shell shorthand. Before --, only zero or one known node is accepted.
For convenience, omitting -- uses a known first argument as the node, or
runs all arguments as a command on the default node with a warning.
In the 0.9 candidate, a first argument containing a digit or - is checked
for a near-miss node name: at most one edit for words of four characters or fewer,
or two edits for longer words. Such a typo is refused; ordinary commands such as
ls, df, and wc still run. Use an explicit -- in scripts.
Load barn completion bash|zsh|fish|powershell for command and scoped-flag
completion. It also provides command aliases, templates, image aliases, closed
flag choices, and best-effort node names from the desired or applied
specification. In the 0.9 candidate, -f completion filters for YAML files.
Exit codes
This table gives the Barn 0.9.0 release candidate exit-code contract. Missing inventory and unknown images are usage errors (2); setup and network failures use runtime (1) or capability (3), according to their cause.
| Code | error |
Meaning |
|---|---|---|
| 0 | success, including usable guests with optional limitations | |
| 1 | runtime |
the operation ran and failed (a tool, download, or guest failed) |
| 2 | usage |
the command line or inventory is wrong |
| 3 | capability |
the host lacks a tool, the Barn network, or a privilege |
| 4 | conflict |
the deployment’s state forbids it, or another barn command holds it |
| 5 | partial |
node-level batch failures; inspect failures[]; any successful peers are retained |
| 6 | resource |
a host address, port, subnet, or disk is taken |
| 7 | integrity |
a verified digest, signature, identity, or ownership did not match |
| 130 | cancelled |
interrupted (SIGINT/SIGTERM) or confirmation declined |
In the 0.9 candidate, a modifying command that finds another Barn command
holding the deployment waits up to 10 minutes and names it; timeout is exit 4
with reason: "deployment_busy". status, ssh, exec, ssh-config, and
hosts do not wait. status reports the concurrent operation in note while
showing recorded state; this is not a guarantee of a completed deployment.
ssh and exec pass through the SSH child exit code unchanged, including
255. That value may indicate an SSH connection failure or a remote command
returning 255; text, JSON, and process exit status agree.
2.3 - Mac Commands
Barn 0.9.0 release candidate; unreleased. See
Status for current validation and release checks.
Use barn mac --help from the binary you run.
barn mac requires Apple Silicon and macOS 27 or later, and runs as the
logged-in user; it refuses to run as root. On other hosts the command is
hidden, and commands that need the Mac component fail with
mac_host_unsupported. Bare barn mac is barn mac ls.
Commands
| Command | Purpose |
|---|---|
ls |
every machine with state, address, SSH, macOS, resources and shares; aliases list, status, st |
up [name] |
create the machine if needed, start it, wait for SSH and sudo |
start [name...] |
start existing machines |
stop [name...] |
shut down normally; power off after two minutes |
restart [name] |
stop, then start, applying configuration changes |
open [name] |
show the desktop, starting the machine first if needed |
ssh [name] |
interactive shell, or, after --, a command line for the guest shell |
exec [name] -- cmd |
run a command, keeping argument boundaries |
configure name |
change a machine’s settings |
recreate name |
replace the machine with a fresh macOS, keeping its settings |
destroy name... |
delete machines |
password [name] |
show or copy the login password |
ssh-config |
print, install or remove the OpenSSH entries |
logs [name] |
recent runtime log |
setup |
prepare the macOS base without creating a machine |
image ls |
restore images and bases with the machines using them; alias list |
image update |
prepare Apple’s newest macOS 27 as the default base |
image prune |
list, and with --yes delete, unused images |
doctor |
check the host, the component, the base and every machine |
Flags by command
| Command | Flags |
|---|---|
up |
--cpu --memory --disk --user --share --clipboard --subnet --ipsw -y/--yes -n/--no-wait --open |
start |
--all -n/--no-wait --open --recovery |
stop |
--all --force |
restart |
-n/--no-wait --open |
configure |
--cpu --memory --share --unshare --clipboard --subnet |
recreate |
--update --force -n/--no-wait |
destroy |
--force |
password |
-c/--copy |
ssh-config |
-i/--install --remove |
logs |
-n/--lines (default 100, at most 10000) |
setup |
--ipsw --disk -y/--yes |
image update |
--ipsw -y/--yes |
image prune |
--installers -y/--yes |
A command without a machine name acts on the only machine, or on mac1; with
several machines and none named mac1, it asks for a name. start and stop
accept several names, or --all. destroy and recreate describe what they
delete and ask for the command name to be typed; --force confirms without a
terminal.
Flag values
| Flag | Value |
|---|---|
--cpu |
virtual CPUs, at least 2 and at most the Mac’s logical CPUs; default 4 |
--memory |
16G, 16GiB, 16GB or bytes; at least 4 GiB and at most installed memory; default 8 GiB |
--disk |
capacity of the base, at least 32 GiB; default the prepared base’s, 100 GiB. Another capacity installs another base |
--user |
administrator account; default your macOS user name, or barn when that is not a valid account name |
--share |
[name=]path[:ro|:rw], repeatable, at most 8; the name defaults to the last path component; ~/ expands to your home |
--clipboard |
on or off; default on |
--subnet |
a canonical private /24 such as 10.10.30.0/24, or auto for the first free one |
up refuses creation options that differ from an existing machine instead of
ignoring them, and its next: line names the fix. CPUs, memory, shares, the
subnet and the clipboard change with configure. The account and disk capacity
are fixed for a machine’s lifetime, and recreate keeps them: other values
need another machine. A different macOS comes from image update, then
recreate --update.
Machines
- Names: 1–32 lowercase letters, digits and inner hyphens, starting with a
letter:
mac1,dev,build-2. - Running limit: two macOS virtual machines per Mac, counting other tools and macOS installation. Barn never stops a machine to make room.
- Account: an administrator with passwordless sudo, SSH key login, desktop
automatic login and Remote Login. SSH password login is disabled. The login
password is random and stored in the machine’s
passwordfile. - Guest names: the computer name is the machine name; the local host name is
barn-<name>, so the guest answers asbarn-<name>.local. - Shares: one VirtioFS device mounted by macOS under
/Volumes/My Shared Files/<name>. Shares must be existing directories, not symlinks, and change only while the machine is stopped. - Clipboard: plain text, synchronized over the machine’s SSH connection when its window gains or loses focus; at most 1 MiB; items marked concealed are never sent.
- Stop: normal shutdown through macOS in the guest; if the machine is still
running after two minutes it is powered off and the result carries
"forced": true.--forcepowers off at once.
Networks
Each machine’s network is created by its own runner process when the machine starts and disappears when it stops. No daemon and no root is involved.
| Item | Value |
|---|---|
| Subnet | first free private /24 from 10.10.20.0/24 to 10.10.59.0/24, avoiding host routes and other machines; --subnet selects one |
| Gateway | .1, the Mac |
| Guest address | .10, by DHCP reservation for the machine’s MAC address |
| Reachability | the Mac and the internet through NAT; not other machines, not the LAN |
start refuses a subnet that a host route now overlaps, such as a VPN, with
reason mac_subnet_in_use. SSH host keys are pinned to the machine instance,
not its address, so configure --subnet keeps trust.
macOS Local Network privacy stops third-party programs from connecting to
these networks unless their app is allowed under Privacy & Security → Local
Network; the error is “No route to host”. Barn itself connects through
Apple’s /usr/bin/nc and /usr/bin/ssh, which are exempt.
JSON output
Every command honors --json and --yaml. Progress goes to stderr.
ls
| Field | Values |
|---|---|
state |
prepared (never booted to readiness), starting, running, stopping, stopped, unknown |
ready |
true only when this check reached the guest over SSH with sudo |
ssh |
ready, pending (first boot in progress), unavailable, offline, unchecked |
observed |
present when the guest reports another macOS build than its base |
window_visible |
present while the desktop window is shown |
error, warnings |
the last recorded failure, and SSH problems found by this check |
Lifecycle results
up, restart, open, recreate and configure return one result;
start, stop and destroy return {"machines": [...]}, one result per
machine, even for a single name.
| Field | Values |
|---|---|
action |
created, started, running (already running), restarted, recreated, opened, configured, stopped, powered_off, already_stopped, destroyed, absent |
forced |
true when a normal stop had to power the machine off |
window |
true when the desktop was shown |
warnings |
non-fatal follow-ups, such as a change that applies at the next start |
exec --json returns the same object as Linux barn exec --json: node is
the machine name, and exit_code, stdout and stderr come from the guest.
An interactive ssh has no JSON form.
Failures
Exit codes are the Barn CLI’s. A remote command’s own
exit status passes through ssh and exec unchanged. JSON failures carry a
stable reason and a next command:
| Reason | Exit | Meaning and next step |
|---|---|---|
mac_host_unsupported |
3 | not Apple Silicon, or older than macOS 27 |
mac_runner_missing |
3 | the Mac component is not installed next to the CLI |
mac_runner_protocol |
3 | CLI and component come from different builds; install them together |
mac_root |
2 | run as your normal login user, not with sudo |
mac_download_consent |
2 | downloading macOS needs --yes without a terminal, or use --ipsw |
mac_machine_absent |
4 | no machine by that name; barn mac up NAME |
mac_not_initialized |
4 | the machine has not finished its first boot; barn mac up NAME |
mac_not_running |
4 | ssh/exec need a running machine; barn mac start NAME |
mac_running |
4 | a change needs the machine stopped; barn mac stop NAME |
mac_configuration_conflict |
4 | up options differ from the existing machine; the named configure or recreate |
ssh_config_linked |
4 | ~/.ssh/config is a link; add the printed Include line yourself |
mac_vm_limit |
6 | two macOS VMs already run; stop the named one |
mac_subnet_in_use |
6 | a host route now overlaps the machine’s network; configure NAME --subnet auto |
disk_full |
6 | not enough free space to download or install macOS |
mac_machine_damaged |
7 | a machine that booted before lost disk or identity files; its directory is kept |
mac_readiness_interrupted |
130 | a stop interrupted a first-boot SSH wait |
Files
Runtime sockets live in /tmp/barn-mac-<uid>-<hash>/. ssh-config writes
~/.ssh/barn-mac_config and one # barn-mac:include block in
~/.ssh/config, independent of the Linux # barn:include block. The desktop
window positions are saved in ~/Library/Preferences/io.pgsty.barn.mac-runner.plist.
Inside the guest, Barn writes ~/.ssh/authorized_keys,
/private/etc/sudoers.d/80-barn, /etc/ssh/sshd_config.d/000-barn.conf,
the computer and local host names, and disables sleep with pmset.
2.4 - Images
Barn uses a materialized static-file Catalog plus immutable qcow2 artifacts. Official and HTTP Catalogs are signed; explicitly selected local and HTTPS repositories may be unsigned. A Catalog update does not require a new Barn binary, but the binary decides which signing keys and image safety rules are trusted.
EL7, EL9 9.3/9.6, and EL10 10.0 are deprecated compatibility images. All
other built-in versions are supported.
Aliases and pull order
Barn 0.9.0 embeds Catalog 2026092902: 9 families and 39 artifacts. el7 is
amd64-only; every other family has amd64 and arm64 artifacts. EL9 includes 9.3, 9.6, 9.7, and 9.8;
EL10 includes 10.0, 10.1, and 10.2. u24:stable (Ubuntu 24.04) on the native
architecture is the default request.
The stable versions below include both amd64 and arm64. This is the
embedded Catalog snapshot, not a live repository listing. Run barn update
then barn image list to inspect the currently selected repository. Dated
public endpoint checks and guest point-release observations are recorded in
Status.
| Family | Embedded stable | Distribution series |
|---|---|---|
d12 |
20260923.2610.1 |
Debian 12 |
d13 |
20260914.2601.2 |
Debian 13 |
el8 |
8.10.20240528.2 |
Rocky Linux 8.10 |
el9 |
9.8.20260525.2 |
Rocky Linux 9.8 |
u22 |
20260926.0.0 |
Ubuntu 22.04 LTS |
u24 |
20260926.0.0 |
Ubuntu 24.04 LTS |
u26 |
20260927.0.0 |
Ubuntu 26.04 LTS |
Debian retains offline-installed XFS tools and the generated en_US.UTF-8
locale, with C.UTF-8 still the default. Ubuntu retains Canonical’s original
image bytes; cloud-init configures accounts and networking at startup.
The Debian 12 upstream build dated September 23 still lacks XFS tools and
en_US.UTF-8, so those adjustments remain necessary. The Ubuntu builds above
already include both; their amd64 and arm64 guests passed locale and XFS
data-disk checks on September 29.
A Catalog refresh changes newly resolved stable requests. Existing VMs and
explicitly pinned versions continue using their original base images.
| Alias | Distribution | Architectures | Boot | Status |
|---|---|---|---|---|
el7 |
CentOS Linux 7.9 / 2211 | amd64 | BIOS | deprecated |
el8 |
Rocky Linux 8.10 | amd64, arm64 | UEFI | supported |
el9 |
Rocky Linux 9.7 / 9.8 | amd64, arm64 | UEFI | supported |
el9 |
Rocky Linux 9.3 / 9.6 | amd64, arm64 | UEFI | deprecated |
el10 |
Rocky Linux 10.1 / 10.2 | amd64, arm64 | UEFI | supported |
el10 |
Rocky Linux 10.0 | amd64, arm64 | UEFI | deprecated |
d12, d13 |
Debian | amd64, arm64 | UEFI | supported |
u22, u24, u26 |
Ubuntu | amd64, arm64 | UEFI | supported |
Catalog status values are advisory rather than an activation switch:
supported has passed the declared support gate; testing is available for
explicit test/risk acceptance but is not supported; deprecated is retained
only for EOL compatibility; and unknown has no support classification.
Non-supported entries remain runnable and print a warning.
For a pull, Barn:
- reads the selected repository’s active local Catalog once for the complete
command: the Catalog embedded in this build, or the one last activated for
that repository by
barn updateorimage sync; - resolves
image[:channel]orimage@version-prefix, defaulting tou24:stablewith the official Catalog; standaloneimage pulldefaults to the native architecture and accepts--arch, while lifecycle resolution honorsvm_arch; - reuses a local file only after size, SHA-256, and qcow2 checks pass;
- otherwise downloads the exact Catalog-named artifact, with retries and resumption; the two official repositories can fall back to one another, while custom repositories remain exclusive. All accepted bytes must match the Catalog. An immutable upstream URL is provenance, not a fallback.
Released builds use https://repo.pigsty.io/barn by default. Long-only
--mirror selects https://repo.pigsty.cc/barn; precedence is --repo,
--mirror, BARN_REPO, then the global default. Both official roots retain
canonical signed-Catalog trust. Repository selection determines both the local
Catalog slot and the source of downloads. Keep selecting the same custom
repository even when its image bytes are cached. Barn never refreshes the
Catalog on its own; ordinary image resolution can work offline with an active
local Catalog and verified cache. Run
barn update to fetch, verify, and activate the selected repository’s current
Catalog. Catalog updates use that selected source; a failed update is an error.
An image download fails when none of its permitted sources supplies verified bytes.
Changing --repo alone does not fetch or activate that repository’s Catalog;
run barn update --repo <root> before using its custom aliases.
Verified writable cache files are made read-only again. A damaged, unreferenced
cache file is preserved with a .corrupt-<timestamp> suffix before replacement;
a base image still referenced by a VM is kept in place and reported as an error.
Runtime policy
Matching architectures use native HVF/KVM except one catalogued incompatibility:
the stock EL8 arm64 64K-granule kernel cannot run through Apple HVF, so Apple
Silicon uses visible same-architecture TCG automatically. Explicit foreign
vm_arch also uses TCG. amd64-on-arm64 uses a single translation thread to
preserve x86 memory ordering. TCG results are not performance evidence.
EL7 is deliberately limited to native Linux/amd64. Linux setup installs only
the native QEMU family; foreign architectures require the matching system
emulator and UEFI firmware before up or recreate can proceed. For Catalog images, plan
resolves the intended image and runtime without requiring those tools. Named
local-* imports are byte-checked during resolution and still need qemu-img.
Unsigned repositories must be local paths or HTTPS. HTTP repositories require a Catalog signed by a trusted key. Immutable upstream artifact URLs must be HTTPS.
Trust and verification
Current ordinary builds embed both production public verification keys. The private signing keys are external to the source repository. Catalog activation rejects unknown keys, malformed content, equivocation, and revisions below the repository-scoped high-water mark unless the operator explicitly allows a downgrade.
Every accepted image must be a size- and SHA-256-matched plain qcow2 with no backing file, external data file, encryption, or unknown incompatible feature. Verified base images become read-only; node root disks are overlays and never modify the base.
image reset restores the embedded Catalog but keeps the anti-rollback
high-water mark.
barn update checks the repository now and activates a newer Catalog. Barn
never refreshes the Catalog on its own; the Catalog embedded in each release is
used until you update. image sync is the recovery path for an exact URL or
file, including a downgrade.
For repository-scoped recovery, pass the same root explicitly:
--repo selects the independent active-Catalog and high-water slot. The source
argument does not change this selection. image sync and image reset accept
--repo, but not --mirror; when --repo is omitted they use BARN_REPO
or the compiled default. For an unsigned custom Catalog, the exact source must
be the selected root’s catalog.json.
Static repository format
The published root is deliberately small:
repo.yaml stores author intent: defaults, aliases, channels, exact versions,
architectures, boot mode, status, and optional provenance-only upstream URLs.
source_user records the image’s declared source login identity, for example
rocky in an upstream image or dba after Barn’s official normalization.
The pipeline takes the upstream account separately when sanitizing a candidate.
Catalog/import metadata does not replace the deployment SSH user (dba by
default) or itself normalize the image. The file contains no generated
checksum or size fields. catalog.json uses the same logical tree but
materializes each variant’s file, SHA-256, artifact size, and virtual size.
repo.yaml is schema: 1; the generated catalog.json is the schema-3
Catalog that Barn embeds and signs.
With no explicit file, the two expected artifacts are
images/u24-1-amd64.qcow2 and images/u24-1-arm64.qcow2. A variant may use a
safe basename override for an existing custom file.
Channels and numeric prefixes are movable selectors. An exact key wins;
otherwise a prefix matches on dot-component boundaries and chooses the
numerically newest version (el9@9.7 selects the newest 9.7 build, while
el9@9 selects the newest 9.x release). The immutable artifact identity remains
(image, exact version, arch):
barn repo scan is read-only. build performs strict YAML validation,
full qcow2 inspection/checking, and atomic Catalog replacement without changing
repo.yaml or QCOW bytes. verify requires the generated Catalog bytes to
match a fresh materialization exactly. build and verify require local
qemu-img; scan does not. Build on a QEMU host, then publish immutable
artifacts first and the Catalog
plus its matching signature last. Update a signed Catalog/signature pair
together where possible; an inconsistent pair fails verification. Increase
revision when changing Catalog contents.
Local layout and imports
Images live under BARN_HOME/images (default ~/.barn/images): family
directories contain downloaded artifacts, manifests/ stores the active
Catalog with an independent high-water entry per repository, and local/ plus
local-images.json hold imports.
The expected --sha256 is optional in the CLI; supplying an independently
obtained trusted digest adds an explicit authenticity check to the mandatory qcow2
inspection. Import copies and verifies the file; it does not clean credentials,
install cloud-init, detect the guest CPU architecture, or prove that it boots.
Named local aliases must begin with local-, so a future signed Catalog cannot
shadow them. --name, --boot, and --source-user must be supplied together.
A named import records the importing host’s native architecture; there is no
image import --arch option. Use a static repository with explicit variants
for foreign-architecture artifacts. Aliases are immutable: choose a new name
for different bytes or metadata. Use vm_image: local-mybase in an inventory
to select a named import; unnamed imports only populate the cache.
Pruning
Bare prune and --dry-run only report candidates; --yes deletes them.
Prune protects the union of all artifacts in the selected active Catalog,
applied node image digests, and registered local aliases. Therefore a cached
Catalog image or named import is retained even when no VM uses it. Unprotected
images and recognized stale staging files are candidates; unsafe or damaged
files cause an error. Use the same --repo when inspecting a custom Catalog’s
cache policy. Images remain cached after destroy, destroy --purge, and purge.
The compiled schema-3 Catalog can be exported byte-for-byte with
go run ./tools/catalogexport /absolute/new/catalog.json. A public Catalog at
the embedded version must use those exact bytes; same-version different bytes
are rejected as equivocation. Release signing and image Catalog signing remain
separate trust domains.
2.5 - Image Pipeline
The low-level packaging/image-pipeline/build.sh accepts one already-downloaded
immutable qcow2 and an independently obtained SHA-256. It never downloads,
uploads, touches Barn runtime/network state, reads signing keys, or marks an
image supported.
Run these commands from the Barn source checkout. Python 3 and qemu-img
are required; offline additionally needs working virt-customize and
virt-cat tools from libguestfs. These are build-host dependencies, separate
from the dependencies that barn setup installs for running VMs.
Modes
validate: copy/re-hash, force qcow2 inspection, validate the single backing chain, runqemu-img check, and emit an explicitly unpublishable evidence bundle. Guest credentials are not changed.offline: additionally use libguestfsvirt-customize --no-networkandvirt-caton the staged copy. It rejects unrelated UID/GID 88 occupants, normalizes the lockeddba/adminidentity, disables password/root SSH, removes keys/history/host identity/cloud-init cache, restores targeted SELinux labels, and reads back a deterministic marker.
Official candidate matrix
build-official.py wraps the same offline boundary for a fixed eight-target
matrix: Debian 12/13 and Rocky Linux 8/9, each on amd64 and arm64. Every
upstream qcow2, RPM/DEB input, release name, digest, and source epoch is pinned
in official-v1.json.
Without --fetch, every locked input must already exist in the two canonical
cache directories. With it, the wrapper downloads only the pinned HTTPS URLs
and rejects any digest mismatch before invoking offline normalization. Debian
12/13 install the locked XFS userspace closure; Rocky Linux 8 installs the
locked python36 and python3-pip RPMs, and Rocky Linux 9 needs no extra
package input. Rocky Linux 8 uses its shipped RHEL chrony template and
chronyd service when cloud-init enables NTP. SELinux label restoration
belongs to normalization, not an additional package set.
Debian also generates en_US.UTF-8 while retaining C.UTF-8 as the default.
Both the guest normalization script and host-side marker validation check these
postconditions so a base-image refresh preserves these guest requirements.
Ubuntu uses dated, unmodified official images outside this offline matrix.
Each result remains an unsigned testing candidate. To build the complete
matrix, omit --target; repeat it to select several targets. --list shows
the exact releases pinned by this checkout. The matrix currently contains
Debian 20260923.2610.1/20260914.2601.2 and Rocky Linux
8.10.20240528.2/9.8.20260525.2.
Assembly takes parent directories containing the named bundles, not the individual bundle directories. If all eight builds were written below one output root, assemble them with:
Repeat --assemble-from if builds are split across roots. Assembly requires
exactly one bundle for each of the eight targets, creates a new static
repository, and runs barn repo build plus verify using barn on PATH
(or --barn /absolute/path/to/barn). Unlike build mode’s existing output
root, the assembly destination must not exist. Its channels are candidate,
not stable: use d13:candidate, for example. This does not perform native
smoke, signing, upload, or Catalog publication.
Validate one downloaded image
Source/output paths must be absolute; source is canonical, regular, non-symlinked, stable while copied, and at most 16 GiB. Output must not exist. The builder uses an exclusive adjacent lock, mode-0700 staging, and one final rename. Failure removes only its guarded staging directory.
Every successful bundle contains the read-only qcow2, recipe, SLSA provenance,
SPDX boundary SBOM, manifest-candidate.json (testing), validation evidence,
and checksums. The candidate manifest is a pipeline evidence format; repository
assembly produces the runtime schema-3 catalog.json. The SPDX file describes
the declared input/build boundary and is not a complete package inventory of
the guest filesystem. Signing is deliberately outside this pipeline. Validate
mode is
byte-reproducible for fixed inputs/tools; offline mutation must be built twice
and compared before release evidence is accepted.
A release still needs runtime smoke on each declared host/guest path, explicit
review of support status and provenance, a new Catalog revision, production
signing, and public artifact verification. Build success alone does not
authorize a supported status or prove that a candidate is publicly available.
3 - About Barn
- Design explains why Barn has one Inventory, one deployment, and one fixed-IP network.
- Status separates implemented behavior, native validation, and remaining release gates.
- Engineering defines source, generated-output, package, image-pipeline, and evidence boundaries.
3.1 - Design
One useful abstraction
Barn boots one Pigsty Inventory as one local QEMU deployment. It deliberately has no project marker, project registry, lease model, provider layer, or second configuration format.
State lives under BARN_HOME (default ~/.barn) for one Unix user. The
product assumes one active
Pigsty deployment per computer; this is not a root-enforced cross-user
singleton.
Node-level convergence
Barn extracts only the documented VM and Pigsty-native fields, computes
per-node hashes, and keeps applied state plus process identity. Additions are
incremental. Changes require an
explicit per-node recreate. up also starts selected existing stopped nodes;
already-running peers keep their processes while unfinished guest setup and
managed hosts/SSH entries are refreshed. Unrecognized or confirmed damaged
test data filesystems may be reset, including persistent disks; see
Data disks. Absence never authorizes deletion.
Runtime selection
Guest architecture is deployment-wide desired state. Omitted/native follows
the host; explicit amd64 or arm64 selects that Catalog artifact exactly.
Native HVF/KVM remains the default. A foreign architecture or one catalogued
image/host incompatibility selects a fixed TCG profile; there is no user
accelerator argument and no arbitrary failure fallback.
The effective architecture and accelerator are persisted in each QEMU
invocation and exposed by status. Before destructive recreate, Barn proves
the selected QEMU binary and version, network backend, image bytes, boot mode,
and firmware. A later binary changing runtime policy cannot mix new nodes with
old invocations: runtime drift requires whole-deployment recreation.
Two NICs, one fixed subnet
The management NIC supplies DHCP, DNS, egress, and loopback SSH. The fixed-IP NIC supplies host/peer/Ansible traffic. macOS uses socket_vmnet. Linux follows active NetworkManager; otherwise it uses systemd-networkd and connects through the distribution bridge helper. Inactive networkd is started only after an activation-safety scan proves existing units cannot claim a real host link.
On Debian, the helper is temporarily and reversibly scoped to a group the caller actually belongs to. A real unprivileged QEMU bridge smoke must pass before setup accepts the network; failure rolls the install back automatically.
Storage and configuration have different lifetimes
The Inventory records desired VM definitions. Applied state records what was created, including the exact base-image identity and runtime invocation. Changing a Catalog channel does not rewrite an existing root disk.
Verified base images are shared read-only; each VM writes to its own root overlay. Data disks have a separate preservation contract: normal destroy retains persistent disks, while explicit disk deletion or purge removes them. Cache pruning has another boundary and also protects the active Catalog and registered local aliases. See Storage and access and Images.
Safety boundary
QEMU and all guest artifacts run as the caller. Root is limited to host package installation, network setup, and the optional hosts publisher. Destruction requires matching ownership, containment, node identity, QMP/process identity, and an allowlist of artifacts. Ambiguity stops the operation.
3.2 - Status
Barn 0.9.0 is an unreleased candidate. Source checks, local builds, packages, CI, releases, and the public site are verified separately. Installation instructions are in the Quick Start.
Documentation baseline
| Object | Current identity | How to use it |
|---|---|---|
| Application and current docs | Barn 0.9.0 release candidate | Install Homebrew HEAD or build from source; release packages are not yet published. |
| CLI and configuration | barn, barn.yml, BARN_* |
Check barn version before applying version-specific guidance. |
| State and host resources | ~/.barn, Barn networking and helpers |
One Linux deployment per user; macOS guests use separate state. |
| Image repository | /barn at the official endpoints |
Signed Catalog 2026092902 published and publicly verified on 2026-09-29; see the image record below. |
The final release commit, artifact digests, and installation channels will be recorded after publication and download verification.
macOS guests
barn mac runs macOS 27 guests on Apple Silicon; see the guide
and reference. Its component is Barn Mac.app, with
signing identifier io.pgsty.barn.mac-runner and independent $BARN_HOME/mac state.
On 2026-09-29, local CLI/hosts-helper tests, native Bridge/image/exit-prompt/menu
tests, the runner build, ad-hoc signature checks, and probe passed.
These checks started no VM and do not establish complete Mac lifecycle acceptance.
Remaining release checks
- The full source, archives, DEB/RPM, installer, and cross-platform checks at the final Barn commit;
- Fresh host setup and Linux/macOS VM lifecycle and cleanup;
- Mac Developer ID signing and notarization;
- Barn 0.9.0 publication and download verification, including Homebrew.
Linux image Catalog: 2026-09-29
Signed Catalog 2026092902 is published at both official /barn endpoints:
nine families and 39 artifacts. The normalized Debian and Rocky Linux images
use Barn configuration and metadata throughout.
| Family | Stable version | Architectures |
|---|---|---|
| Debian 12 | 20260923.2610.1 |
amd64, arm64 |
| Debian 13 | 20260914.2601.2 |
amd64, arm64 |
| Rocky Linux 8 | 8.10.20240528.2 |
amd64, arm64 |
| Rocky Linux 9 | 9.8.20260525.2 |
amd64, arm64 |
| Ubuntu 22.04 / 24.04 | 20260926.0.0 |
amd64, arm64 |
| Ubuntu 26.04 | 20260927.0.0 |
amd64, arm64 |
The six Debian 13 and Rocky Linux images passed UEFI boot, SSH, dual-NIC,
UID/GID 88, Python, cloud-init, and XFS data-disk checks. The amd64 images used
KVM; Debian 13 and Rocky Linux 9 arm64 used HVF. Rocky Linux 8 arm64 used TCG
because its upstream 64 KiB kernel is incompatible with Apple HVF. Rocky Linux
8 uses its shipped RHEL chrony template and chronyd service.
The Debian 12 and Ubuntu images passed native KVM/HVF boot, SSH, dual-NIC,
UID/GID 88, locale, and XFS data-disk checks. Debian images include the locked
XFS tools and en_US.UTF-8, retaining C.UTF-8 as the default; Ubuntu keeps the
original Canonical bytes. These image checks used isolated QEMU user networks;
full host networking and VM lifecycle acceptance remains a separate release check.
The embedded and published Catalog bytes match. Both official endpoints pass signature verification, and the new objects pass public download checks. See Images for version selection and update behavior.
3.3 - Engineering
This page describes the Barn 0.9.0 release candidate. Build commands use your current checkout; record its commit and uncommitted changes. Source builds and local checks do not establish a published release.
Repository boundary
The Barn source repository contains code, tests, build/package definitions,
legal notices, README.md, CHANGELOG.md, CONTRIBUTING.md, SECURITY.md,
and bilingual application release notes. This site provides user, design,
operator, and release documentation. Runtime behavior and command flags must
be checked against the matching source and binary; an unpublished source
change is not evidence that a public package has the same behavior.
Review transcripts, scratch inventories, generated binaries, and release output trees are not production source inputs.
Generated output is disposable:
bin/— development builds;dist/and.goreleaser-*— release/snapshot staging;- root
barn,barn-hosts-helper, andcatalogsignbinaries; - Hugo
public/andresources/.
Build and source gates
make check runs module and shell checks, maintenance ownership, unit/race
tests, Vet, Staticcheck, dead-code and errcheck checks, vulnerability scanning,
four-target cross-builds, installer/image-pipeline tests, and license checks.
The Makefile defines the exact list. CI additionally checks the pinned
toolchain, Go formatting, whitespace, and GoReleaser configuration. Installation
of the quality tools is covered in Build from Source.
Packaging changes have a separate snapshot gate:
Install the versions in packaging/toolchain.env, including GoReleaser, nFPM,
and Syft. The snapshot target also needs the archive/package inspection tools
used by the verification scripts. Choose a new output directory directly under
the checkout; existing output is refused. A snapshot is local and does not
upload a release.
A source gate is not native VM evidence. macOS HVF, Linux KVM/networking,
package consumption, release publication, and public website rendering remain
separate checks. make image-pipeline-native-test runs the separate native
image-pipeline gate with QEMU/libguestfs and explicit image inputs; the required
BARN_IMAGE_PIPELINE_NATIVE_* variables are documented in
tests/image-pipeline-native-test.sh. It never downloads a test image.
Release and package contract
Release tooling under packaging/, .goreleaser.yaml, and .github/workflows
is source, even though its generated directories are not. Archives and Linux
packages contain the matching CLI and hosts-helper binaries, LICENSE, the
source README, and exact upstream license bytes reconstructed from modules
pinned by go.mod. Archives place the two binaries under bin/ and the license
texts under licenses/. Linux packages install /usr/bin/barn,
/opt/barn/libexec/barn-hosts-helper, and documentation under
/usr/share/doc/barn/.
BUILD_INFO.json is included in Linux packages. GoReleaser archives carry
build identity in the binary,
with release metadata alongside the published assets; do not assume every
archive contains that file. Generated dependency license files are staged at
build time. Detailed user documentation stays on this site.
Application releases are built in GitHub Actions, with checksums.txt,
release metadata, and SPDX SBOM assets. The current workflow does not produce
a separate application-release signature or provenance/attestation bundle.
Catalog Minisign signatures authenticate image catalogs and are a separate
trust mechanism.
Commit, tag, archive/package verification, CI, draft upload, public release,
and anonymous consumption are separate evidence. The tag workflow creates a
draft; it does not publish it. Pre-1.0 versions are GitHub prereleases and the
installer requires an explicit BARN_VERSION.
make release-local VERSION=<version> builds and verifies without publishing.
It requires a clean checkout at the matching v<version> tag, an origin
remote, pinned tools, and unused staging/output directories. Use the snapshot
path for reviewing an untagged candidate.
Image normalization
The low-level packaging/image-pipeline/build.sh accepts an explicit local
qcow2 source and never downloads or uploads. It copies and hashes the source,
forces qcow2 parsing, rejects backing/external/encrypted/unknown features, runs
qemu-img check, and can perform a no-network offline Guest mutation in an
explicit QEMU sandbox. UID/GID 88 collisions are rejected rather than rewritten
ambiguously.
build-official.py adds a fixed digest-pinned wrapper for Debian 12/13 and
Rocky Linux 8/9 on amd64/arm64. It may fetch only the locked source and offline
package inputs, then emits unsigned testing candidates and can assemble a
separate candidate repository. Native smoke, repeat-build comparison,
production signing, upload, and Catalog activation remain later gates.
Catalog bytes are exported with:
The exporter is atomic and refuses an existing output path. make catalog-sign
and make catalog-verify use the catalog Minisign key pair; production private
keys stay outside source and CI. Application checksums do not replace catalog
signatures.
Evidence policy
Design explains the implementation choices; Status records validation. Each status claim names its date, host, path, and remaining checks. Source changes need their own validation.