Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

rusta is a macOS-only CLI for creating and managing Ubuntu VMs on Apple Silicon using Tart.

It is the spiritual successor to ubuntu-tart-vm.sh, exposing its features through a subcommand-based UX rather than a single mega-script. It handles cloning Ubuntu OCI images, provisioning guests over SSH, wiring up Docker, and keeping track of a default VM so day-to-day commands stay short.

Requirements

  • Apple Silicon Mac (arm64). The CLI aborts at startup on any other architecture.
  • macOS with Homebrew available on PATH.
  • Outbound HTTPS to ghcr.io for OCI image pulls and rusta versions.

tart, sshpass, and (only for docker-setup) docker are auto-installed via Homebrew on demand.

Where next?

  • Installation — install via Homebrew, crates.io, or from source.
  • Quick Start — the shortest path from zero to an SSH session in a fresh Ubuntu VM.
  • Commands — full CLI command reference.

Installation

brew install pallewela/tap/rusta

This installs the latest prebuilt aarch64-apple-darwin binary from the GitHub Releases page. Upgrades land via brew upgrade rusta.

Manual download

Grab the tarball for the release you want from GitHub Releases, then:

tar -xzf rusta-vX.Y.Z-aarch64-apple-darwin.tar.gz
install -m 0755 rusta /usr/local/bin/rusta

The tarball also ships shell completions and man pages under share/. Install them alongside the binary:

