Install¶
Requires Python 3.11+, the WireGuard userspace tools (wireguard-tools/wg),
and iproute2 (ip). The kernel WireGuard module is built into Linux 5.6+ and
autoloads on first use.
Two ways to install on a host that will run the daemon; both let create/join
set up the managed service — a systemd unit, or an OpenRC service on Alpine and
other non-systemd hosts.
With pipx (recommended on Linux) — the standard way to install a Python application in its own isolated environment, straight from PyPI:
sudo apt install pipx wireguard-tools # Debian/Ubuntu; use your distro's pkg mgr
sudo pipx install --global greasewood
--global puts gw on root's PATH so sudo gw … resolves, and the daemon
service launches as <interpreter> -m greasewood, so it stays valid wherever
pipx put the package. pipx manages only the Python side — install the WireGuard
tools separately with your distro's package manager (shown above). Upgrade with
sudo pipx upgrade greasewood.
Prefer uv? sudo UV_TOOL_BIN_DIR=/usr/local/bin uv tool install greasewood
is the faster equivalent (uv tool is uv's app installer). uv has no --global
flag, so point its bin dir at a spot already on root's PATH yourself, as shown.
To run an unreleased commit instead of the latest release, point pipx at the git URL — it builds the default branch directly, no clone needed:
(For a git install, pull newer commits with sudo pipx reinstall greasewood — a
clean re-pull; plain pipx upgrade can skip a git install when the version
string hasn't moved.)
With the bundled installer — a self-contained alternative that also installs
the WireGuard deps and pins a fixed venv at /opt/greasewood. Re-run any time
(after a git pull) to upgrade in place:
Distro packages — .deb and .rpm are attached to each GitHub
release; they bundle their own
Python, so they need nothing but glibc (and pull in wireguard-tools,
iproute2, nftables):
sudo apt install ./greasewood_<ver>_amd64.deb # Debian/Ubuntu
sudo dnf install ./greasewood-<ver>.x86_64.rpm # Fedora/RHEL
On Arch it's on the AUR: yay -S greasewood. See packaging/ for
how these are built.
Either way you get the gw command (and man gw), and gw create/join
install + enable the systemd service. Most subcommands need sudo/root (they
create WireGuard interfaces and edit routing); gw watch does not. For plain
library/dev use, pip install greasewood (or pip install '.[test]' from a
checkout for pytest).
After install the workflow is just setup/join → the daemon runs as a managed
systemd service that gw create/gw join set up for you — see Running as a
service. The Quickstart below
runs it by hand with gw run to show the moving parts.
First-run pitfalls¶
Every one of these comes from a real fleet coming up; each has a one-command fix.
-
sudo: gw: command not foundafter a plainpipx install. A per-user install lands in~/.local/bin, which sudo'ssecure_pathnever searches. And "fixing" it withsudo pipx install(no--global) is the same trap one home directory over: it lands in/root/.local/bin, on nobody's PATH. The--globalflag isn't cosmetic — the daemon is a root service, so its venv must live somewhere root-owned and stable, not inside a login account's home. Uninstall the stray one (pipx uninstall greasewood, withsudoif it went to root's home) and reinstall with--global. -
pipx: error: unrecognized arguments: --global. The distro's pipx predates 1.5 (Ubuntu 24.04 ships 1.4.3). Set by hand exactly what--globalsets:
Upgrades on that host need the same env vars (a plain sudo pipx upgrade
looks in /root/.local and finds nothing). Or skip pipx and use the
bundled install.sh, which pins its own venv at /opt/greasewood.
-
FileNotFoundError: ... 'wg'at join. pipx installs only the Python side; the WireGuard tools come from your distro (sudo apt install wireguard-tools). Don't be fooled by the join getting partway: the kernel module being present (ip link add … type wireguardsucceeds) says nothing about thewgbinary being installed. The distro.deb/.rpmpackages pull it in for you; pipx cannot. -
credential verification failed: no trusted CA signature foundat join — even with a fresh token. If the anchor was re-created (gw create --force) after its daemon started, the daemon keeps signing with the old CA it loaded at startup while every new invite embeds the new on-disk CA: the join's door tunnel comes up (the door key survives a re-create) and then the credential is rightly refused. Restart the anchor daemon so it re-reads the key, then mint a fresh invite: