| BSDT(1) | General Commands Manual | BSDT(1) |
NAME
bsdt — repeatable
FreeBSD development VMs from a TOML file
SYNOPSIS
bsdt |
[-f file]
command [args] |
DESCRIPTION
bsdt boots a FreeBSD virtual machine for
the project in the current directory, described by a
bsdt.toml environment file, much as
‘docker compose up’ starts containers
from a compose file. It is meant for building and testing software on
FreeBSD while working on Linux or macOS.
The guest is booted with qemu-system-x86_64(1) or qemu-system-aarch64(1) from the official FreeBSD BASIC-CLOUDINIT VM image, which is downloaded once, checked against the release's CHECKSUM.SHA256 and cached. Each project gets its own copy-on-write overlay disk on top of the cached image, so a VM can be thrown away and recreated in seconds.
On first boot, a small configuration disk is attached that
FreeBSD's
nuageinit(7)
reads to create a ‘bsdt’ user and to
allow key-based SSH logins as that user and as root.
bsdt then installs the listed packages, copies the
project into the guest with
rsync(1)
and runs the provision commands. Later commands reach the guest over SSH on
a forwarded port bound to 127.0.0.1.
Hardware acceleration is used when the guest architecture matches the host: KVM on Linux and the Hypervisor framework on macOS. Otherwise QEMU emulates the CPU, which works but is much slower.
NetBSD and OpenBSD guests are planned. They are accepted in the
environment file but rejected by up for now.
COMMANDS
init- Write a commented starter bsdt.toml in the current
directory, or to the path given with
-f. up- Boot the VM. On first use this downloads the base image if needed, creates the overlay disk, waits for first-boot setup to finish, installs packages, syncs the project and runs the provision commands. On later runs it boots the existing disk and syncs. Does nothing if the VM is already running.
down- Shut the guest down through ACPI, waiting up to a minute before stopping QEMU forcibly. The disk is kept.
destroy- Stop QEMU immediately and delete the VM's state directory, including its disk. The cached base image is kept.
status- Show the environment file, guest, sync destination, whether the VM is running, and its forwarded ports.
ssh[--root]- Open a login shell in the guest directory matching the current directory.
exec[--root] [--no-sync]--command [arg ...]- Sync the project, then run command in the guest
directory matching the current directory. Each argument is passed through
unchanged, so shell syntax needs an explicit ‘
sh -c’. A terminal is allocated when standard input and output are terminals. The exit status is that of command. sync- Copy the project directory into the guest; see SYNCING.
provision- Install the packages and run the provision commands again on the running VM.
gui- Open a VNC viewer window on the VM's desktop; see
DESKTOP.
updoes this already unlessgui.openis false, so this is for reopening a closed window. keychord ...- Press each chord in turn on the VM's keyboard, as if
typed on it: the keys go through the emulated USB keyboard, so the guest
sees them in /dev/input. A chord is key names
joined by ‘
+’ or ‘-’, pressed in order and released in reverse, such as ‘super+return’ or ‘ctrl-alt-f2’. Names are QEMU's key codes, such as ‘a’, ‘1’, ‘f5’, ‘tab’, ‘slash’ or ‘minus’, plus the aliases ‘super’ (also ‘meta, ‘’), ‘win, ‘’cmd’ctrl’, ‘alt’, ‘shift’, ‘enter’, ‘space’, ‘escape’ and ‘plus’. Needsvm.input, whichvm.guiturns on. type[--enter] text- Type text on the VM's keyboard, one key at a time,
assuming a US layout. With
--enter, press Return afterwards. logs[-F]- Print the guest's serial console log, which shows the boot and is the
first place to look when
uptimes out. pull- Download and cache the base image for the environment file without booting anything.
images- List cached base images and their sizes.
man[--install]- Print this manual page to standard output. With
--install, write it to ../share/man/man1/bsdt.1 relative to the directory holding thebsdtbinary instead, and print the path written.
OPTIONS
-f,--filefile- Use file as the environment file instead of the first bsdt.toml found in the current directory or its parents. Each environment file gets its own VM, so one project can have several.
--root- For
sshandexec, log in as root instead of the ‘bsdt’ user. --no-sync- For
exec, skip syncing the project first. --enter- For
type, press Return after the text. -F,--follow- For
logs, keep printing as the log grows. -h,--help- Print a short usage summary.
-V,--version- Print the version.
ENVIRONMENT FILE
The environment file is TOML. Only vm.os
and vm.version are required.
[vm]
os- The guest operating system. Only
‘
freebsd’ is supported so far. version- The release to boot, such as ‘
15.1’ or ‘14.5’. arch- ‘
auto’ (the default) to match the host, ‘amd64’ or ‘aarch64’. cpus- Number of virtual CPUs. Defaults to 2.
memory- Memory size in QEMU syntax. Defaults to
‘
2G’. disk- Size of the overlay disk; the guest grows its file system to fill it on
first boot. Defaults to ‘
20G’. filesystem- ‘
ufs’ (the default) or ‘zfs’, choosing between the two images FreeBSD publishes. hostname- The guest's hostname. Defaults to the project directory's name.
update- When true, let the image install FreeBSD security updates on first boot
and reboot, as it does by default outside
bsdt. Defaults to false, so every VM starts from exactly the release image and the firstupis much faster. Only read when the disk is created. gui- When true, run a desktop in the guest and open a window on it; see DESKTOP. Defaults to false.
input- When true, attach a USB keyboard and tablet, which the guest sees as evdev
devices in /dev/input, and which
keyandtypepress keys on. Defaults to the value ofgui. ports- TCP ports to forward from 127.0.0.1 on the host, as
‘
HOST:GUEST’ strings, or a single port number when both sides are the same.
[packages]
[sync]
[provision]
root- Shell commands run as root after packages are installed.
run- Shell commands run as the ‘
bsdt’ user in the synced directory, afterroot.
Provision commands run in order on the first
up and on every provision,
and stop at the first failure.
[gui]
Only read when vm.gui is true. Every key
has a default, so the table can be left out.
desktop- ‘
sway’ (the default) or ‘none’. With ‘sway’,bsdtinstalls and runs the desktop described in DESKTOP. With ‘none’, it runs onlystart, which must bring up something serving VNC onvnc. start- A shell command run as the ‘
bsdt’ user in the synced directory once the desktop is up, on every boot. With ‘sway’, it runs inside the desktop, and defaults to ‘foot’, a terminal. An empty string runs nothing. vnc- Port the guest's VNC server listens on. Defaults to 5900.
port- Host port on 127.0.0.1 forwarded to
vnc. Defaults to a free port picked at eachup, whichstatusshows. open- Whether
upopens a viewer window. Defaults to true. resolution- Size of the desktop, as
width
xheight. Defaults to ‘1280x800’. modifier- Sway's modifier key in the configuration
bsdtgenerates: ‘alt’ (the default) or ‘super’. Desktops on the host usually keep Super combinations for themselves while the viewer is in a window, so Alt reaches the VM more reliably. fullscreen- Open the TigerVNC viewer full screen, where it passes system keys such as Super and Alt+Tab to the VM instead of the host. Defaults to false.
DESKTOP
FreeBSD has no driver for QEMU's virtual graphics cards, so a
desktop cannot draw to the VM's own screen. Instead, with
vm.gui set, bsdt runs
sway(1)
on a headless output, drawn in software, and shares it over VNC with
wayvnc(1).
Input still comes from the VM's USB keyboard and tablet through
libinput
and
seatd(1).
The first up installs
sway,
seatd,
wayvnc,
foot and
dejavu,
enables
seatd(1),
and adds the ‘bsdt’ user to the
‘video’ group. Every
up then starts sway, wayvnc and
gui.start, waits for the VNC server to answer, and
opens a viewer on the forwarded port. On macOS that is always the TigerVNC
viewer (‘brew install --cask
tigervnc-viewer’), since the built-in Screen Sharing requires
a password and the desktop has none. On other systems it is the first of
vncviewer(1),
xtigervncviewer(1),
remmina(1)
and
krdc(1)
that is installed, or whatever
xdg-open(1)
opens ‘vnc://’ links with.
Sway reads ~/.config/sway/config in the
guest. When that file does not exist, or still starts with the comment
bsdt writes, bsdt generates
it from sway's stock configuration with gui.modifier
as the modifier, so ‘alt+return’ opens
a terminal by default. Delete the comment to keep your own edits. Its output
and wayvnc's go to /tmp/bsdt-sway.log and
/tmp/bsdt-wayvnc.log in the guest.
Keys typed into the VNC viewer reach sway through wayvnc, not
through /dev/input. Programs that read input devices
directly, such as hotkey daemons, only see keys sent with
key and type.
SYNCING
The directory holding the environment file is mirrored to
sync.dest as the
‘bsdt’ user by
up, sync and
exec. Files deleted on the host are deleted in the
guest, except for paths matching sync.exclude, which
are neither copied nor deleted, so build output in the guest survives a
sync. The .bsdt directory is always excluded.
Syncing is one way, from host to guest. Use
scp(1)
or exec to get results back.
ENVIRONMENT
BSDT_CACHE_DIR- Cache directory to use instead of the default.
XDG_CACHE_HOME- When set, the cache is $XDG_CACHE_HOME/bsdt.
FILES
- bsdt.toml
- The environment file.
- .bsdt/stem/
- Per-VM state beside the environment file, where stem is the environment file's name without .toml: the overlay disk disk.qcow2, the configuration disk seed.img, the SSH key pair id_ed25519, the serial console log console.log, QEMU's pid file qemu.pid and the forwarded ports in state.json. It contains a .gitignore that ignores everything in it.
- ~/.cache/bsdt/images/
- Cached base images on Linux; ~/Library/Caches/bsdt/images/ on macOS. Delete files here to free space.
EXIT STATUS
ssh and exec exit
with the status of the remote shell or command. Otherwise,
The bsdt utility exits 0 on success,
and >0 if an error occurs.
EXAMPLES
Start a project with a FreeBSD 15.1 VM that has Rust installed:
$ bsdt init $ $EDITOR bsdt.toml $ bsdt up
with a bsdt.toml like:
[vm] os = "freebsd" version = "15.1" memory = "4G" ports = ["8080"] [packages] install = ["rust", "git"] [sync] exclude = ["target"] [provision] run = ["cargo fetch"]
Build and test after editing on the host:
$ bsdt exec -- cargo test
Run a pipeline, which needs a shell in the guest:
$ bsdt exec -- sh -c 'uname -a | tee uname.txt'
Keep a second VM for an older release next to the default one:
$ cp bsdt.toml freebsd14.toml $ sed -i.bak 's/15.1/14.5/' freebsd14.toml $ bsdt -f freebsd14.toml up
Open a sway desktop, then drive it from a script:
$ cat bsdt.toml [vm] os = "freebsd" version = "15.1" gui = true $ bsdt up $ bsdt type --enter 'uname -a' $ bsdt key alt+return
Start again from a clean disk:
$ bsdt destroy && bsdt up
Install this page after ‘cargo install
bsdt’, or read it without installing:
$ bsdt man --install $ bsdt man | man -l -
SEE ALSO
AUTHORS
Divan Visagie <me@divanv.com>
CAVEATS
The desktop's VNC server has no password. Like SSH, it is only reachable through a port bound to 127.0.0.1 on the host.
The guest's root account has no password on the serial console, as in the official images, and accepts the project's SSH key. The VM is meant for development, not for anything exposed to a network.