Command & configuration reference¶
Command reference¶
Bare gw is the dashboard, not a usage error: sudo gw in a terminal
opens the live watch view; without root (or piped) it prints the static
snapshot with an everyday-commands index below it; on an unconfigured or
multi-mesh host it tells you how to start (or which -c). gw --help keeps
the full reference.
| Command | sudo? | What it does |
|---|---|---|
create |
yes | One-shot anchor bootstrap: CA, door key, routing, self-cred. |
run |
yes | Start the daemon (WireGuard iface, control plane, loops). Port enforcement (grant port scopes, nftables) is on by default; --no-enforce-ports (or enforce_ports=false) disables it for an nft-less host. See Access control. |
invite |
yes | Open a 15-min door window, print a single-use join token. --standing opens a standing door instead: one token, any number of enrollments, until close-door. |
close-door |
yes | Close the current door window — permanently invalidates its token (standing or single-use); enrolled nodes unaffected. |
join <token> |
yes | Enroll this machine using a token from invite. |
watch |
sudo | Live mesh dashboard (redraws in place, so it needs sudo for live WireGuard state): the split roster + link state, per-second throughput, and a latency column that fills in as pings return. Ctrl-C to exit. --snapshot prints one static view and exits (no root; auto-used when piped); --json emits that snapshot as a stable, versioned schema for monitors/jq (add sudo for live WireGuard stats) — and the human roster is rendered from that very JSON, so the machine contract can't silently drift from what you see. --by-role groups by role and, per group, reports connectivity — connected components (the emergent segments), and policy-expected-but-down edges with a firewall/NAT hint — computed fleet-wide from each node's self-reported live links. Shows how fresh the view is (time since last sync) up top. |
config [key] |
no | Print resolved config facts machine-readably for scripting — gw config interface gives the mesh interface name (gw-<mesh>), no arg lists all as key<TAB>value. |
firewall |
no | Print the recommended firewall ruleset (a suggestion — greasewood never changes your firewall; nothing is applied). The same posture on every node; with sudo also flags anything that looks blocked. |
diagnose [A [B]] |
sudo | Pairwise link diagnosis: compare up to two nodes + the anchor side by side and explain whether a tunnel can form (policy/roles, reachability, firewall directionality with OPEN-inferred-from-handshake and upstream-router localization). No args = this host ↔ anchor. |
revoke <node> |
no | Revoke a node on the anchor (denies renew/publish, evicts it, frees its hostname). <node> = hostname, <host>.<mesh_domain> mesh name, or 64-char id_pub hex. |
set-roles <node> <r> |
no | Change a node's roles (on the anchor; effective next renewal). |
policy |
show: no · apply: sudo | The mesh's grant table (roles → roles : ports; derives which tunnels exist). show renders the active policy on any node; apply (anchor) validates grants.toml, previews the tunnel delta, signs with the CA key, publishes. See Roles & the grant table. |
set-caps <node> <caps> |
no | Change a node's full tag set (on the anchor; effective next renewal). |
anchor-promote |
yes | Turn this enrolled node into an anchor (generate its own CA key). |
cert-request |
no | Get an x509 TLS cert from the anchor for a local service. The daemon auto-renews it at ~half its TTL; --reload-cmd runs a command after each renewal, --no-auto-renew opts out. --profile <name\|path> issues + places the key/cert/ca where the service wants them (right owner/mode) and re-places on every renewal; --profile <name> --show prints a bundled template to adapt. |
cert-profiles |
no | List the bundled cert profile templates (postgres, nginx, haproxy, redis, nats, minio, mosquitto) — starting points to copy and adapt. |
cert-remove <name> |
sudo | Stop managing a cert (drop it from auto-renewal + remove its profile snapshot). Leaves the placed files by default; --delete-files removes them too. |
cert-status |
no | Show every daemon-managed TLS cert (expiry, renewal state, SANs, placed files, profile) from the manifest — wherever the files live. |
narrate |
no | Translate the ip/wg command trail (audit.log) into a plain-English story of what greasewood did and why. Filters: --since, --peer, --grep, --failures, --stats, --raw. |
rename-node <name> |
yes | Change this node's mesh hostname (anchor-validated, no re-join; refused if the anchor pinned the name). |
rename-mesh <name> |
yes | Rename this mesh — domain, config, data dir, interface, and service move together. Run on the anchor, then on each member (surfaced in its gw watch). Old names resolve + verify in TLS through a one-TTL grace window. See the RUNBOOK SOP. |
renew |
yes | Force an immediate credential renewal for this node (applies an anchor-side set-caps/set-roles now, instead of at the ~half-TTL renewal). |
renew-all |
no | On the anchor: request a fleet-wide renewal (advertise renew_after=now; cooperating nodes renew, jittered so the anchor's rate stays ~constant with mesh size). |
anchor-backup |
no | On the anchor: write one passphrase-encrypted archive of the CA key, node registry, revoke list, door key, and anchor identity. Store it offline. |
anchor-restore |
yes | Restore a anchor-backup archive onto a replacement host (same CA key → a restore, not a re-root). |
anchor-transfer <host> |
yes | On the anchor: hand the anchor role to another host over SSH (same CA → no re-root; the CA never touches the mesh). Streams the encrypted state, copies the config, then stops here / starts there / verifies — rolling back if the target doesn't come up. See the RUNBOOK SOP. |
purge |
yes | Remove all greasewood state from this machine. |
Global flags: -c/--config FILE (default: discovered — with one mesh on the host every command finds its config unaided; with several, gw lists them and asks for -c) and
-v/--verbose. Both must precede the subcommand (gw -v run, not gw run -v).
Enrollment is door-only: invite on the anchor, join on the node. There is no
manual credential-copy path.
Configuration¶
gw create and gw join write /etc/greasewood_<name>.toml for you; see
greasewood.toml.example for the full annotated schema. Key fields:
[node]
hostname = "node01"
role = "node" # "anchor" | "node"
caps = ["role:mesh"] # role:<x> tags are the grant-table vocabulary; "tls" allows certs
[network]
interface = "gw-myfleet"
listen_port = 51900
overlay_prefix = "fd8d:e5c1:db1a:7::" # the fleet's overlay /64 (ULA)
seeds = ["http://[<anchor-overlay>]:51902"] # directory URLs to pull (the anchor)
root_url = "http://[<anchor-overlay>]:51902" # where to publish / renew
hosts_sync = true # manage /etc/hosts names (on by default)
mesh_domain = "myfleet.internal" # the mesh's ONE domain (from create <name>)
[ca]
trusted_pubs = ["<hex Ed25519 CA pubkey>"] # a set, to allow CA migration
[anchor] # anchor role only
ca_key_file = "/var/lib/greasewood/ca.key"
control_listen = ":51902"
credential_ttl = "24h"
Roles¶
- anchor — holds the CA private key; serves the control plane and the enrollment door; participates in the mesh.
- node — a plain mesh participant.
One host on two meshes¶
The overlay /64 is configurable ([network] overlay_prefix, set at
create --overlay-prefix; a node learns it from its credential at join). A
node learns and verifies addresses prefix-agnostically — the self-certifying
part is the host bits, blake2s(id_pub), and the CA signature attests the
prefix — so one host can be a plain node on two independent meshes at once
(anchor-in-two-meshes is not supported). Joining a second mesh is just:
join routes by the token's CA: a token for a mesh you're already on
refreshes that membership; an unknown CA auto-provisions the next membership
slot — config /etc/greasewood2.toml, data /var/lib/greasewood2, interface
gw-<name>, UDP 51910 (then +10 each). The mesh's name domain rides
in the token (declared once at gw create <name> → <name>.internal), so
every member of a mesh, including multi-mesh hosts, mounts it under the SAME
suffix, and TLS names agree fleet-wide with no flags.
Domain collisions: a node cannot bridge two meshes with the same domain — no local aliasing exists (a per-host alias would diverge from the names in the mesh's TLS certs, a debugging trap; and rewriting is off the table since names are CA-attested). The join refuses before the door dance (the token is not consumed) and tells you the fix: rename one mesh on its anchor. Requiring a mesh name at create (becomes subdomain) should makes this a rare coincidence.
Every derived value is still overridable — pass any of the explicit knobs and the auto-slotting steps aside entirely:
sudo gw -c /etc/gw-b.toml join "$TOKEN_B" --data-dir /var/lib/gw-b \
--interface gw-b --listen-port 51920 --mesh-domain beta
The mesh domain must differ between the two, for the same reason the interface
name must — both are flat, host-global namespaces with no scoping. The
/etc/hosts block is keyed by mesh_domain, so two meshes sharing one would
(a) clobber each other's block every reconcile, each daemon strips and rewrites
the same-tagged block, and (b) collide on the names themselves: both meshes'
db.mymesh.internal would claim the same name for two different addresses.