Barn turns one Pigsty-compatible Ansible inventory into one fixed-IP local Linux QEMU deployment on macOS or Linux. The inventory you hand to Pigsty is the same file that describes the virtual machines, so there is no second project format to keep in sync.
The independent barn mac command runs macOS 27
virtual machines on Apple Silicon, with their own state, images, SSH trust and
private networks. It never reads the Linux inventory and needs no root. It is a
preview: its native component is built from source until a release includes a
signed, notarized one.
Authoritative documentation: https://barn.pgsty.com/
barn up # prepare the host and start your first VM
barn ssh # connectOn first use in a terminal, up creates a one-node barn.yml if no inventory
or applied deployment exists. To customize it first, run barn init and edit
the file; use barn init full for the four-node template.
- One inventory, one deployment.
vm_*host variables describe the guests; everything else in the file stays opaque and is passed through to Pigsty untouched. There is no separate VM manifest. - Fixed IPs, not DHCP. Nodes get the addresses the inventory names, on a
host-global private network Barn installs once.
10.10.10.10is10.10.10.10across reboots and recreates. - Declarative, but never surprising.
barn planshows the difference between the inventory and the applied state. Removing a host from the file never destroys a machine — deletion is always an explicitbarn destroy. - Ubuntu 24.04 by default. New inventories use
u24:stable; setvm_imageexplicitly to choose another supported distribution. - Verified images. Guest images come from a signed catalog with SHA-256, qcow2, and virtual-size verification on every fetch. The selected repository supplies the final image bytes; immutable upstream URLs remain build-provenance markers.
- Recoverable lifecycle. Repeating
upcontinues unfinished work and keeps successful nodes available. QMP/process identity, image digests, and disk ownership are still verified before changing resources.
Barn is a local development-lab runtime. It is not a cluster manager, not a cloud provisioner, and not a container runtime.
plan reads local configuration and catalog data without requiring host setup.
It shows exact images, resources, and disk effects; up checks host capabilities
before applying changes. Starting commands also refresh the Barn-managed
hosts and SSH entries inside running guests. --no-wait skips readiness and
that guest refresh; a later up completes them.
Fast checks stay quiet. Longer operations share one live progress area with
elapsed time, download bytes/speed/ETA, and individual node readiness. A
successful start ends with a short result and a connection command. Partial
starts list usable and pending nodes, group repeated errors, and give a retry
command that retains the inventory path. status keeps the detailed table;
--verbose exposes diagnostic detail and time spent in each foreground stage,
including waits and prompts. Redirected output uses occasional plain
progress lines on stderr, and --json/--yaml keep stdout machine-readable.
Bare barn shows the next few actions; barn --help is the full reference.
up can install missing host tools and restore an inactive, verified Barn
network during an interactive session. The hosts-file helper is installed only
when barn hosts install needs it. A fresh, untouched default template can use
an available subnet; its original is saved as barn.yml.before-network-change.
Explicit -f files, edited templates, and existing deployments keep their subnet.
For unattended first setup, run barn setup --yes explicitly.
Interrupted image downloads retry and resume automatically. Official image
repositories can fail over to their counterpart; custom repositories stay
exclusive. Writable cache files are made read-only only after verification.
Damaged, unreferenced cache files are preserved as .corrupt-<timestamp> before
replacement; referenced backing files stay in place. Ctrl-C preserves completed
work and resumable downloads, so the same command can continue later.
An offline node-prepare failure can also be retried with up: recognized
uncommitted artifacts are cleaned first, while committed nodes and unexpected
files are preserved.
Guest readiness requires working management SSH and the expected login identity.
Data disks, shared directories, guest hostnames, node-to-node SSH, and private
network checks run independently with bounded waits. Unavailable features produce
ready · with limitations and exit 0, while other features remain usable. Each
failed disk or share is named; an unavailable data disk is never reported as
mounted. Internet access is optional, so offline labs can finish setup.
Repeating barn up [node...] retries unfinished guest setup and updates old
guest helpers in place. Running VMs keep their process and root disk; healthy
setup stages are skipped. --no-wait skips these guest checks as well as waiting
for readiness. No separate repair command is needed.
Each node gets a 128 GiB /data disk unless vm_disks says otherwise;
vm_disks: [] gives a node none. barn plan lists every node's data disks.
Data disks are disposable test storage. Working filesystems are reused. If a
configured data disk has no recognizable filesystem, or cannot mount and a
filesystem check confirms damage, up resets it to an empty filesystem and
reports that previous data was discarded. This can erase a persistent data
disk too: persistent retains it across destroy/recreate, not after filesystem
failure. A missing device, failed probe, busy mount, or underlying I/O error is
reported without formatting; other guest features remain available. Root disks
and host shared directories are never reset by this recovery.
Writable shares use the guest user's identity for newly created files. If the
host directory does not permit guest writes, Barn mounts it read-only and
reports the limitation. It does not recursively change host project ownership.
After fixing access, repeat barn up to retry the writable mount.
Host-side share failures are different from a guest mount limitation: QEMU must
open each configured source before that node can start. up and start keep
independent nodes progressing if one source is missing. Restore the original
directory or its mount and retry the affected node; Barn does not create an
empty source. Restart/reload/recreate check the selected sources before stopping
or deleting existing VMs.
Known macOS limitation: QEMU's 9p backend cannot reopen the directory
descriptor used by Barn on the tested macOS/QEMU 11.1.1 host. Such nodes cannot
start; the CLI now diagnoses the limitation without bypassing path identity
checks. Omit vm_shares for new macOS labs. Changing an existing node's shares
requires explicit recreation and replaces its root disk, so preserve needed
data first. Linux sharing and macOS support have separate acceptance gates.
If another process takes an automatically allocated management SSH port while a VM is stopped, the next start chooses another free port and updates VM state and SSH aliases together. Explicit application forwards keep their assigned ports. Running VMs keep their port and process.
SSH trust is keyed by VM instance UUID, allowing a recreated VM to reuse an SSH
port without inheriting its predecessor's host key. Changed keys for the same
instance still fail verification.
SSH aliases, guest hostname refresh, and diagnostic event writes are optional
after a successful lifecycle operation: their failures produce warnings with a
retry command, while the VM result remains successful. Explicit commands such
as ssh-config --install still report their own failures. Lifecycle integration
waits have separate budgets: 2 seconds for the SSH configuration snapshot,
5 seconds for guest metadata, and 1 second for diagnostic events. A timeout
preserves the VM result and gives a warning; a later up retries the refresh.
On an Apple Silicon Mac with macOS 27 or later:
barn mac up # create mac1, wait until SSH and sudo work
barn mac open # show its desktop; closing the window keeps it running
barn mac ssh # shell with the machine's own pinned key
barn mac up dev --cpu 8 --memory 16G --share ~/src
barn mac lsThe first up downloads macOS only from Apple (about 27 GB, resumable,
verified) after asking, or uses --ipsw with a restore image you already have,
and installs it once into a base; every machine is a copy-on-write clone of it.
Machines have their own private network with a fixed address, text clipboard
sharing while their window is focused, and shared folders under
/Volumes/My Shared Files. Apple allows two running macOS VMs per Mac.
Mac commands use $BARN_HOME/mac and never read barn.yml; Linux
destroy and purge leave them alone. They need the native component,
Barn Mac.app, next to barn. Releases do not include it until it is
signed and notarized by Apple; until then build both with Xcode 27 —
make mac-build puts them in bin/mac. See the guide
and the release checklist.
| Host | Accelerator | Minimum QEMU | Tier |
|---|---|---|---|
| macOS arm64 | HVF | 8.2.1 | 1 |
| macOS amd64 | HVF | 8.2.1 | 2 |
| Linux amd64 | KVM | 6.2 | 1 |
| Linux arm64 | KVM | 6.2 | 2 |
Tier 1 is the dated, natively validated matrix; tier 2 is cross-built and
package-verified against the narrower status published at
https://barn.pgsty.com/docs/about/status/. You also need qemu-img, UEFI
firmware for arm64 guests, and an OpenSSH client.
barn doctor reports host dependencies, persisted state, and network setup;
barn status audits live QMP/process identity and safely converges interrupted
transitions. barn setup installs what it can and asks for administrator
access only when a host transaction genuinely needs it.
Install the current development version with Homebrew:
brew install --HEAD pgsty/infra/barn
barn versionThe formula builds the CLI and hosts-file helper from source.
The 0.9.0 release packages are not published yet. Once available, download
install.sh, barn.rb, or the native package from the
Barn 0.9.0 release:
# From a release: user-scoped, no sudo, checksum-verified
curl -fLO https://github.com/pgsty/barn/releases/download/v0.9.0/install.sh
chmod +x install.sh
BARN_VERSION=0.9.0 ./install.sh
# Homebrew formula (shipped as a release asset)
brew install --formula ./barn.rb
# Debian/Ubuntu and RHEL-family packages are release assets too
sudo apt install ./barn_<version>_linux_amd64.deb
sudo dnf install ./barn_<version>_linux_amd64.rpmGitHub does not expose prereleases through /releases/latest, so
BARN_VERSION is required until a stable release exists. The installer
always verifies the selected archive against the checksums.txt produced by
the GitHub release workflow.
Barn uses barn.yml, BARN_*, and ~/.barn for its configuration and data.
From source:
make build
export PATH="$PWD/bin:$PATH"barn init full # a four-node inventory instead of one
barn validate # parse and resolve without touching anything
barn plan # what would change, and why
barn up # converge
barn update # fetch and activate a newer image catalog
barn status # audit/converge selected runtime state, from anywhere
barn ssh meta -- uptime # run something in a guest
barn hosts install --yes # publish node names into the host hosts file
barn destroy # explicit, confirmed teardown
barn purge # no-confirmation disposal; images/network remainEvery command accepts --json or --yaml for stable machine-readable output.
Presentation flags never change an exit status.
barn exec meta -- command arg... preserves argument boundaries, including
quoted spaces and empty values. Use sh -c 'script' for shell expressions.
The single-string shorthand (barn exec meta -- 'uptime; id') and ordinary
barn ssh shell semantics remain available.
| Code | error |
Meaning |
|---|---|---|
| 0 | success | |
| 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 current state forbids it, or another barn command holds it |
| 5 | partial |
some nodes succeeded and some failed |
| 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 by SIGINT/SIGTERM, or a confirmation was declined |
A failure prints 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 thing to do. With --json, stdout carries the same failure:
{"error": "conflict", "reason": "node_not_running", "message": "node meta is not running",
"next": "barn start meta", "operation_id": "…"}reason is a stable identifier for automation and is present only where it
matters. command (name, argv, exit_status, signal, timed_out,
stderr) describes an external program that failed. Lifecycle and setup
failures keep their full result document, as before.
ssh, exec, and a single-node provision pass the guest command's own exit
status through unchanged, so their non-zero codes are the remote program's,
not one of the categories above. Barn's own failures on those paths still use
the table.
Linux deployment state lives under $BARN_HOME (default ~/.barn): the
applied deployment, per-node state and journals, the verified image cache, and
the signed catalog. Applied-state commands therefore work from any directory.
Outside that tree Barn writes only three marked things: the host network
(barn network uninstall), the hosts-file block (barn hosts uninstall),
and ~/.ssh/barn_config with one Include line in ~/.ssh/config
(barn ssh-config --remove). When ~/.ssh/config is a symlink or hard link,
as dotfile managers create, Barn leaves it alone and asks you to add the
Include line once. Mac machines keep their data under $BARN_HOME/mac
and their SSH entries in ~/.ssh/barn-mac_config with a separately marked
Include (barn mac ssh-config --remove); see Mac storage.
The image catalog ships inside each Barn release, and Barn never refreshes
it implicitly. barn update fetches the configured repository's catalog;
barn image sync activates an exact URL or file. Ordinary commands use the
active local catalog. The default repository is https://repo.pigsty.io/barn;
--mirror selects https://repo.pigsty.cc/barn, while an explicit --repo
overrides --mirror, BARN_REPO, and the default.
Optional integration warnings appear in structured lifecycle results as
warnings with code, message, detail, and an optional next command.
Per-node ready: true records a successful guest readiness check in that startup
operation; ordinary status reports runtime state without claiming SSH readiness.
Per-node warnings contain {stage, detail} for limited guest features; these
survive CLI invocations and are refreshed on the next readiness check. These
limitations use a disposable cache separate from core VM state. Per-node
repairs describe automatic actions taken in that operation, such as a changed
SSH port or a reset data disk. Automation that requires every configured guest feature should check
nodes[].warnings as well as the exit code.
Setup and lifecycle retries reuse one operation_id. After a failed first setup,
barn logs --source events --json works even without deployment state. Event
files are bounded; setup traces contain phase/category summaries, while the
command output retains the actual cause. Missing deployment public keys are
derived from the original private key during startup. If that private key is
lost, restore it from backup; an existing VM is never silently given a new key.
make build # build into ./bin
make test # unit tests
make check # the complete source gate CI runsmake check covers module integrity, shell syntax, unit and race tests, vet,
Staticcheck, govulncheck, cross-compilation for all four targets, image and
installer trust boundaries, and the dependency-license inventory. See
CONTRIBUTING.md.
Barn asks for root when installing Linux host packages, setting up the private network, or publishing node names into the system hosts file through a separate helper binary. See SECURITY.md for the privilege boundary and how to report a vulnerability.
Apache-2.0. Release tooling reconstructs dependency license texts from the
exact module versions pinned by go.mod, and ships them inside every archive
and package.