This is the multi-page printable view of this section. .
Reference
- 1: Configuration
- 2: CLI
- 3: Mac Commands
- 4: Images
- 5: Image Pipeline
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.
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 - 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.
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.
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.
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.