Skip to content

Latest commit

 

History

History
146 lines (112 loc) · 6.01 KB

File metadata and controls

146 lines (112 loc) · 6.01 KB

The project file

stoat.toml in a repository declares that repository's VMs. git clone the repository, then run stoat up, to build them.

Scope

stoat.toml in the current directory activates project scope. There is no walk-up: a parent directory's stoat.toml has no effect.

The file

# stoat.toml declares this repository's VMs. Commit it, and stoat.lock with it.
# Every field carries its type, its default, and who writes it.
schema = 1                      # int, required. The file format version.
                                 # 2 accepts `provider` on a [vms.x] table;
                                 # 1 rejects it.

[project]
name = "myrepo"                 # string, default: the directory name.
                                 # The prefix for a VM's global name.

# Remote recipes. A value is a ref string, or a table naming a source.
# stoat recipe lock pins each one to a commit in stoat.lock.
[recipes]
tailscale = "v1.2"

[vms.dev]                       # the key "dev" is the name you type
image        = "ubuntu-24.04"   # string, required. A catalog id, or a path
                                 # to your own image, relative to this file.
name         = "shared-dev"     # string, default "<project>-<key>". The VM's
                                 # global name under ~/.stoat.
cpus         = 4                # int, default 4
ram          = 4096             # int, MB, default 4096
disk         = "20G"            # string, default 8G. Disk-mode images only.
recipes      = ["docker", "tailscale"]  # applied in dependency order
shares       = [".", "src"]     # directories from this project, mounted under
                                 # /work. "." mounts at /work, "src" at
                                 # /work/src. Every entry stays inside the
                                 # project.
agent_access = "manage"         # none | observe | manage | exec, default manage
provider     = "qemu"           # execution surface, default qemu (local).
                                 # requires schema = 2.

[vms.dev.params.docker]         # non-secret recipe params
user = "dev"                    # secrets go in .stoat/secrets.toml, 0600

[vms.docs]
image  = "alpine-virt"
shares = ["docs"]

See the sample file on its own.

Names

A VM's global name is its declaration's name field, if set, otherwise <project>-<key>. project.name defaults to the repository directory name, lower-cased, with every character outside the grammar replaced by a dash.

A declaration key, a name override and project.name are lower-case letters, digits and dashes, starting with a letter or a digit. The global name must also clear the rule in cli.md: it becomes a directory, so a Windows device name such as nul is refused there even though the grammar accepts it.

A bare command argument resolves to the declaration key first, then to a global name. stoat ssh dev reaches shared-dev.

Two declarations that resolve to one global name are an error.

Shares

Each shares entry mounts read-write under /work in the guest. . mounts at /work. Every other entry mounts at /work/<basename>.

Every entry must resolve inside the project directory. A relative path that escapes it, directly or through a symlink, is refused.

Shares do not mount on a Debian cloud VM. Debian's cloud kernel has no 9p module, so the mount would fail on every boot. Debian's guest.toml sets the skip_9p flag under [backend.cloudinit], and stoat skips the mount step there. Ubuntu cloud VMs mount shares as usual.

Reconcile

stoat up reconciles a declared VM before it starts it:

  • A missing VM is created from its declaration.
  • An existing VM takes cpus, ram, recipes, params, shares and agent_access from the declaration, through the same path as stoat update. cpus, ram and shares take effect at the VM's next down and up.
  • image, disk and provider are immutable. A declaration that changes any of them is an error naming stoat rm <key> as the fix.

Secrets

Secrets declared by a project live in .stoat/secrets.toml, mode 0600, keyed <key>.<recipe>.<param>. stoat init adds .stoat/ to .gitignore in a git checkout. Every reader renders a secret as <set> or <unset>, never as its value. A VM created outside a project keeps secrets in its own data-directory secrets.toml.

Limits

A [limits] table lowers the account limits in ~/.stoat/config.toml for this project:

[limits]
max_vms = 4
max_ram_mb = 8192

A project can only lower a limit. A value above the account's is ignored, and an unset key leaves the account value alone. The file sits in the repository, where an agent that writes files could otherwise lift its own ceiling.

Commands

Command Effect
stoat init [--name n] writes stoat.toml from the annotated sample, with one VM
stoat status one line per declared VM: global name, state, health, drift
stoat ls --project filters the VM list to the current project
stoat up, down, apply, wait, rm with no VM argument act on every declared VM, in declaration order

Project fan-out currently has a JSON limitation: stoat up --json, down --json, and apply --json can emit human progress lines before the terminal JSON result. Use a named VM command such as stoat up dev --json when a clean JSON stream is required. After down, use stoat wait <key> --until stopped when a caller needs confirmed termination; the stop request can return while QEMU is still exiting.

Errors

Condition Message
duplicate global name stoat.toml: vms.dev and vms.ci both resolve to "myrepo-dev"
share outside project stoat.toml: vms.dev.shares: "../secrets" is outside the project
immutable change dev: image changed (ubuntu-24 → debian-12); run stoat rm dev and stoat up
new at project scope a stoat.toml is present; declare the VM there and run stoat up, or pass --global
unknown key in a bare argument no VM "db" in stoat.toml or ~/.stoat/vms