bsdt 0.2.2

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.

Website, install guide and manual: <https://bsdt.divanv.com>

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 gui                   # reopen the desktop window (gui = true)
bsdt key alt+return        # press keys on the VM's keyboard
bsdt type --enter 'ls'     # type text on it
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.

## Desktop

```toml
[vm]
os = "freebsd"
version = "15.1"
gui = true
```

is enough for a sway desktop in a window. The first `bsdt up` installs
sway, seatd, wayvnc and the foot terminal, and every `up` starts them and
opens a VNC viewer on the host. Every setting under `[gui]` has a default;
see the man page to use your own desktop, port, resolution, modifier key
or full screen. Sway uses Alt as its modifier by default (`alt+return` for
a terminal), because the host desktop usually keeps Super for itself.

FreeBSD has no driver for QEMU's virtual graphics cards, so sway draws to
a headless output in software and wayvnc shares it. Input comes from the
VM's emulated USB keyboard and tablet through `/dev/input`, so
`bsdt key` and `bsdt type` can drive the desktop from scripts, and tools
that read input devices directly see those keys. `input = true` gives you
the keyboard and tablet without a desktop.

On Linux, install a VNC viewer such as TigerVNC (`apt install
tigervnc-viewer`). On macOS, bsdt opens TigerVNC Viewer
(`brew install --cask tigervnc-viewer`). The built-in Screen Sharing can't
connect, because it requires a password and the desktop has none.

## Requirements

- QEMU: `qemu-system-x86_64` or `qemu-system-aarch64` (plus UEFI firmware
  for aarch64), and `qemu-img`
- `ssh`, `ssh-keygen`, `rsync` and `curl`
- For `gui = true`, a VNC viewer

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.

The project website lives in [`docs/`](docs/) and is served by GitHub
Pages. `make docs` rebuilds it (needs [mandoc](https://mandoc.bsd.lv)):
the home page comes from [`templates/index.html`](templates/index.html),
and the manual page and license are rendered into the same layout. Run it
after editing the man page or templates, and commit the result.

## Development

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

To try a change against a real VM, `make try` builds a debug binary and
boots [`examples/hello-c`](examples/hello-c), a C program that needs
nothing installed. `EXAMPLE=hello-rust` boots
[`examples/hello-rust`](examples/hello-rust) instead, and `EXAMPLE=gui`
boots [`examples/gui`](examples/gui), a sway desktop. `make try-down` and
`make try-destroy` stop and delete the VM. [TESTING.md](TESTING.md) is the
checklist to run by hand before merging `develop` into `master`.

## License

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