A got server on an OpenBSD VM, from one first-boot script
A first-boot script turns a chroot.dev VM into a got server: gotd(8) takes pushes over ssh and gotwebd(8) shows them over HTTPS behind httpd(8). The setup takes seconds.
One script handed to ssh chroot.dev new -s gives you an OpenBSD -current VM running gotd(8), which takes pushes over ssh, and gotwebd(8) behind httpd(8), which shows them over HTTPS. In one run the VM answered ssh in 337 ms, and the script was done seconds later, packages installed and the three daemons up.
The recording is real time. The VM was created with the script below before it started. The top pane is my laptop and the bottom one is the VM: it shows the configs, the laptop pushes a commit, and the VM checks that it arrived. Then the browser follows that commit in gotwebd.
WHY A THROWAWAY VM
gotd is small, but setting it up touches a lot of the system: a user whose login shell is gotsh(1), a repository owned by _gotd, gotd.conf(5), gotwebd.conf(5), and an httpd.conf(5) that hands requests to gotwebd over FastCGI.
I didn't want to learn that on my laptop or on a server I care about. Here I create the VM from a script, get something wrong, remove it and run the script again. Every attempt starts from a clean OpenBSD -current, so the script is the whole setup and nothing lingers from earlier tries.
THE FIRST-BOOT SCRIPT, PIECE BY PIECE
new(1) -s runs a script once, as the VM's user puffy (who has doas(1)), on the first boot, and -w waits for it. This is the one from the video, gotd-setup.sh:
$ ssh chroot.dev new -n gotd -w -s /dev/stdin < gotd-setup.sh gotd 10.201.0.22 2607:5300:203:665d:432b:2fa:de96:fc5 ssh in 337 ms setup ok in 17362 ms
The packages come first. On -current, while a new package snapshot reaches the mirrors, pkg_add(1) can read a listing that names a file already gone. In this run got, gotd and gotwebd installed from the CDN in 5 s and only quirks failed, so the script tries another mirror:
doas pkg_add -I got gotd gotwebd || doas env PKG_PATH=https://ftp.openbsd.org/pub/OpenBSD/snapshots/packages/amd64/ \ pkg_add -I got gotd gotwebd
Pushes arrive as an ssh login, so the git user gets gotsh as its shell and the account's keys. gotsh only runs git-receive-pack and git-upload-pack against repositories that gotd.conf allows:
doas useradd -m -s /usr/local/bin/gotsh git doas install -o git -g git -m 600 ~/.ssh/authorized_keys /home/git/.ssh/authorized_keys
The repository is created and the first commit imported as _gotd, before gotd starts:
doas -u _gotd gotadmin init -b main /var/git/hello.git doas -u _gotd env GOT_AUTHOR="puffy <puffy@chroot.dev>" \ got import -r /var/git/hello.git -b main -m "initial import" $src
gotd.conf gives git write access. gotwebd 0.130 reads repositories outside its /var/www chroot, so the same /var/git serves both daemons:
repository "hello" { path "/var/git/hello.git" permit rw git }
server "localhost" { site_name "gotd on chroot.dev" site_owner "puffy" repos_path "/var/git" show_repo_owner off }
httpd serves gotwebd's CSS and images from /htdocs/gotwebd and sends everything else to gotwebd's socket. My first try had empty blocks like location "/*.css" { }, which httpd rejects as a syntax error, so the static files are matched with one pattern instead:
server "*" { listen on * port 80 location match "^/[^/]*%.[a-z]+$" { root "/htdocs/gotwebd" } location "/*" { fastcgi socket "/run/gotweb.sock" } }
The run above used the default 128 MiB; the VM has swap, and pkg_add fits.
PUSHING THROUGH THE LOBBY
There is one shared IPv4 and ssh has no SNI, so the lobby picks the VM from the user part: vm+gotd@chroot.run. That always logs you in as puffy. To reach the git user, jump through the VM and connect to its own sshd. In ~/.ssh/config:
Host gotd HostName 127.0.0.1 User git ProxyJump vm+gotd@chroot.run HostKeyAlias gotd.chroot.run
HostKeyAlias stores the VM's host key under its own name, not under 127.0.0.1, where it would clash with every other VM you jump to the same way. The path a push takes:
laptop --ssh vm+gotd@chroot.run--> lobby --> sshd on the VM, as puffy | forwards to 127.0.0.1:22 v sshd, as git --> gotsh --> gotd
After that, gotd:hello behaves like any other remote:
$ git clone -q gotd:hello && cd hello $ sed -i 's/"hello"/"hi g33ks =]"/' hello.c $ git commit -qam 'hello: greet the g33ks' $ git push -q
Even with -q, git clone still prints one remote: line with gotd's progress. On the VM, the commit is in the repository gotd writes to:
gotd$ got log -r /var/git/hello.git -l 1 -s 2026-10-21 main hello: greet the g33ks
GOTWEBD OVER HTTPS
Every VM gets https://NAME.chroot.run with a valid certificate. It is private by default: until you run ssh chroot.dev browser and open the one-time link, a visit redirects to the login page. For the video I made it public:
$ ssh chroot.dev share set-public gotd https://gotd.chroot.run/
The browser half of the video starts there: the repository list, the summary with the new commit on top, and the diff. share(1) also gives access to one person at a time, or back to private with set-private.
WHAT IS ROUGH
- Snapshot syncs. On -current the packages are whatever the snapshots have, and during a sync the first mirror can fail. The fallback in the script covers that, and took most of this run's time: the got packages were in place 5 s after boot.
- No backups. During the beta there are none, see beta(7). Don't keep the only copy of a repository here; push to it as a second remote.
RUNNING IT YOURSELF
chroot.dev is a beta by invitation: invite@chroot.dev. An account starts on a 14-day trial with 1 VM and 512 MiB, more than this setup needs. Then:
$ ssh chroot.dev new -n gotd -w -s /dev/stdin < gotd-setup.sh
Add the Host gotd block above and clone gotd:hello. If your push doesn't reach gotd, I want to know where it stopped: contact@chroot.dev.
AUTHORS
Murilo Ijanc