bsdt 0.1.0

Repeatable FreeBSD development VMs from a TOML file, booted with QEMU from the official images
# bsdt

Repeatable FreeBSD development VMs from a TOML file, for working on
FreeBSD targets from Linux or macOS. It aims for the same feel as
`docker compose up`: describe the environment in `bsdt.toml`, run
`bsdt up`, and get the same machine every time.

bsdt boots the official FreeBSD `BASIC-CLOUDINIT` VM image with QEMU.
The image is downloaded once, checked against the release's
`CHECKSUM.SHA256`, and cached. Each project gets a copy-on-write overlay
disk on top of it, so throwing a VM away and starting fresh takes seconds.
On first boot bsdt sets up SSH access through FreeBSD's own `nuageinit`,
installs your packages, copies the project in with rsync, and runs your
provision commands.

NetBSD and OpenBSD are planned next, in that order.

## Usage

```sh
bsdt init                  # write a starter bsdt.toml
bsdt up                    # boot (and on first use create + provision) the VM
bsdt exec -- cargo test    # sync the project, then run a command in the guest
bsdt ssh                   # shell in the synced directory (--root for root)
bsdt sync                  # copy the project into the guest
bsdt provision             # reinstall packages and rerun provision commands
bsdt status                # is it running, and on which ports
bsdt logs [-F]             # the guest's serial console
bsdt down                  # shut down, keeping the disk
bsdt destroy               # delete the VM's disk and state

bsdt pull                  # download the base image without booting
bsdt images                # list cached base images
bsdt man [--install]       # print or install the bsdt(1) man page
```

`exec` and `ssh` work in the guest directory matching your current
directory, so running `bsdt exec -- make` from `src/` runs it in the
guest's copy of `src/`.

## bsdt.toml

```toml
[vm]
os = "freebsd"
version = "15.1"
# arch = "auto"         # auto (match the host), amd64 or aarch64
memory = "4G"
ports = ["8080"]        # or "HOST:GUEST"; bound to 127.0.0.1
# update = true        # install security updates on first boot (slower)

[packages]
install = ["rust", "git"]

[sync]
exclude = ["target"]    # not copied, and not deleted in the guest

[provision]
root = ["sysrc nginx_enable=YES"]
run = ["cargo fetch"]   # as the bsdt user, in the synced directory
```

Only `vm.os` and `vm.version` are required. The man page documents every
key. Use `-f other.toml` to give one project several VMs, for example a
second one on an older release.

## Requirements

- QEMU: `qemu-system-x86_64` or `qemu-system-aarch64` (plus UEFI firmware
  for aarch64), and `qemu-img`
- `ssh`, `ssh-keygen`, `rsync` and `curl`

Guests that match the host architecture use KVM on Linux and the
Hypervisor framework on macOS, so an Apple Silicon Mac gets a fast aarch64
FreeBSD guest. Other architectures fall back to emulation, which works but
is slow.

## How it works

- **Images** are cached in `~/.cache/bsdt/images` (Linux) or
  `~/Library/Caches/bsdt/images` (macOS). Set `BSDT_CACHE_DIR` to put them
  somewhere else.
- **Per-VM state** lives in `.bsdt/<file stem>/` beside `bsdt.toml`: the
  overlay disk, an SSH key pair generated for this VM, the console log and
  QEMU's pid file. The directory has its own `.gitignore`.
- **First boot**: bsdt writes a tiny FAT disk labelled `CIDATA` with
  cloud-config user data. `nuageinit` reads it to create a `bsdt` user and
  allow the VM's key to log in as `bsdt` and as root. No ISO tools are
  needed on the host.
- **Access** is SSH on a port forwarded from `127.0.0.1`. Syncing is a
  one-way `rsync --delete` from the host.

## Man page

`cargo install` only installs the binary, so the man page is built into it
instead. `bsdt man --install` writes it to `../share/man/man1/` relative
to the binary, which is `~/.cargo/share/man/man1/bsdt.1`. man-db searches
there automatically for anything in `~/.cargo/bin` on your `PATH`, so
`man bsdt` works with no `MANPATH` changes. `make install` does both
steps. The page's source is [`man/bsdt.1`](man/bsdt.1), written by hand in
mdoc. A test checks that it mentions every subcommand and flag.

[`docs/index.html`](docs/index.html) is the same page rendered to HTML for
the web. Regenerate it with `make docs` (needs
[mandoc](https://mandoc.bsd.lv)) after editing the man page, and commit
the result.

## Development

Run `make` to list the targets: `build`, `install`, `test`, `lint`,
`docs`, and `publish-check`/`publish` for crates.io releases.

## License

BSD 3-Clause; see [LICENSE](LICENSE).