1 Onboarding Nodes
Kyle Mendell edited this page 2026-10-27 15:06:39 -05:00

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:

  1. detects the OS (Linux, macOS, or OpenBSD), architecture, and init system,
  2. downloads the release matching the server's own version and verifies its SHA-256 checksum,
  3. enrolls the node in the foreground (so token or network problems surface immediately in your terminal), and
  4. 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) and sha256(1), then manages the agent with rcctl(8). The configured service name overpass-node is normalized to the rc.d-safe name overpass_node. Check it with doas rcctl check overpass_node; for startup failures, run doas rcctl -d restart overpass_node and inspect /var/log/daemon.

Note

On macOS, the installer stores node identity under /Library/Application Support/Overpass and registers the root LaunchDaemon us.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 on 127.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, op and opctl — 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 what op update and 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=binary

Then run systemctl daemon-reload && systemctl restart overpass-node.

The guided wizard (web UI)

The easiest way to add a machine:

  1. Sign in to the web UI and open Servers → Add a resource.
  2. 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 --insecure for a local self-signed control plane. The installer records the current certificate internally so you do not pass a pin on the command line.
  3. 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.
  4. 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 (tty group, mode 0620), so programs that reopen it by path work — write, wall, tmux, and pinentry, which is what GPG uses to prompt for a passphrase. Without this, git commit -S fails with gpg: 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 passwd entry, started as a login shell, so /etc/profile and your own profile run.
  • The environment sshd exports: USER, LOGNAME, HOME, SHELL, PATH, MAIL, TERM, SSH_TTY, SSH_CLIENT and SSH_CONNECTION. Only LANG and LC_* 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/$UID exists, loginctl lists the session, systemctl --user works, and a gpg-agent or D-Bus started in it is the same one your other logins use. Both systemd-logind and the elogind that Alpine, Void, Gentoo and Artix ship in its place are recognized — the agent asks the system bus who owns org.freedesktop.login1 rather 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.