Getting started
The quickest way onto a fresh vulnbox is the setup wizard. It installs vulnboxd, gives every service a git repository and a private GitHub repository to push to, and writes the configuration for vulnboxd and for the traffic capture tools.
Prerequisites
- An x86_64 Linux box with systemd (other architectures: build it yourself).
- Docker with the compose plugin.
ssh-keygen, for the per-service deploy keys.- Optionally a GitHub token with repo access, if the services should be pushed to GitHub.
Git itself is not required: vulnboxd drives the repositories through libgit2. You will probably want it on the box anyway, for working on services by hand.
Run the wizard
Unpack a release, and run the wizard from it. It installs both binaries on the box itself:
curl -fsSL https://github.com/aarondewes/vulnboxd/releases/latest/download/vulnboxd-x86_64-unknown-linux-gnu.tar.gz | tar -xz
export GITHUB_TOKEN=ghp_... # needs repo access; it is never written to disk
sudo -E ./vulnboxd-x86_64-unknown-linux-gnu/vulnboxctl setup-interactiveThe wizard asks for:
| question | what it is for |
|---|---|
| services directory | one directory per service, each with a compose file |
| game | the game mode: GENERIC, FAUST_2026 or ECSC_2026 |
| team number, gameserver | your team on the gameserver, to read the checker's verdicts; only with a game |
| vulnbox address | this box's address, for the capture tools' config; the game's if left empty |
| tick length | how long a round lasts, such as 60s; only without a game, which knows its own |
| deploy window | the end of each tick vulnboxd restarts services in, such as 5s |
| flag format | the game's flags, which rules write as {flag}; the game's if left empty |
| alert webhook | a Discord webhook for alerts; the setup posts its own progress there too |
| bypass value | the header value that lets your team's requests skip every rule; a random one is made up |
| roll back automatically | whether vulnboxd undoes a change that breaks a service |
| health timeout, port check interval, failed checks before an alert | how patient the checks are |
| GitHub owner, GitHub token | where the repositories go; leave the owner empty to skip GitHub |
| repository prefix | put in front of each service's repository name, service- by default |
| ssh directory | where the per-service deploy keys are kept |
| capture config directory | where tulip.json and shovel.yaml are written |
| services file | optional: a plain list of every service listener — name, address, port — for tools that want to be told where the services are |
| capture traffic | run the PCAP-over-IP broker as its own service |
| capture served on, capture ports, capture filter | where readers connect, and what is captured; only with capture on |
| status world | run the Minecraft status world as its own service; a toy, off by default |
| status world address, status world password | where it listens and the word that lets somebody in to see what the rules are doing; only with the world on |
| release repository, release token | where vulnboxctl upgrade gets releases; the token only for a private repository |
| install and start vulnboxd | install the binaries, the unit and the compose guard, and start it; needs root |
Every question is pre-filled from the existing config, so a second run is mostly confirming the answers. Picking a game fills in its gameserver, and dims the questions that game answers itself.
↑ ↓ or Tab move between questions, typing edits, Space or ← → change a yes/no or a choice, Enter starts and Esc cancels. The mouse works too: click a question to select it, click it again to change a yes/no or a choice, scroll to move, and click Start or Cancel. If an answer does not add up (a directory that does not exist, a window longer than a tick, a game without a team) the wizard says so instead of starting.
Services are then prepared in parallel, each shown with its progress.
Without a game mode, set the tick start
A GENERIC config has tick.start: 1970-01-01T00:00:00Z. Set it to when the game actually began, so vulnboxd's restart windows line up with the real ticks. See Deploys.
Or script it
vulnboxctl init does the same without asking. It reads everything from the config file and the GITHUB_TOKEN environment variable:
sudo -E vulnboxctl init --installWithout --install it prepares the repositories and writes the configs, but leaves installing and starting the daemon to you.
What happens to each service
Every service goes through these steps, in order, and stops at the first that fails:
- git repository:
git initif it is not one already. - branch: rename the branch to
mainif it is something else. - large files: anything at or over
init.max_file_size(500 MiB) is added to.gitignorerather than failing the push later. - initial commit: commit the current state as
vulnboxd.
With a GitHub owner and token, also:
- deploy key: an ed25519 key for this service alone, in the ssh directory.
- GitHub repository: a private
service-<name>repository, in the organisation or, failing that, the user account. The prefix isinit.repo_prefix, and a name that belongs to something else gets a number:service-web-2,service-web-3, and so on. - key on GitHub: the deploy key is added to that repository with write access.
- ssh access: the key is recorded in the repository, so
git pushby hand uses it too, along with GitHub's host keys (see below). - remote:
originis pointed atgit@github.com:<owner>/service-<name>.git. A remote that was there before is kept asorigin-before-vulnboxd. - push:
mainis pushed and set to track the remote.
Safe to run again
Every step looks before it acts, so a second run only does what is still undone. If one service fails, the others carry on, and the run ends with a list of what went wrong and a non-zero exit status. Fix the cause and run it again: only the failed service is touched.
Somewhere other than GitHub
Everything above is GitHub-shaped because that is what init can create repositories and deploy keys on. Any other remote works, you just do that part yourself: leave init.github_owner empty, and the setup makes each service a git repository locally without pushing it anywhere. Add your own origin afterwards and vulnboxd polls it like any other.
For authentication it uses the deploy key for that service if there is one, and otherwise whatever ssh would use, including an agent. A service that already has an origin keeps it.
GitHub's host keys
A fresh vulnbox knows no ssh hosts, so the first connection to GitHub would normally be a question nobody is there to answer: the daemon refuses it outright (invalid or unknown remote ssh hostkey) and the git cli would wait for a yes.
vulnboxd carries GitHub's three host keys, so it recognises GitHub without being told. Nothing else is taken on trust: on a game network, where every other team is a neighbour, accepting whatever key is offered the first time is exactly what you do not want.
Setup also writes them to <ssh_dir>/known_hosts and points each service's core.sshCommand at it with StrictHostKeyChecking=yes, so pushing by hand verifies GitHub the same way and still needs no yes.
GitHub rotates these keys rarely and announces it when it does. If it has, pushes stop with an unknown host key until vulnboxd is upgraded; adding the new key to ~/.ssh/known_hosts also works, since any host vulnboxd does not carry keys for is looked up there as usual.
After setup
Once vulnboxd is running it takes over the services at the end of the next tick. That moves every public port behind the proxy and recreates the containers, which costs a few seconds of downtime once. After that:
- Each service has a
vulnboxd-rules.yml, committed and pushed. Edit it through git or on the box; see Rules. - Use
vulnboxctl compose <service> ...instead ofdocker compose; see Day-to-day operation. - Watch it with
vulnboxctl top.
Checking it worked
systemctl status vulnboxd
vulnboxctl topIn the first window you should see one deploy per service and then nothing: ports answering, rules loaded, no rollbacks. journalctl -u vulnboxd | grep -i warn is the quickest way to find anything it did not like — a rules file that would not load, a port the compose file does not publish.