A node is a machine reachable through the cluster. It runs the overpass node
agent, which enrolls once with a join token and then maintains a reverse tunnel to
the proxy.
One-line install
The fastest way to set up a node is the install script served by your own control plane:
# Linux
curl -fsSL https://gate.acme.com/install.sh | sh -s -- --token <join-token>
# macOS on Apple Silicon
curl -fsSL https://gate.acme.com/install.sh | sh -s -- --token <join-token>
# OpenBSD
ftp -V -o - https://gate.acme.com/install.sh | sh -s -- --token <join-token>
The script re-runs itself under sudo or doas when it is not already root,
then:
- detects the OS (Linux, macOS, or OpenBSD), architecture, and init system,
- downloads the release matching the server's own version and verifies its SHA-256 checksum,
- enrolls the node in the foreground (so token or network problems surface immediately in your terminal), and
- installs and starts a service under whichever init system it found. On hosts without a supported init system (e.g. plain containers) it prints the manual run command instead.
| Init system | Typical hosts | How it comes back after an update |
|---|---|---|
| launchd | macOS | launchctl kickstart -k, with KeepAlive as backup |
| systemd | Debian, Ubuntu, Fedora, RHEL, Arch, openSUSE | systemctl restart --no-block, with Restart=always as backup |
| OpenRC | Alpine, Gentoo, Artix | supervised by supervise-daemon; the agent exits and is respawned |
| runit | Void, Artix, Debian with runit-init |
runsv respawns the agent on exit |
| s6 | s6-based and s6-overlay systems | s6-supervise respawns the agent on exit |
| sysvinit | Devuan, antiX, older Debian | nothing respawns it, so the agent re-executes itself in place |
| rc.d | OpenBSD | nothing respawns it, so the agent re-executes itself in place |
The service definition never contains the join token: enrollment persists the node identity to the data directory first, so unit files stay free of secrets.
Alongside the unit file the installer writes service.env into the data
directory, recording the init system, the service name, the installed binary
path and whether that service is actually supervised. The agent reads it after a
self-update to restart the right service the right way — details that cannot be
inferred from the host alone, because the service name is your choice and OpenRC
only respawns services that use supervise-daemon.
Useful options (pass after sh -s --):
| Option | Purpose |
|---|---|
--token <token> |
join token, required for first enrollment |
--name <name> |
node name (default: the host's short hostname) |
--labels k=v,k=v |
node labels |
--version vX.Y.Z |
install a specific release (required on dev-build servers) |
--no-service |
install and enroll, but skip service setup |
--uninstall |
stop and remove the service and binary (identity is kept) |
The web UI's server list shows each node's reported platform (distribution and init system) next to its agent version, so you can tell at a glance which hosts in a fleet will take which restart path.
Re-running the script upgrades in place: the binary is swapped atomically, the existing identity is reused (no token needed) and the service is restarted.
Note
OpenBSD needs no packages for installation: the script uses base-system
ftp(1)andsha256(1), then manages the agent withrcctl(8). The configured service nameoverpass-nodeis normalized to the rc.d-safe nameoverpass_node. Check it withdoas rcctl check overpass_node; for startup failures, rundoas rcctl -d restart overpass_nodeand inspect/var/log/daemon.
Note
On macOS, the installer stores node identity under
/Library/Application Support/Overpassand registers the root LaunchDaemonus.ofkm.overpass-node. The agent provides SSH and file access. If Screen Sharing or Remote Management is already enabled, it also reports the local VNC service on127.0.0.1:5900, and Overpass adds that desktop without exposing a VNC port to the network.
Warning
Current control planes require the agent to enforce the authoritative labels stored at enrollment. A current agent automatically synchronizes those labels after proving possession of its existing host identity, so older enrollments do not need a fresh token. Agents too old to support authenticated synchronization remain fail-closed and must be upgraded before they can reconnect.
Note
Every binary —
overpass,opandopctl— comes from the release page (configurable under Settings → Node enrollment defaults → Download base URL for mirrors or air-gapped setups). It is what the install script downloads, what the account page links to, and whatop updateand fleet updates fetch. A freshly tagged release must be published — draft releases are not downloadable.
Note
LXC, Incus, and systemd-nspawn nodes run systemd over a persistent rootfs and default to binary deployment mode, so fleet updates can update them in place. If an LXC node running 0.12.0 refuses updates, apply this systemd drop-in to unblock the pending rollout:
# /etc/systemd/system/overpass-node.service.d/override.conf [Service] Environment=OVERPASS_CONTAINER=0 Environment=OVERPASS_RUNTIME=binaryThen run
systemctl daemon-reload && systemctl restart overpass-node.
The guided wizard (web UI)
The easiest way to add a machine:
- Sign in to the web UI and open Servers → Add a resource.
- Pick Unix Server or macOS Device, give it a name, attach labels
(e.g.
env=prod), and choose a token TTL. Toggle Development proxy to add--insecurefor a local self-signed control plane. The installer records the current certificate internally so you do not pass a pin on the command line. - Copy the generated command — the script tab gives you the one-line installer above with the token filled in; the binary tab remains portable, while docker and systemd are Linux-specific manual setups — and run it on the target server.
- The page polls live and flips to online the moment the agent dials in.
The labels you set are recorded on the join token and applied to the node — they are what roles target to grant access. The control plane's record is authoritative: administrators can edit a node's labels later (Servers → … → Edit), and the agent picks the new set up on its next reconnect. Relabeling is limited to labels your own management scope covers, so a scoped manager cannot move a node into someone else's scope.
Manual enrollment
Mint a token and run the agent yourself:
# on the control plane
opctl tokens add
# on the target server
overpass node \
--name web-1 \
--proxy gate.acme.com:3025 \ # the gRPC address
--token <token> \
--labels env=prod,team=web
| Flag | Default | Purpose |
|---|---|---|
--name |
hostname | node name (unique in the cluster) |
--proxy |
— | proxy gRPC address host:port (used to enroll) |
--tunnel |
learned at enrollment | proxy tunnel address |
--token |
— | join token (first run only) |
--labels |
— | k=v labels, comma-separated |
--data-dir |
platform default | node state (/Library/Application Support/Overpass on macOS; /var/lib/overpass-node elsewhere) |
--tls-pin |
— | sha256:<base64> fingerprint of the agent TLS cert (self-signed deployments) |
--insecure |
false |
skip TLS verification of the proxy (dev) |
After the first run the node persists its host key and certificate, so the token is only needed once. Tokens are reusable until they expire; revoke them under Tokens when you're done.
Note
The node connects to the proxy's gRPC port (
3025) to enroll and the tunnel port (3024) to serve traffic — both directly, not through any HTTP reverse proxy. Make sure those are reachable from your servers. With TLS routing enabled on the cluster, both flow through the web port instead (--proxy gate.acme.com:443) and no extra ports are needed.
Browser terminal
For an online node you have access to, the dashboard shows a Connect button.
The control plane mints a short-lived, single-node delegated certificate, dials
the node through the tunnel, and bridges an xterm.js session over a same-origin
WebSocket — no local SSH client required. Using it requires the terminal rule
(both default roles grant it).
What a session looks like
An interactive session is meant to be indistinguishable from logging in on the
machine itself. The agent runs as root and drops to the account named in your
certificate, then reproduces what login(1) and sshd set up:
- The terminal belongs to you. The pty is handed to the session's account
(
ttygroup, mode0620), so programs that reopen it by path work —write,wall,tmux, andpinentry, which is what GPG uses to prompt for a passphrase. Without this,git commit -Sfails withgpg: signing failed: Permission denied. - Your terminal modes are applied. Erase, interrupt, flow control and UTF-8 handling come from your local terminal, not from the kernel's defaults.
- A login shell. The shell from your
passwdentry, started as a login shell, so/etc/profileand your own profile run. - The environment sshd exports:
USER,LOGNAME,HOME,SHELL,PATH,MAIL,TERM,SSH_TTY,SSH_CLIENTandSSH_CONNECTION. OnlyLANGandLC_*are accepted from the client. - A registered session, where the host has a session manager: the agent asks
it to create a session before your shell starts, so
/run/user/$UIDexists,loginctllists the session,systemctl --userworks, and agpg-agentor D-Bus started in it is the same one your other logins use. Bothsystemd-logindand theelogindthat Alpine, Void, Gentoo and Artix ship in its place are recognized — the agent asks the system bus who ownsorg.freedesktop.login1rather than looking for systemd specifically.
Everything above the last point applies on every Unix node. The BSDs and macOS
have no logind-compatible session manager, so sessions there start without
/run/user/$UID and the XDG_SESSION_* variables — which is also how a local
login on those systems behaves.
Overpass does not use PAM. /etc/security/limits.conf, pam_loginuid and
utmp/wtmp therefore do not apply, so who, w and last will not show
overpass sessions — the control plane's Sessions page and the audit log are
the record. SSH agent forwarding is not supported.
Session registration can be turned off per node in node.yaml, in the agent's
data directory (/Library/Application Support/Overpass on macOS,
/var/lib/overpass-node elsewhere):
session:
logind: auto # auto (default) | on | off
auto registers when the host has a session manager and does nothing otherwise;
on always tries and logs the failure, which is what to use if yours is
dbus-activated rather than already running; off never registers, which drops
/run/user/$UID and the XDG_SESSION_* variables from the session.
Removing a node
Delete a node from the Nodes page or with opctl nodes rm <name>. The agent
can re-enroll later with a fresh token.