# man pages
sudo install -d /usr/local/share/man/man1
sudo install -m 0644 share/man/man1/*.1 /usr/local/share/man/man1/

# zsh completions (adjust to your $fpath)
sudo install -m 0644 share/zsh/site-functions/_rusta /usr/local/share/zsh/site-functions/

# bash / fish — install to your shell's standard completion path

Each release page lists the SHA256 of the tarball; verify with shasum -a 256 before installing.

From crates.io

cargo install rusta-cli

The crate is named rusta-cli on crates.io because rusta was already taken; the installed binary is still rusta.

From source

cargo install --path .
# or
cargo install --git https://github.com/pallewela/rusta

Shell completions

If you installed via cargo install or built from source, the prebuilt completion scripts aren’t on disk — generate them yourself at any time:

rusta completions bash > /usr/local/etc/bash_completion.d/rusta
rusta completions zsh  > "${fpath[1]}/_rusta"
rusta completions fish > ~/.config/fish/completions/rusta.fish

Homebrew installs and the Manual download tarball already include these under share/, so no extra step is needed there.

Quick Start

# Create + provision a default Ubuntu 24.04 VM
rusta create

# Boot it (graphics window by default; pass --no-gui for headless)
rusta up

# SSH in
rusta ssh

# Find its IP
rusta ip

# Shut it down gracefully
rusta down

The first time you run an argument-less command with more than one VM present, rusta interactively prompts you to pick a default and persists the choice to ~/.local/share/rusta/state.toml.

At a glance

SubcommandPurpose
rusta up [<vm>]Start a VM (graphics window by default; --no-gui for headless).
rusta down [<vm>]Gracefully shut down a VM (--force to hard-stop).
rusta create [<vm>]Create and provision a new Ubuntu VM.
rusta delete <vm>Delete a VM (requires confirmation or --yes).
rusta listList Tart VMs and indicate the current default.
rusta versionsList available Ubuntu OCI tags from ghcr.io/cirruslabs/ubuntu.
rusta default [<vm>]Print or set the default VM.
rusta ip [<vm>]Print the guest IP of the VM.
rusta ssh [<vm>] [-- cmd...]Open an SSH session or run a command on the VM.
rusta docker-setup [<vm>]Install Docker in the VM and wire host SSH/Docker context.
rusta ssh-copy [<vm>]Copy host ~/.ssh/id_* and *.pem into the VM.
rusta set-gui <vm> <on|off>Set whether rusta up <vm> defaults to graphical (on) or headless (off).

Global flags accepted by every subcommand:

  • --verbose — verbose logging.
  • --log <file> — tee all stdout/stderr to the given file.
  • --help, -h — print help and exit.

For full behavior, flags, and exit codes, see the Commands reference.

State

rusta keeps a small amount of host-side state under ~/.local/share/rusta/:

  • state.toml — the default VM name.
  • run/<vm>.pid — PID of the headless tart run process.
  • provision/<vm>.sh — the generated provisioning script (kept for debugging).

Commands

rusta is invoked as:

rusta <command> [args...] [flags...]

Global flags

Accepted by every subcommand:

FlagDefaultDescription
--verboseoffVerbose logging (equivalent to set -x + LogLevel=INFO on SSH).
--log <file>Tee all stdout/stderr to the given file.
--help, -hPrint help for the current (sub)command and exit 0.

rusta (no args) and rusta --help print top-level help and exit 0.

Exit codes

CodeMeaning
0Success (including --help, versions, no-op when target already in desired state).
1Bad usage, unmet prerequisite, validation failure, or runtime error.
2VM not found (when an explicit name was given or default resolution failed).

rusta up

rusta up [<vm>] [--graphical|-G|--graphics|--gui] [--no-gui|--no-graphics]

Boot a VM.

  • Resolves <vm> from the default VM if omitted.
  • If the VM is already running, prints [skip] and exits 0.
  • Boots with a graphics window by default. VMs explicitly created without --gui boot headless (tart run <vm> --no-graphics); VMs created with --gui or VMs with no recorded preference boot graphical.
  • --graphical (aliases: -G, --graphics, --gui) forces a graphics window for this invocation.
  • --no-gui (alias: --no-graphics) forces headless boot, even for GUI-enabled VMs.
  • --graphical and --no-gui are mutually exclusive.
  • Backgrounds the run and writes the PID to ~/.local/share/rusta/run/<vm>.pid.
  • Waits for the Tart guest agent and prints the guest IP once available (best-effort).

rusta down

rusta down [<vm>] [--force|-f] [--timeout <secs>]

Stop a VM.

  • Resolves <vm> if omitted; prints [skip] if already stopped.
  • Graceful (default): issues sudo shutdown -h now via tart exec and waits up to --timeout seconds (default 60s) for the tart run process to exit. If the timeout expires, exits 1 with a hint to retry with --force.
  • --force: skip the graceful path; call tart stop <vm> (or kill the recorded PID).
  • Removes the stale ~/.local/share/rusta/run/<vm>.pid on success.

rusta create

rusta create [<vm>] [flags]

Clone + provision a new Ubuntu VM.

FlagDefaultDescription
--version <ver>24.04Ubuntu release line (OCI tag), resolved across configured sources.
--image <name>first imageImage family (repo) to clone, e.g. ubuntu-desktop; defaults to the first configured image. Composes with --source.
--gui [pkg]off / ubuntu-desktopInstall a desktop (ubuntu-desktop, xubuntu-desktop, lubuntu-desktop, lightdm).
--cpus <n>6CPU count.
--memory <mb>8192Memory in MB.
--disk <gb>80Disk size in GB.
--user <username>adminGuest login username (image-dependent).
--password <password>adminGuest login password used by sshpass.
--ssh-copy-keysoffAfter provisioning, copy host SSH keys into the guest.
--debug-no-headlessoffRun with a graphics window during provisioning (debug only).
--source <registry>(all sources)Pin resolution to one configured source (registry prefix or label).
--image-ref <ref>(unset)Clone this exact image reference verbatim, bypassing sources and images. Conflicts with --source and --image.

rusta create never assumes a name: if <vm> is omitted and stdin is a TTY it prompts (suggesting <image>-<version>, e.g. ubuntu-2404 or ubuntu-desktop-2404); a non-TTY stdin exits 1. The chosen name is not written to state.default_vm.

If the VM name already exists, creation is skipped and a recreate hint is printed. Otherwise, rusta resolves which image to clone (the selected image across configured sources), clones it, sets CPU/memory/disk, generates a provisioning script under ~/.local/share/rusta/provision/<vm>.sh, boots, runs the script, and shuts the VM down.

rusta delete

rusta delete <vm> [--yes|-y] [--force-running]

Delete a VM from Tart’s storage.

  • Requires an explicit <vm> (no default-VM fallback).
  • Refuses to run if the VM is currently running unless --force-running is given (stop + delete in one shot).
  • Prompts for confirmation unless --yes is given.
  • Clears state.default_vm if it pointed at this VM.

rusta list

rusta list

Print a table of all Tart VMs with their status and a * next to the resolved default. Exits 0 even if there are no VMs.

NAME          STATUS    DEFAULT
ubuntu-2404   running   *
lab-22        stopped

rusta versions

rusta versions [--source <registry>] [--image <name>]

List the available OCI tags across the configured sources × images matrix (see rusta source and rusta image). For each (source, image) cell, rusta fetches a pull token and lists tags from <host>/v2/<namespace>/<image>/tags/list, keeps X.Y tags, then merges and sorts them, highlighting 24.04 as (default).

  • With a single source and a single image the list is unannotated (legacy format).
  • With multiple sources but one image, each tag shows which source(s) provide it; on a conflict, the one create would pick (first by priority) is noted ((create uses <label>)).
  • With multiple images, each tag line groups providers by image: <image>: <src>, <src> (create uses <src>).
  • --source <registry> limits output to one source; --image <name> limits it to one image. They compose.
  • A source whose host is unreachable is skipped with a warning; a source that simply lacks an image is a silent empty cell. versions only fails (exit 1) when no cell produces any result.
$ rusta versions
22.04   ubuntu: cirruslabs, pallewela  (create uses cirruslabs)
24.04 (default)   ubuntu: cirruslabs   ubuntu-desktop: pallewela
25.04   ubuntu-desktop: pallewela

rusta source

rusta source                              # list (default action)
rusta source add <registry>               # e.g. ghcr.io/pallewela
rusta source rm <registry>
rusta source move <registry> <position>   # 1-based priority

Manage the image sources that create and versions consider. A source is a registry host + namespace prefix (e.g. ghcr.io/cirruslabs); rusta appends the selected image (ubuntu by default) to form the repository. Sources are an ordered list — position is priority, and the first source advertising a requested version wins.

  • When no sources are configured, rusta uses a built-in default of ghcr.io/cirruslabs, preserving the original behavior. Adding your first source materializes that default ahead of it, so create still finds the stock images.
  • add validates the prefix (a trailing /ubuntu is stripped; a tag is rejected). Only ghcr.io is supported for now; other registries are rejected (you can still clone any image once-off with rusta create --image-ref <ref>).
  • rm of the last remaining source re-seeds the default — rusta is never sourceless. rm/move of an unknown source exit 2.
$ rusta source add ghcr.io/pallewela
  [ok] Added source: ghcr.io/pallewela
$ rusta source list
==> Image sources (priority order):
  1. ghcr.io/cirruslabs  (cirruslabs)
  2. ghcr.io/pallewela  (pallewela)

rusta image

rusta image                              # list (default action)
rusta image add <name>                   # e.g. ubuntu-desktop
rusta image rm <name>
rusta image move <name> <position>       # 1-based priority

Manage the image names (repositories) that rusta clones under each source. An image is a single repository segment (e.g. ubuntu, ubuntu-desktop); the namespace comes from the source. Images are a global, ordered list — position is priority, and the first image is the create default.

  • When no images are configured, rusta uses built-in defaults of ubuntu and ubuntu-desktop (with ubuntu as the create default). Adding your first image materializes those defaults ahead of it.
  • The list is global (applied to every source). rusta create --image <name> picks one for a run; versions enumerates the full sources × images matrix.
  • add validates the name (a single segment: no /, no : tag, lowercase). A source that doesn’t host an image is just skipped — not an error.
  • rm of the last remaining image re-seeds the defaults — rusta is never imageless. rm/move of an unknown image exit 2.
$ rusta image list
==> Images (priority order; first is the create default):
  1. ubuntu  (default)
  2. ubuntu-desktop
$ rusta image add ubuntu-kairos
  [ok] Added image: ubuntu-kairos

rusta default

rusta default [<vm>]
  • No arg: print the resolved default VM, or no default set and exit 1 (never prompts).
  • With arg: set state.default_vm = <vm>. Exits 2 if <vm> does not exist.

rusta ip

rusta ip [<vm>]

Print tart ip <vm> (waits up to 60s). Exits 1 if no IP is obtained.

rusta ssh

rusta ssh [<vm>] [--auto-up] [-- cmd args...]
  • Resolves <vm> if omitted.
  • If the VM is not running, exits 1 with a hint to rusta up. Pass --auto-up to boot first.
  • Connects via sshpass -p <password> ssh <user>@<ip> with the standard rusta SSH options.
  • Anything after -- is executed as a remote command; otherwise an interactive shell is opened.

rusta docker-setup

rusta docker-setup [<vm>]

Install Docker Engine inside an existing VM and wire host-side docker context + ~/.ssh/config alias.

  • Resolves <vm> if omitted; boots the VM if not running and shuts it back down at the end (only when rusta started it itself).
  • Ensures host has sshpass and docker (auto-installed via Homebrew).
  • Generates ~/.ssh/id_ed25519 (empty passphrase) if missing and ssh-copy-ids the public key into the guest.
  • Inside the guest, installs Docker via curl -fsSL https://get.docker.com | sudo sh only if docker is absent. Adds $USER to the docker group and runs systemctl enable --now docker.
  • On host: idempotently appends a Host docker-<vm> block to ~/.ssh/config pinned to the observed IP and idempotently creates a Docker context docker-<vm> pointing at ssh://<user>@docker-<vm>.

rusta ssh-copy

rusta ssh-copy [<vm>]

Copy host ~/.ssh/id_* and ~/.ssh/*.pem files into the guest’s ~/.ssh/.

  • Resolves <vm> if omitted; boots the VM if not running and shuts it back down at the end (only when rusta started it itself).
  • Exits 1 if the host has no ~/.ssh.
  • If no matching files exist, prints [skip] and exits 0.
  • In the guest: mkdir -p ~/.ssh && chmod 700 ~/.ssh, scps the files, normalizes permissions (*.pub → 644, others → 600).

rusta set-gui

rusta set-gui <vm> <on|off>

Update the per-VM gui preference stored in state.toml for an existing VM, so that rusta up <vm> defaults to the chosen boot mode on future invocations.

  • onrusta up <vm> boots with a graphics window by default.
  • offrusta up <vm> boots headlessly by default.
  • Requires an explicit <vm>; exits 2 if the VM is not known to Tart.
  • Equivalent to the value rusta create writes based on whether --gui was passed. Useful for VMs created before per-VM gui tracking existed, or to flip an existing VM’s default without recreating it.
  • Per-invocation --gui / --no-gui on rusta up still override the stored preference.

Security Policy

Supported Versions

Only the latest release of rusta is supported with security updates. Older versions remain available on the releases page but will not receive patches.

VersionSupported
Latest:white_check_mark:
Older:x:

Reporting a Vulnerability

Do not file a public GitHub issue for security vulnerabilities.

Please use GitHub’s private vulnerability reporting:

  1. Open https://github.com/pallewela/rusta/security/advisories/new
  2. Describe the issue with as much detail as possible:
    • affected version(s) (rusta --version)
    • reproduction steps
    • impact and any proposed mitigation

You should receive an acknowledgement within 7 days. We aim to ship a fix or a status update within 30 days of confirmation.

Scope

In scope:

  • The rusta binary and any code in this repository.
  • The release artifacts published to GitHub Releases and crates.io (rusta-cli).
  • The Homebrew formula at pallewela/homebrew-tap.

Out of scope:

  • Vulnerabilities in Tart itself — report those to the Tart project.
  • Vulnerabilities in the guest Ubuntu images we clone — report those to Canonical.
  • Vulnerabilities in third-party crates we depend on — report those to the crate maintainers and the RustSec Advisory Database.

Disclosure

Once a fix is available we will:

  1. Publish a patch release.
  2. File a GitHub Security Advisory.
  3. Credit the reporter (unless anonymity is requested).

Changelog

All notable changes to this project are documented here. The format is based on Keep a Changelog and this project adheres to Semantic Versioning.

[1.3.1] - 2026-06-27

Dependencies

  • Bump taiki-e/install-action from 2.82.0 to 2.82.5 (#57) (32f7566)

[1.3.0] - 2026-06-22

Documentation

  • Release v1.3.0 [skip release] (9774e58)

Features

  • Configurable image sources and image names [bump:minor] (#56) (edd1d38)

[1.2.7] - 2026-06-20

Dependencies

  • Bump actions/checkout from 6.0.3 to 7.0.0 (#55) (4608d34)
  • Bump softprops/action-gh-release from 3.0.0 to 3.0.1 (#54) (3e88c85)
  • Bump taiki-e/install-action from 2.81.10 to 2.82.0 (#53) (78d142f)

Documentation

  • Release v1.2.7 [skip release] (3a4a719)

[1.2.6] - 2026-06-13

Dependencies

  • Bump taiki-e/install-action from 2.81.6 to 2.81.10 (#51) (d3836eb)
  • Bump codecov/codecov-action from 6.0.1 to 7.0.0 (#52) (adfa96d)

Documentation

  • Release v1.2.6 [skip release] (daaf386)

[1.2.5] - 2026-06-06

Dependencies

  • Bump actions/checkout from 6.0.2 to 6.0.3 (#50) (9db512c)
  • Bump github/codeql-action from 4.36.0 to 4.36.2 (#49) (65da8a8)
  • Bump taiki-e/install-action from 2.79.14 to 2.81.6 (#48) (301e156)

Documentation

  • Release v1.2.5 [skip release] (f4c4ab4)

[1.2.4] - 2026-05-30

Dependencies

  • Bump taiki-e/install-action from 2.79.5 to 2.79.14 (#47) (13a4008)

Documentation

  • Release v1.2.4 [skip release] (af0d7ce)

[1.2.3] - 2026-05-23

Dependencies

  • Bump actions/download-artifact from 4.3.0 to 8.0.1 (#46) (7bfc693)
  • Bump taiki-e/install-action from 2.78.2 to 2.79.5 (#43) (f49c615)
  • Bump github/codeql-action from 3.35.5 to 4.36.0 (#45) (7dae6c6)
  • Bump actions/upload-artifact from 4.6.2 to 7.0.1 (#44) (e4853b7)
  • Bump clap_mangen from 0.2.33 to 0.3.0 (#42) (c16a07e)
  • Bump serde_json in the cargo-minor-and-patch group (#41) (b026145)
  • Bump codecov/codecov-action from 5.5.4 to 6.0.1 (#40) (57799dd)

Documentation

  • Release v1.2.3 [skip release] (eeeaf8c)

[1.2.2] - 2026-05-22

Bug Fixes

  • Drop CHECK_INTERVAL to 1h so same-day releases surface (#39) (b36a058)

Documentation

  • Release v1.2.2 [skip release] (54a8c53)

[1.2.1] - 2026-05-22

Bug Fixes

  • Raise JOIN_TIMEOUT to 1s so real fetches complete (#38) (35ef17c)

Documentation

  • Release v1.2.1 [skip release] (9b49966)

[1.2.0] - 2026-05-22

Documentation

  • Release v1.2.0 [skip release] (3139742)

Features

  • Color the update-availability notice [bump:minor] (#35) (a8efb61)

[1.1.0] - 2026-05-22

Documentation

  • Release v1.1.0 [skip release] (55e7b41)

Features

  • Notify users of new releases [bump:minor] (#33) (20bb1d5)

[1.0.30] - 2026-05-22

Documentation

  • Release v1.0.30 [skip release] (818bbc2)
  • Drop sort=semver from release badge (#31) (9b598dc)

[1.0.29] - 2026-05-22

Documentation

  • Release v1.0.29 [skip release] (47963ad)

Features

  • Add rusta set-gui <vm> <on|off> subcommand (#30) (cdddc74)

[1.0.28] - 2026-05-22

Bug Fixes

  • Default to graphical boot for VMs without recorded preference (#28) (2bb6568)

Documentation

  • Release v1.0.28 [skip release] (1f601e4)

[1.0.27] - 2026-05-17

Documentation

  • Release v1.0.27 [skip release] (72716dd)

Features

  • Add shell completions and man pages (#26) (94f85b2)

[1.0.26] - 2026-05-17

Documentation

  • Release v1.0.26 [skip release] (e94b605)
  • Add crates.io version badge (#25) (98817de)

[1.0.25] - 2026-05-16

Documentation

  • Release v1.0.25 [skip release] (0c4bfdc)
  • Add CodeQL status badge (#24) (99f97fd)

[1.0.24] - 2026-05-16

Documentation

  • Release v1.0.24 [skip release] (f46497c)
  • Document brew update before brew upgrade (#23) (85f0267)

[1.0.23] - 2026-05-16

CI

  • Verify Homebrew tap formula after dispatch (#22) (60e89e9)

Documentation

  • Release v1.0.23 [skip release] (d823060)

[1.0.22] - 2026-05-16

Bug Fixes

  • Target x86_64-unknown-linux-gnu explicitly (#21) (2f2c6a7)

Documentation

  • Release v1.0.22 [skip release] (ffa37ba)

[1.0.21] - 2026-05-16

Documentation

  • Release v1.0.21 [skip release] (b27a7c5)

Features

  • Add cargo-fuzz harness for parse_list_json (#20) (45c5db7)

[1.0.20] - 2026-05-16

Documentation

  • Release v1.0.20 [skip release] (129b815)
  • Document SLSA Pinned-Dependencies exception (#19) (13a9052)

[1.0.19] - 2026-05-16

Bug Fixes

  • Use github.sha for auto-tag checkout (#18) (568a0d3)

Documentation

  • Release v1.0.19 [skip release] (66d3383)

[1.0.18] - 2026-05-16

Documentation

  • Release v1.0.18 [skip release] (3580466)
  • Add Versioning & releases section (#17) (de23808)

[1.0.17] - 2026-05-16

Documentation

  • Release v1.0.17 [skip release] (9fb967a)
  • Add explicit Requirements for acceptable contributions (#16) (647e6ab)

[1.0.16] - 2026-05-16

Documentation

  • Release v1.0.16 [skip release] (7d6cf54)
  • Add CONTRIBUTING and SUPPORT, link from README and mdBook (#15) (cfab863)

[1.0.15] - 2026-05-16

CI

  • Generate provenance before release to satisfy immutability (#14) (19128e2)

Documentation

  • Release v1.0.15 [skip release] (ade0f1c)

[1.0.14] - 2026-05-16

CI

  • Support manual workflow_dispatch for recovery (#13) (454fa72)
  • Use RELEASE_PAT and add CI workflow_dispatch (#12) (f125621)
  • Use RELEASE_PAT for CHANGELOG API write (#11) (a00b2b0)
  • Grant contents: write so auto-merge can be enabled (#10) (8f03115)
  • Enable auto-merge on trusted-author PRs (#9) (d2f95f1)
  • Pass –repo to gh pr review (#8) (cf4234e)
  • Add auto-approve workflow for solo-maintainer PR flow (#6) (e5c1290)

Documentation

  • Release v1.0.14 [skip release] (8ef0d59)
  • Add mdBook documentation site with Pages deploy (#7) (2f2d391)

[1.0.13] - 2026-05-16

CI

  • Generate SLSA Level 3 provenance for releases (#5) (9ffe832)

Documentation

  • Release v1.0.13 [skip release] (83e1cd0)

[1.0.12] - 2026-05-16

CI

  • Harden workflows for OSSF Scorecard (c2c08e4)

Documentation

  • Release v1.0.12 [skip release] (e1e6826)

[1.0.11] - 2026-05-16

CI

  • Pin ossf/scorecard-action to v2.4.3 (48c90d6)
  • Add OSSF Scorecard workflow and badge (5716ebc)

Documentation

  • Release v1.0.11 [skip release] (b2157bb)

[1.0.10] - 2026-05-16

CI

  • Add cargo-deny, CodeQL, and CycloneDX SBOM on release (2ac77a4)

Documentation

  • Release v1.0.10 [skip release] (8a7eb97)

[1.0.9] - 2026-05-16

Documentation

  • Release v1.0.9 [skip release] (6219773)

Features

  • Publish to crates.io as rusta-cli on release (6f1996c)

[1.0.8] - 2026-05-16

CI

  • Collect coverage with cargo-llvm-cov and upload to Codecov (263db91)

Documentation

  • Release v1.0.8 [skip release] (9271799)

[1.0.7] - 2026-05-16

Documentation

  • Release v1.0.7 [skip release] (d94f0f4)
  • Add CI, release, license, platform, and brew tap badges (0000d3a)

[1.0.6] - 2026-05-16

CI

  • Commit CHANGELOG via GitHub API for signed-commits rule (47c6452)
  • Bump git-cliff-action to v4 (a1e6770)

Dependencies

  • Bump actions/checkout from 4 to 6 (#4) (fdf7788)
  • Bump peter-evans/repository-dispatch from 3 to 4 (#1) (9d9cd59)

Documentation

  • Release v1.0.6 [skip release] (5522c92)
  • Add CHANGELOG.md and automate via git-cliff (98ee645)

[1.0.5] - 2026-05-16

Tests

  • Gate fake-tart exec on VM running state (d04eeaf)

[1.0.4] - 2026-05-16

Dependencies

  • Bump toml from 0.8.23 to 1.1.2+spec-1.1.0 (#2) (a048a08)
  • Bump softprops/action-gh-release from 2 to 3 (#3) (6bb8410)

[1.0.3] - 2026-05-16

Dependencies

  • Add Dependabot config for cargo and github-actions (4711f2c)

[1.0.2] - 2026-05-16

Bug Fixes

  • Auto-size NAME and STATUS columns (dfc1520)

[1.0.1] - 2026-05-13

CI

  • Scope bump scan to head commit, auth tag push with RELEASE_PAT (f0f46f9)

[1.0.0] - 2026-05-13

CI

  • Auto-tag green main commits with patch bump by default (0a6d046)

Features

  • Default boot mode follows create-time –gui choice (742280e)

[0.1.1] - 2026-05-13

CI

  • Add tagged-release workflow and brew install docs (beaeb17)
  • Add GitHub Actions workflow for build and test (a6dcd5b)

Chores

Features

  • Require explicit VM name, prompt interactively when missing (1b98fa0)

Contributing to rusta

Thanks for your interest in rusta. This document covers how to set up a development environment, the conventions the project follows, and how to get a change merged.

For bug reports, feature requests, and other support questions, see SUPPORT.md. For security vulnerabilities, see SECURITY.md — please do not file them as public issues.

Quick start

git clone https://github.com/pallewela/rusta.git
cd rusta
cargo build
cargo test

The integration tests under tests/ exercise the CLI surface end-to-end against stubbed tart/brew/ssh binaries, so they run on any platform that supports a Rust toolchain — you do not need an actual Tart install to develop.

How to submit a change

  1. Fork the repository on GitHub.
  2. Create a feature branch: git checkout -b feat/short-description.
  3. Make your changes. Run cargo build, cargo test, and cargo fmt before committing.
  4. Commit using Conventional Commits — the auto-generated CHANGELOG keys off the commit prefix:
    • feat: ... — new feature
    • fix: ... — bug fix
    • docs: ... — documentation only
    • test: ... — test-only change
    • ci: ... — CI/release pipeline change
    • refactor: ... — code change that neither fixes a bug nor adds a feature
    • chore: ... — anything else
  5. Push the branch and open a pull request against main.

What happens after you open a PR

  • CI runs on macOS arm64 (build, integration tests with coverage, cargo-deny advisories/licenses).
  • CodeQL analyzes the Rust source and the workflow YAMLs.
  • For PRs by the maintainer or by Dependabot, the auto-approve workflow approves the PR once CI passes and enables auto-merge. Squash-merge happens as soon as every required gate is green.
  • For PRs by external contributors, the workflow does not auto-approve. The maintainer will review by hand; expect a response within ~7 days. Mention @pallewela in the PR description if you need an earlier look.

After merge, the existing automation handles tagging, release notes, CHANGELOG update, GitHub Release with SBOM + SLSA provenance, crates.io publish, and the Homebrew tap bump.

Versioning & releases

  • Versioning scheme: Semantic Versioning 2.0.0 (semver.org) — MAJOR.MINOR.PATCH.
  • Release tags: every release is identified in version control by an annotated git tag named vMAJOR.MINOR.PATCH (e.g. v1.0.16). The tag set on main is the source of truth for the list of releases the project has ever cut — list them with git tag --list 'v*' --sort=-v:refname.
  • Publication channels for each tag:
    • A GitHub Release with the aarch64-apple-darwin tarball, a CycloneDX SBOM, and a Sigstore-signed SLSA provenance attestation.
    • A crates.io version of rusta-cli.
    • A Homebrew tap formula bump.
  • Tagging is fully automated. Maintainers do not create tags by hand. The Auto-tag workflow runs after CI succeeds on main and:
    1. Inspects the head commit message to pick the SemVer bump level — [bump:major] / BREAKING CHANGE → major; [bump:minor] → minor; default → patch; [skip release] → no tag.
    2. Regenerates CHANGELOG.md via git-cliff.
    3. Commits the updated CHANGELOG and pushes a new annotated tag pointing at that commit.
  • Tag immutability policy. Once pushed, release tags are considered immutable: they are not deleted, moved, or rewritten. The repository enforces this with a protect-tags ruleset on refs/tags/v* (deletion and non_fast_forward blocked). If a release contains a defect, the fix ships as a new patch release with an incremented tag, never as a rewritten old tag.

Requirements for acceptable contributions

This section uses RFC 2119 language. MUST items are hard requirements — CI enforces them and a PR cannot merge without satisfying every one. SHOULD items are expectations during review; deviating from one is fine if you have a reason.

MUST (enforced by CI)

  1. The change builds. cargo build --all-targets --locked succeeds on macos-latest (Apple Silicon).
  2. All tests pass. cargo llvm-cov --locked --lcov succeeds; CI uploads the lcov to Codecov.
  3. No new advisories or banned crates. cargo deny --locked check (advisories, licenses, bans, sources) exits clean. New dependencies must use a license on deny.toml’s allow-list — typically MIT / Apache-2.0 / BSD / ISC / Unicode / CDLA-Permissive. Copyleft licenses (GPL, AGPL, LGPL, MPL beyond what is already allowed) are not accepted.
  4. CodeQL passes. No new high-severity findings in the Rust source or workflow YAML.
  5. Conventional commit prefix. The PR’s commits use Conventional Commitsfeat:, fix:, docs:, test:, ci:, refactor:, chore:. [skip release] is reserved for the auto-tag CHANGELOG commit; do not use it manually.
  6. Verified signed commits. The main branch protection rule requires all incoming commits to be signed. Sign your commits with either a GPG or SSH key configured on your GitHub account, or rebase through the GitHub web UI which signs automatically.
  7. License agreement. Contributions are accepted under the MIT License only. By opening a PR you confirm that your contribution is yours to license under MIT.

SHOULD (review expectations)

  1. Tests covering the change. New features SHOULD ship with at least one integration test under tests/ exercising the new behaviour end-to-end. Bug fixes SHOULD ship with a regression test that fails on main and passes with the fix.
  2. No coverage regression. Codecov reports a delta on each PR. A small regression (a couple of percent on a non-critical path) is acceptable if justified in the PR description; large drops will be pushed back.
  3. cargo fmt applied. cargo fmt --check should produce no diff. CI does not currently fail on this but it is the de-facto project style.
  4. cargo clippy clean. Run cargo clippy --all-targets -- -W clippy::all and address findings in code you touched. Pre-existing warnings are not your problem.
  5. Edition 2021. Stick to the Cargo.toml edition setting. Edition bumps are a separate, coordinated change.
  6. Minimal dependencies. Prefer the standard library or an existing transitive dependency over pulling a new crate. Adding a new direct dependency SHOULD be justified in the PR description.
  7. Documentation in step with code. User-facing behaviour changes SHOULD update the relevant docs:
    • README.md for top-level Quick Start or command summary.
    • docs/src/commands.md (and adjacent pages) for the mdBook site. The CHANGELOG.md is regenerated automatically from conventional commits; do not edit it by hand.
  8. Comments explain why, not what. Default to no comments. Add one only when a non-obvious invariant, constraint, or workaround warrants it. Identifiers should carry the meaning.

Coding conventions

  • Rust edition 2021. The Cargo.toml edition field is the source of truth.
  • Tests. Integration tests live under tests/. Unit tests live next to the code under #[cfg(test)] mod tests {}.
  • Comments. See the SHOULD item above.
  • Dependencies. See the MUST and SHOULD items above.

Project structure

src/                # CLI entry point and command implementations
src/commands/       # One module per subcommand (up, down, list, …)
src/tart.rs         # Wrapper around the `tart` binary
src/state.rs        # Persisted CLI state (default VM, etc.)
tests/              # Integration tests using a stubbed environment
.github/workflows/  # CI, release, security, and Scorecard workflows
docs/               # mdBook documentation source (also published as a site)

License

By contributing, you agree that your contributions will be licensed under the MIT License, the same license as the project.

Getting Support for rusta

This page tells you where to file what. Picking the right channel keeps things moving and avoids cross-talk.

Bug reports

If something does not work as documented, please open a bug report on the issue tracker:

https://github.com/pallewela/rusta/issues/new?labels=bug

Useful information to include:

  • rusta --version
  • macOS version (sw_vers -productVersion) and chip (uname -m).
  • The exact command you ran.
  • The output you got vs. the output you expected.
  • Re-run with --verbose and include the trailing output if relevant.

Enhancement requests

For new features or behaviour changes, open a feature request:

https://github.com/pallewela/rusta/issues/new?labels=enhancement

Explain the use case first (“I want to X because Y”), then the proposed shape of the feature. Concrete examples (“rusta foo --bar should print Z”) are easier to act on than abstract descriptions.

Questions and discussion

For “how do I…?” questions, also use the issue tracker — open an issue with the question label. If GitHub Discussions are enabled on the repo, they are equally welcome there.

Security vulnerabilities

Do not file security issues as public GitHub issues. Use GitHub’s private vulnerability reporting flow as described in SECURITY.md.

Response expectations

rusta is maintained on a best-effort basis by a single person. Realistic expectations:

ItemTarget
Acknowledgement of a bug report7 days
Acknowledgement of a feature request14 days
Security advisory acknowledgement7 days
Patch for a confirmed regression30 days

There are no paid support tiers and no SLAs. Pull requests are the fastest way to get something fixed; see CONTRIBUTING.md.

Development

cargo build
cargo test

The integration tests under tests/ exercise the CLI surface end-to-end against stubbed tart/brew/ssh binaries, so they run without an actual Tart install.

Releasing

Maintainer notes for cutting a new release:

  1. Bump version in Cargo.toml (e.g. 0.2.0), commit as chore: release v0.2.0.
  2. git tag v0.2.0 && git push --tags.
  3. The Release workflow (.github/workflows/release.yml) builds on macos-latest, attaches rusta-v0.2.0-aarch64-apple-darwin.tar.gz to a GitHub Release, and (when the TAP_REPO_TOKEN secret is configured) dispatches a rusta-release event to pallewela/homebrew-tap so the Formula picks up the new version and sha256 automatically.