Skip to content

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

Return to the regular view of this page.

Start

Start Barn with up, connect with ssh, and read the other guides only when needed.

With Barn installed, new users can start a test lab with the Quick Start:

barn up
barn ssh

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:

  1. Daily Operations — status, access, start/stop, changes, scale-in, and destroy.
  2. Troubleshooting — diagnostics and common fixes.
  3. Image Repositories — choose images, use mirrors, import, and prune the cache.
  4. Build from Source — developer builds, checks, and local PATH setup.
  5. Uninstall and Clean Up — remove the deployment, images, networking, and state.
  6. Automation and Guest Scripts — unattended setup, JSON acceptance, repeatable guest commands, and the Pigsty handoff.
  7. Storage and Access — disk retention, file transfer, SSH tunnels, and Linux directory sharing.
  8. macOS Virtual Machines — unreleased barn mac: macOS 27 guests on Apple Silicon with desktop, SSH, shared folders, and clipboard.

1 - Quick Start

Install Barn 0.9.0, start an Ubuntu lab with up, connect with ssh, and scale from the same inventory.

Install

This tutorial targets the Barn 0.9.0 release candidate. For now, build from source. The package and Homebrew commands below apply after 0.9.0 is published and the formula is updated; these links do not establish publication. See Status.

Barn has no compatibility layer for earlier development builds. It uses barn.yml, BARN_*, and fresh ~/.barn state, with no old command aliases or state migration. Stop internal old environments, preserve needed data, and create a fresh Barn installation. Renaming an old state directory is unsupported.

After publication, the user-scoped installer supports macOS and Linux on arm64 and amd64, verifies the archive checksum, and needs no sudo to install:

curl -fLO https://github.com/pgsty/barn/releases/download/v0.9.0/install.sh
chmod +x install.sh
BARN_VERSION=0.9.0 ./install.sh
export PATH="$HOME/.local/bin:$PATH"
barn version

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.

Other installation methods

Once the release assets and Homebrew formula are available, choose one method. The Linux examples use amd64 packages; use the corresponding linux_arm64 asset on ARM64 Linux.

Homebrew
brew install pgsty/infra/barn
barn version
Debian / Ubuntu
barn_release=https://github.com/pgsty/barn/releases/download/v0.9.0
curl -fLO "$barn_release/barn_0.9.0_linux_amd64.deb"
sudo apt install ./barn_0.9.0_linux_amd64.deb
barn version
RHEL / Fedora
barn_release=https://github.com/pgsty/barn/releases/download/v0.9.0
curl -fLO "$barn_release/barn_0.9.0_linux_amd64.rpm"
sudo dnf install ./barn_0.9.0_linux_amd64.rpm
barn version

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:

mkdir -p ~/barn-lab && cd ~/barn-lab
barn up
barn ssh

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.

Note

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:

  ✓  1 node ready
connect:   barn ssh meta

barn ssh selects the control node, meta in this template. You can also name it or run a command directly:

barn ssh meta
barn exec meta -- hostname
barn st

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.

Warning

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:

barn init
barn validate
barn plan
barn up

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:

all:
  vars:
    admin_ip: 10.10.10.10
  children:
    nodes:
      hosts:
        10.10.10.10: { nodename: meta }

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:

barn setup --dry-run
barn setup

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:

barn setup --yes
barn up --json

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 validate -f pigsty.yml
barn plan -f pigsty.yml
barn up -f pigsty.yml

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:

all:
  vars:
    admin_ip: 10.10.10.10
  children:
    nodes:
      hosts:
        10.10.10.10: { nodename: meta }
        10.10.10.11: { nodename: node-1 }
        10.10.10.12: { nodename: node-2 }
        10.10.10.13: { nodename: node-3 }

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.

barn plan
barn up
barn st

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 [email protected]; 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:

barn stop
barn start

When finished, destroy the deployment:

barn destroy

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.

Fresh installation for 0.9.0

Barn 0.9.0 is the first release under the new name. Stop earlier internal labs, preserve needed data, and create a fresh Barn lab. There is no in-place upgrade, old command alias, or state migration. Build from source before publication; the installation commands at the top apply after release. barn update refreshes the image Catalog, not the Barn executable.

2 - Daily Operations

The normal lifecycle for the one deployment: inspect, access, scale, change, stop, and destroy.

