This is the multi-page printable view of this section. .
Start
- 1: Quick Start
- 2: Daily Operations
- 3: Troubleshooting
- 4: Automation and Guest Scripts
- 5: Storage, Files, and Service Access
- 6: macOS Virtual Machines
- 7: Image Repositories
- 8: Build from Source
- 9: Uninstall and Clean Up
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 - 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.
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.
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.
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.
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.
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.
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.
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.
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.