Command line
goon
One static binary. No runtime, no dependencies, no package manager. Drive every goy from a terminal, and hand the same thing to CI.
$ goon vm ls ID HOSTNAME STATUS OS IPV6 PLAN be9ef25d-9acc-449a-aead-c2142fdb9575 giggle-fiber running ubuntu-24.04 fd00:6969:85d::8 Gooning 2448c363-0596-4bbf-a705-2d8bed878313 clownflare-2 running ubuntu-24.04 fd00:6969:85d::7 Gooning
Install
macOS & Linux
curl -fsSL https://s3.ua-west.goonhost.rocks/releases/cli/v0.1.3/goon_0.1.3_$(uname -s | tr A-Z a-z)_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') -o goon chmod +x goon sudo mv goon /usr/local/bin/
Windows
curl.exe -fsSL https://s3.ua-west.goonhost.rocks/releases/cli/v0.1.3/goon_0.1.3_windows_amd64.exe -o goon.exe
Put it anywhere on your PATH.
Verify what you downloaded against SHA256SUMS.
Sign in
goon uses an API token, never your password. Create one in settings, then paste it when asked — it is read from stdin so it stays out of your shell history.
$ goon auth login Paste your API token: **** Signed in as you@example.com
A read-scoped token is refused on anything that changes state, server side. Use one for anything you do not fully trust. In CI, set GOONHOST_TOKEN and skip the config file entirely.
Describe a lab in a file
goon apply creates whatever is missing and nothing else. Run it twice and the second run does nothing.
# goonhost.yaml
apiVersion: goonhost/v1
kind: Stack
spec:
networks:
- name: lab-net
firewalls:
- name: lab-guard
rules:
- allow: tcp/22
- allow: tcp/179 # BGP
servers:
- name: node
count: 4 # node-1 .. node-4
plan: Edging
sshKey: laptop
network: lab-net
firewall: lab-guard$ goon apply -f goonhost.yaml --dry-run Planned changes: create network lab-net create firewall lab-guard create rule lab-guard allow tcp/22 create rule lab-guard allow tcp/179 create server node-1 create server node-2 create server node-3 create server node-4 Dry run — nothing was changed.
Additive by default: anything the file does not mention is left alone. Deleting takes --prune, which says so plainly and is never implied. Plan and key names are resolved before the first change is made, so a typo cannot leave you with half a stack.
Built to be scripted
Every command takes --json. Anything you can read, you can pipe.
$ goon vm ls --json | jq -r '.[] | select(.status=="running") | .hostname' $ goon fw allow lab tcp/179 --from fd00::/8 $ goon vm rdns <id> node1.example.com
The same binary is an MCP server, so agents that cannot run a shell can still ask about your infrastructure.
{
"mcpServers": {
"goonhost": {
"command": "goon",
"args": ["mcp"],
"env": { "GOONHOST_TOKEN": "gh_..." }
}
}
}Read-only unless you opt in, and it cannot destroy anything in either mode — there is no tool to delete a server, a network or a firewall group. Pair it with a read-scoped token and the API refuses every write regardless of what the agent is asked to do.