Getting started¶
Install anyhop, add a provider, create channels, and learn the everyday commands. The CLI reference documents every command and flag; this page is the walkthrough.
Install¶
Choose a native user-level install or the scoped Docker deployment. Native TUN can capture the host; Docker never does.
| Choice | Supervisor | Traffic captured | Host-wide VPN |
|---|---|---|---|
| One-command uv script | launchd / systemd --user |
Host apps or host TUN | Yes |
| Homebrew | brew services |
Host apps or host TUN | Yes |
| Manual uv install | launchd / systemd --user |
Host apps or host TUN | Yes |
| Manual pipx install | launchd / systemd --user |
Host apps or host TUN | Yes |
| Docker proxy hub | Docker restart policy | Proxy-aware containers/apps | No |
| Docker gateway | Docker restart policy | anyhop netns + explicitly joined containers | No |
One-command script (macOS + Linux)¶
curl -LsSf \
https://github.com/anyhop/anyhop/releases/latest/download/install.sh | sh
The release-pinned bootstrap supports normal, non-root macOS and systemd Linux
hosts on arm64/aarch64 and x86_64. It verifies and installs a pinned uv when
needed, installs that release's exact anyhop version, registers the
user-level login service, and verifies readiness. It refuses containers, WSL,
non-systemd Linux sessions, and installs already owned by another package
manager; it never invokes sudo or a system package manager. On Linux, opt
into running after logout explicitly by replacing the final sh with
sh -s -- --linger.
The command works from bash, zsh, ash, and other interactive shells because
they only feed the asset to its declared POSIX sh interpreter; the installer
does not depend on the caller's shell syntax.
If the uv tool directory was not already on PATH, the installer updates the
appropriate shell profile and prints both the exact temporary export command
and the absolute anyhop path. Restart the shell before relying on bare anyhop,
or run that printed export to use it immediately in the current shell.
The one-liner trusts HTTPS and GitHub for the first downloaded byte, and its
latest URL moves to each new stable release. To inspect an immutable,
explicitly tagged asset and verify that release's published digest before
executing it:
version=v0.1.17
base="https://github.com/anyhop/anyhop/releases/download/$version"
curl -LsSf -O "$base/install.sh"
curl -LsSf -O "$base/install.sh.sha256"
sha256sum -c install.sh.sha256 # Linux
# or: shasum -a 256 -c install.sh.sha256 # macOS
less install.sh
sh install.sh # add --linger on Linux if desired
Manual install with uv¶
# Install uv first using its official instructions, then install anyhop.
uv tool install anyhop
# If uv reports that its tool directory is not on PATH:
uv tool update-shell
Restart the shell after uv tool update-shell before using bare anyhop. To run
the background daemon at login, optionally register it in that new shell:
anyhop daemon install
Without the service step, the runtime auto-starts on first use and runs for the session.
Install with Homebrew (macOS + Linux)¶
# 1. add the tap and install the headless CLI + Web UI
brew install anyhop/tap/anyhop
# 2. run the background daemon at login, supervised by brew services
brew services start anyhop
The Homebrew channel is deliberately headless — CLI, daemon, control API, and
bundled Web UI. On this channel let brew services
own the daemon rather than anyhop daemon install (they would register competing
launchd/systemd --user units for the same user). anyhop upgrade recognizes a
brew-owned install and delegates to brew upgrade.
Manual install with pipx¶
# 1. install pipx (e.g. `brew install pipx` or your distro's package)
# 2. install the anyhop CLI
pipx install anyhop
# 3. (optional) run the background daemon at login
anyhop daemon install
The pipx service step is optional: without it the runtime auto-starts on first
use (anyhop start or the first channel you add) and runs for the session.
anyhop daemon install registers it as a user-level login service (macOS
LaunchAgent / systemd --user) so it starts at login and is supervised — see
the CLI reference.
Also works: python -m pip install anyhop into an environment you manage,
or one-off runs with uvx --from anyhop anyhop --help.
With Docker (proxy hub):
docker pull ghcr.io/anyhop/anyhop:latest
docker run -d --name anyhop --restart unless-stopped \
--mount type=volume,src=anyhop-state,dst=/var/lib/anyhop \
--mount type=bind,src="$PWD/bundle.yaml",dst=/etc/anyhop/bundle.yaml,readonly \
ghcr.io/anyhop/anyhop:latest
docker exec anyhop anyhop health
docker exec anyhop anyhop status
The bundle mount is optional, but when present use the long syntax so a
missing host file fails instead of becoming a directory. Docker uses
anyhop run plus its restart policy, not anyhop daemon install. Gateway mode
adds an explicit root override, NET_ADMIN, and /dev/net/tun in place of
the native helper/setcap/sudo ladder. It captures only anyhop's network
namespace and containers explicitly joined to it—not the Linux or macOS host.
See docker.md and the Compose walkthrough.
After installation:
anyhop version
anyhop --help
Uninstall according to the channel that owns anyhop:
# macOS only: do this first if you installed the optional root helper
sudo anyhop helper uninstall
# One-command script: remove service, uv-owned anyhop tool, and all anyhop state
curl -LsSf \
https://github.com/anyhop/anyhop/releases/latest/download/install.sh | \
sh -s -- --uninstall
# Manual uv
anyhop stop
anyhop daemon uninstall
uv tool uninstall anyhop
# Manual pipx
anyhop stop
anyhop daemon uninstall
pipx uninstall anyhop
# pip, from the same managed Python environment used to install anyhop
anyhop stop
anyhop daemon uninstall
python -m pip uninstall anyhop
# Homebrew
brew services stop anyhop
anyhop stop
brew uninstall anyhop
The script uninstaller acts only when its bootstrap receipt proves ownership.
It removes only the uv-owned anyhop tool recorded or adopted by the bootstrap,
never uv itself or a pipx, pip, or Homebrew installation. It refuses while the
optional macOS root helper is still installed because a user-level script
cannot safely remove its root LaunchDaemon. It deletes the dedicated state
directory recorded during bootstrap, including provider credentials and
WireGuard keys, and restores Linux login lingering only when the bootstrap
enabled it. It retains uv itself because uv may now be used independently.
If uninstall is interrupted after teardown starts, rerun the same command; the
receipt-backed cleanup resumes without claiming an unrelated installation.
Manual package-manager uninstalls leave ~/.anyhop behind; remove it separately
if you want their state gone. If you set ANYHOP_HOME for the bootstrap, make it
a dedicated anyhop state directory: successful script uninstall removes that
recorded directory in full. The explicit anyhop stop in the manual recipes
also covers a session runtime that was started without a login service.
Upgrades track stable releases by default. A uv, pipx, or pip installation can
explicitly inspect or install a future prerelease with anyhop upgrade --check
--prerelease or anyhop upgrade --prerelease. Homebrew, the one-command
bootstrap's latest asset, Docker latest, and GitHub's stable latest stay
on numeric stable releases. Version ordering follows
PEP 440
(0.1.8 < 0.1.9rc1 < 0.1.9) while remaining fully compatible with
numeric-only versions.
Quick start¶
Add a provider, create a channel, start the runtime, then use the channel's local proxy port.
anyhop providers add nordvpn
anyhop channels add nordvpn --country "United States"
anyhop start
anyhop channels ls
anyhop channels ls prints the local proxy port for each channel:
LABEL ID PORT COUNTRY CITY IPV6 STATUS
--------------- ----------------------- ------ ------------- ------------- ---- -------
wg_us_1 nordvpn/wg_us_1 :53124 United States (Any City) no enabled
Use that port from any tool or app that supports an HTTP or SOCKS proxy:
curl -x http://127.0.0.1:53124 https://api.ipify.org
Check health and traffic (status is the system summary; test is the
per-channel table — fresh IP/latency plus cumulative sent/received):
anyhop status
anyhop test
Provider setup¶
anyhop supports two provider setup styles today:
NordVPN uses an access token:
anyhop providers add nordvpn
anyhop locations nordvpn
anyhop locations nordvpn --country "United States"
anyhop channels add nordvpn --country "United States" --city "Seattle"
To rotate a bad or expired token later, run anyhop providers add nordvpn again
(or use the gear on the provider in the Web UI): it confirms, validates the new
token, and re-resolves the provider's channels — no need to remove and re-add.
The stored token is never displayed back, only a masked preview. This is distinct
from a bundle import (which changes your whole setup from a file); a token update
changes one live credential. See
anyhop providers add.
Proton VPN uses WireGuard config files downloaded from Proton:
anyhop providers add protonvpn
anyhop channels add protonvpn --config ~/Downloads/wg-US-CA-842.conf
Re-importing the same .conf file updates that channel in place, keeping the
same channel id and local port; re-importing a byte-identical file changes
nothing and tells you the channel already exists.
Friendly names¶
Channels are identified by a globally-unique, provider-qualified id
(nordvpn/wg_us_1) — the handle every command takes, shown in the ID
column. You can also give one a display label for readability (the LABEL
column in channels ls and test). The id never changes,
so relabelling is always safe:
anyhop channels add nordvpn --country "United States" --label "Streaming - US"
anyhop channels setlabel wg_us_1 "Streaming - US" # or set it later
anyhop channels setlabel wg_us_1 # omit text to clear
Common commands¶
Useful commands after setup:
anyhop providers ls
anyhop channels ls
anyhop channels ls --refs
anyhop status
anyhop test
anyhop logs
anyhop stop
Most read commands support --json for scripts:
anyhop status --json
anyhop channels ls --json
anyhop test --json
Channel and provider removals accept multiple targets:
anyhop channels rm wg_jp_1 wg_us_seattle_1
anyhop channels rm protonvpn/wg_us_ca_842
anyhop channels rm 'wg_us_*' --dry-run
anyhop providers rm nordvpn protonvpn -y
Hold more channels than your plan's connection cap¶
Some subscriptions limit simultaneous connections (NordVPN and Proton VPN allow ~10). A disabled channel stays in your config but is not materialised at all — no WireGuard handshake or keepalive toward the provider, so it uses no connection slot. Keep a stable of servers on hand and flip which ones are live:
anyhop channels disable wg_jp_1 # free the slot; config + rules stay
anyhop channels enable wg_jp_1 # dial it again
anyhop channels disable 'wg_us_*' # same ref grammar as rm
Disabled channels stay visible everywhere (channels ls grows a STATUS
column; test shows a skipped Disabled row) and can't be targeted by
routing rules while disabled. This is local intent only — it doesn't
deregister the device from your provider account.
Where to next¶
- Rule-based routing — one router entrypoint, first-match rules.
- Web UI — the browser dashboard (
anyhop ui). - Backup and declarative setup — the bundle file.
- CLI reference — every command, flag, and env var.