This guide describes the Barn 0.9.0 release candidate. See Status for the publication and validation boundary.

Inspect and access

barn status
barn ssh meta
barn exec node-1 -- hostname
barn logs meta --source serial

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

barn stop
barn start
barn restart node-1
barn reload -f barn.yml       # read/check config, stop, then converge

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

barn plan
barn up                         # create/start selected nodes and install SSH aliases
barn recreate node-1            # applies a changed VM definition

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

barn destroy node-3
barn destroy
barn destroy --delete-persistent
barn destroy --purge
barn purge                         # discard everything without confirmation

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

barn network uninstall --yes

See Image Repositories for image selection, mirrors, and cache pruning, or Uninstall and Clean Up to remove host state.

3 - Troubleshooting

Short, safe runbooks for setup, networking, images, drift, interrupted state, and SSH.

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

barn doctor --json
barn network status --json
barn status --json

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:

export PATH="$HOME/.local/bin:$PATH"
command -v barn
barn version

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:

barn network status --json --verbose
barn network uninstall --json

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

id
stat -c '%U:%G %a %n' /usr/lib/qemu/qemu-bridge-helper
dpkg-statoverride --list /usr/lib/qemu/qemu-bridge-helper

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:

barn logs <node>                  # serial console
barn logs <node> --source qemu    # QEMU diagnostics
barn logs --source events        # deployment/setup events, even before the first VM
barn status

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

Prepare an unattended lab, check JSON results, and run repeatable scripts inside selected guests.

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:

mkdir -p ~/barn-lab
cd ~/barn-lab
barn init dual
# Edit barn.yml before proceeding.
barn version
barn validate -f barn.yml
barn plan -f barn.yml
barn setup -f barn.yml --dry-run

Keep the selected inventory in version control. Set vm_image explicitly; use a version such as vm_image: [email protected] 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:

barn setup -f barn.yml --yes
barn up -f barn.yml --json > up.json

--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:

if barn up -f barn.yml --json > up.json 2> up.stderr; then
  jq -e '
    (.nodes | type == "array" and length > 0) and
    all(.nodes[];
      .state == "running" and .ready == true and
      ((.warnings // []) | length == 0) and
      ((.repairs // []) | length == 0)) and
    ((.warnings // []) | length == 0)
  ' up.json
else
  barn_exit=$?
  cat up.stderr >&2
  cat up.json
  exit "$barn_exit"
fi

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:

barn exec meta -- hostname
barn exec node-1 -- sh -c 'id; df -h /data'
barn exec meta --json -- uname -a > uname.json

For multiple guests, save a local Bash script as check-lab.sh:

#!/usr/bin/env bash
set -euo pipefail
hostname
id
findmnt /data
test -d /data

Then run it on selected nodes:

barn provision --script ./check-lab.sh meta node-1
barn provision --script ./check-lab.sh --parallel 2 --timeout 5m --json > provision.json

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 validate -f pigsty.yml
barn plan -f pigsty.yml
barn up -f pigsty.yml
barn ssh

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

Configure test data disks, understand retention, transfer files, and reach guest services through OpenSSH.

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:

all:
  vars:
    admin_ip: 10.10.10.10
    vm_image: [email protected]
  children:
    nodes:
      hosts:
        10.10.10.10:
          nodename: meta
          vm_cpu: 2
          vm_mem: 4096
          vm_disk: 64
          vm_disks:
            - {path: /data, size: 64, fs: auto, persistent: true}
            - {path: /scratch, size: 32, fs: ext4, persistent: false}

Then review and create it:

barn validate -f storage.yml
barn plan -f storage.yml
barn up -f storage.yml
barn exec meta -- findmnt /data
barn exec meta -- findmnt /scratch
barn exec meta -- df -h / /data /scratch

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:

barn exec meta -- sudo -n sh -c \
  'printf "retention check\n" > /data/barn-retention.txt'
barn stop meta
barn start meta
barn exec meta -- cat /data/barn-retention.txt

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:

barn ssh-config > barn-ssh.conf
ssh -F ./barn-ssh.conf barn-meta hostname
scp -F ./barn-ssh.conf ./storage.yml barn-meta:/tmp/storage.yml
scp -F ./barn-ssh.conf barn-meta:/data/barn-retention.txt ./barn-retention.txt

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:

ssh -F ./barn-ssh.conf -N \
  -L 127.0.0.1:15432:127.0.0.1:5432 barn-meta

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:

vm_shares:
  - host: /srv/barn-project
    guest: /workspace
    readonly: true

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

Run macOS 27 virtual machines on Apple Silicon with barn mac — create, connect, share files and the clipboard, and clean up.
Important

Barn 0.9.0 release candidate; unreleased. This page describes the renamed barn mac, which uses fresh Barn state and provides no development-state migration. Earlier native records are retained in Status and do not establish the same acceptance for the renamed build. Check the barn mac --help of 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:

make mac-build
export PATH="$PWD/bin/mac:$PATH"
barn mac doctor

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:

CHECK           RESULT  DETAIL
component       ok      /path/to/barn/bin/mac/Barn Mac.app/Contents/MacOS/barn-mac-runner
virtualization  ok      macOS 27.0.0 on Apple Silicon; virtualization supported
disk            ok      549.8 GiB free
data            ok      no Mac machines yet; barn mac up creates the first

Create your first machine

barn mac up

On a Mac without a prepared macOS, up first shows what it needs and asks:

→ macOS 27.0 (26A428) is not prepared on this Mac yet.
download:  24.8 GiB from Apple (updates.cdn-apple.com)
then:      install macOS once into a reusable base (about 20 minutes)
free:      551.2 GiB
Download macOS from Apple now? [Y/n]

After you confirm, Barn:

  1. Downloads the restore image from Apple only and verifies it against Apple’s published SHA-256. An interrupted download resumes where it stopped.
  2. Installs macOS once into an unbooted base. Installation uses one of the two macOS VM slots while it runs.
  3. Creates mac1 as a copy-on-write clone of the base, boots it, creates your account, and waits until SSH and sudo work.
  ✓  mac1 created and ready · macOS 27.0 (26A428) · [email protected]
shell:     barn mac ssh mac1
desktop:   barn mac open mac1

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:

barn mac up --ipsw ~/Downloads/UniversalMac_27.0_26A428_Restore.ipsw

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

barn mac ssh                                  # interactive shell
barn mac exec -- sw_vers                      # one command
barn mac ssh -- 'id; sudo -n true && echo sudo works'
ProductName:		macOS
ProductVersion:		27.0
BuildVersion:		26A428

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:

barn --json mac exec -- sh -c 'echo out; exit 3'
{
  "command": "exec",
  "node": "mac1",
  "host": "10.10.20.10",
  "arguments": ["sh", "-c", "echo out; exit 3"],
  "success": false,
  "exit_code": 3,
  "stdout": "out\n"
}

The JSON above is shortened; the Mac reference lists every field.

Desktop

barn mac open

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:

barn mac password --copy

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>:

barn mac up dev --share ~/src --share docs=~/Documents:ro
barn mac exec dev -- ls "/Volumes/My Shared Files"

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:

barn mac stop dev
barn mac configure dev --share data=/Volumes/Work/data --unshare docs
barn mac start dev

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:

ssh mac1 'uptime'
rsync -a ./project/ mac1:project/
barn mac ssh-config              # print the entries
barn mac ssh-config --remove     # remove only what Barn added

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:

barn mac up dev --cpu 8 --memory 16G
barn mac ls
NAME  STATE    ADDRESS      SSH    USER   OS          CPU  MEMORY  DISK                 SHARED
dev   running  10.10.21.10  ready  alice  macOS 27.0    8  16 GiB  504.0 MiB / 100 GiB
mac1  running  10.10.20.10  ready  alice  macOS 27.0    4   8 GiB  4.7 GiB / 100 GiB    src
limit:     2 of 2 macOS VMs are running; stop one before starting another

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:

barn mac up build --user ci
error: dev and mac1 are running; macOS allows 2 macOS virtual machines at a time
next: barn mac stop mac1

Everyday lifecycle

barn mac stop dev               # shut down through macOS
barn mac start dev              # boot and wait for SSH
barn mac restart dev            # stop, then start, applying changes
barn mac stop --all             # every machine

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:

error: dev already exists, so --cpu would not apply; its configuration and data were preserved
next: barn mac configure dev --cpu 4

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:

barn mac stop dev
barn mac configure dev --cpu 6 --memory 12G --subnet auto
barn mac start dev

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.

barn mac recreate dev
barn mac destroy dev build
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

barn mac image ls
KIND  OS          BUILD   STATE  ON DISK   CAPACITY  USED BY
base  macOS 27.0  26A428  ready  26.7 GiB  100 GiB   mac1,default

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.

barn mac image prune --installers         # review
barn mac image prune --installers --yes   # delete

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

barn mac destroy --force mac1 dev           # delete machines
barn mac image prune --installers --yes     # delete unused images

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

Choose guest images, use a mirror, import a local qcow2, and prune the cache.

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:

barn image list
barn image info u24
barn image info u24:stable

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:

all:
  vars:
    vm_image: el9
    vm_version: "9.7"

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:

all:
  vars:
    vm_image: [email protected]

Quote numeric vm_version values in YAML so their original text is preserved.

Warning

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:

barn image pull u24 --mirror
barn up --mirror
barn update --repo https://mirror.example/barn
barn image pull u24 --repo https://mirror.example/barn
barn up --repo https://mirror.example/barn

Or set the default repository for the current shell:

export BARN_REPO=https://mirror.example/barn
barn update
barn up

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. Since 0.7.0, 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:

barn/
├── repo.yaml
├── catalog.json
├── catalog.json.minisig       # required for official and HTTP repositories
└── images/
    └── d13-1-arm64.qcow2

repo.yaml is the only human-maintained source. For the single arm64 image shown above, a minimal complete source is:

schema: 1
revision: 1
defaults: { image: d13, channel: stable, arch: native, boot: uefi }
images:
  d13:
    channels: { stable: "1" }
    versions:
      "1":
        status: testing
        variants:
          arm64:
            source_user: debian

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:

barn repo scan /srv/barn
barn repo build /srv/barn
barn repo verify /srv/barn

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:

barn update --repo /srv/barn
barn image info d13 --arch arm64 --repo /srv/barn
barn image pull d13 --arch arm64 --repo /srv/barn

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:

barn image import --name local-mybase --boot uefi \
  --source-user ubuntu --sha256 <digest> /path/to/base.qcow2

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:

barn image prune --dry-run
barn image prune --yes

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

Build Barn for development and review, then run the complete source gate.

Barn 0.9.0 is an unreleased candidate. Use this page to build and check a checkout containing the rename. Installation commands for the eventual release are in the Quick Start.

Choose the source

Use a checkout containing the Barn changes. Once that source is pushed to the public repository, it can also be cloned with:

git clone https://github.com/pgsty/barn.git
cd barn

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:

git log -1 --oneline
git status --short

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
export PATH="$PWD/bin:$PATH"
barn version

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:

mkdir -p ~/barn-lab && cd ~/barn-lab
barn setup
barn 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):

go install honnef.co/go/tools/cmd/[email protected]
go install golang.org/x/tools/cmd/[email protected]
go install github.com/golangci/golangci-lint/v2/cmd/[email protected]
go install golang.org/x/vuln/cmd/[email protected]

Ensure the Go tools installation directory (GOBIN, or $(go env GOPATH)/bin when unset) is in PATH. Before submitting a source change, run:

make check

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

Safely remove the Barn deployment, integrations, images, host network, and default state directory.

This page deletes VMs and local data. Inspect the current state and stop if any Barn VM must remain:

barn st

1. Remove the deployment

Delete nodes, persistent disks, keys, and deployment state:

barn purge

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:

barn ssh-config --remove --name lab
barn hosts uninstall --json
barn hosts uninstall --yes

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

barn image prune --dry-run
barn image prune --yes

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

barn network uninstall --json
barn network uninstall --yes

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:

sudo rm -f -- /opt/barn/libexec/barn-hosts-helper
sudo rmdir /opt/barn/libexec /opt/barn

With the default state directory, and only after every earlier step succeeds, remove the remaining state:

(
  set -eu
  test -z "${BARN_HOME:-}"
  barn_state_root="$(cd "$HOME" && pwd -P)/.barn"
  test ! -L "$barn_state_root"
  if test -d "$barn_state_root"; then
    printf 'removing exact state root: %s\n' "$barn_state_root"
    find "$barn_state_root" -depth -delete
  fi
)

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:

brew uninstall qemu

Verify network removal and the default state directory:

barn network status --json
test ! -e "$HOME/.barn" && echo 'no Barn state'

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.