Install the Agent

The agent is what a device runs. It opens one outbound connection to the server and keeps it, which is what makes the machine reachable without anything listening on it.

$ curl -sSf https://cloud.shellhub.io/install.sh | sh

Nothing else is needed. Given no credential the agent pairs: it prints a code, you enter it in the console, and the device joins the namespace you pick. Quickstart walks that path end to end.

The script is served by the server it will register with, which writes its own address into it on the way out — port included. Fetch it from your own server and you get one that registers there.

What the script does

  1. Picks a method by looking at the machine: Docker, then Podman, then Snap, then WSL, then a native binary. The first one that works is used.
  2. Detects the architecture — amd64, armv6, armv7, arm64, 386.
  3. Installs the agent and registers it to start at boot.

Force a method with INSTALL_METHOD when the choice is wrong for you:

$ curl -sSf https://cloud.shellhub.io/install.sh | INSTALL_METHOD=standalone sh

The methods

Docker

Picked when a rootful daemon is usable. Leaves a container running the agent, plus a shellhub-agent wrapper on the host.

The wrapper is there because shellhub-agent login opens a browser during pairing, which an agent inside a container cannot do. The container itself runs privileged, with the host’s filesystem and process namespace: the agent opens shells as real unix users and forwards ports on their behalf, and a confined container can do neither.

$ docker ps | grep shellhub          # is it running
$ docker logs -f <container>         # what it is doing
$ docker restart <container>         # restart it

Podman

Picked when Podman is present and rootful. The same image and the same wrapper as Docker.

Rootful is a requirement, not a preference: the installer declines rootless, because an agent installed that way would appear to work and fail at the moment somebody tried to use it.

$ podman ps | grep shellhub          # is it running
$ podman logs -f <container>         # what it is doing
$ podman restart <container>         # restart it

Snap

Picked when the snap command is present. Leaves the shellhub snap. The snap needs the tenant named up front and cannot pair or take a provisioning key, so its device always lands pending until somebody accepts it.

That snap is classic rather than strict, for the same reason the containers are privileged, and classic snaps cannot be installed on Ubuntu Core — run the agent as a container under the Docker snap there instead. Being a snap also means snapd refreshes it on its own schedule, so the agent updates itself; sudo snap refresh --hold shellhub pins it.

$ snap services shellhub             # is it running
$ sudo snap logs -f shellhub         # what it is doing
$ sudo snap restart shellhub         # restart it

WSL

Picked on WSL 2 with Ubuntu. Leaves a native binary under systemd, inside the distribution.

Each of those is a requirement rather than a description: version 2, Ubuntu, systemd enabled, and mirrored networking. The installer checks all four and stops on any of them.

$ systemctl status shellhub-agent    # is it running
$ journalctl -u shellhub-agent -f    # what it is doing

Standalone

Picked when nothing above matched. Leaves a binary and a systemd unit that starts it at boot.

That unit is the whole of it: no daemon, no container runtime, no snapd, which is what makes this the leanest way to run the agent.

$ systemctl status shellhub-agent    # is it running
$ journalctl -u shellhub-agent -f    # what it is doing
$ sudo systemctl restart shellhub-agent   # restart it

What it configures

Variable Default Meaning
SERVER_ADDRESS the instance that served the script Which server the agent talks to
INSTALL_METHOD detected Force a method rather than letting the script pick
AGENT_VERSION fetched from $SERVER_ADDRESS/info Which agent version to install
AGENT_IMAGE docker.io/shellhubio/agent:$AGENT_VERSION Container image, for the container methods
INSTALL_DIR /usr/local/bin Where the binary goes, for standalone
PROVISIONING_KEY — Enrol without anybody accepting the device. See below
TENANT_ID — Which namespace to join. Deprecated: the device lands pending until somebody accepts it
SKIP_BOOT_RESTART_CHECK — Set to 1 to install under Podman without podman-restart.service

SERVER_ADDRESS is filled in for you. Set it only to point somewhere other than where the script came from.

Enrolling without accepting each device

Pairing asks somebody to approve the device, which is right for the machines in front of you and wrong for a hundred of them. A provisioning key carries the namespace and the rule for what happens on arrival, so devices enrol on their own, tagged:

$ curl -sSf https://cloud.shellhub.io/install.sh | PROVISIONING_KEY=<your-provisioning-key> sh

Uninstalling

$ curl -sSf https://cloud.shellhub.io/install.sh | sh -s uninstall

Standalone, WSL, Docker and Podman are supported. Other methods refuse rather than half-remove themselves; a Snap install goes with snap remove shellhub.

Machines the script does not cover

The script assumes a machine that already exists, with somebody — or a provisioning script — running a command on it. These are not that command with a different flag.

Yocto and Buildroot are embedded build systems: the agent goes into the product image while the image is built, before there is a machine to run anything on. FreeBSD has its own packaging. Building from source is you producing the binary.

A unit coming off a line has no console to open and no link to click, so an image carries a provisioning key rather than waiting to be paired. And the key that identifies a device is generated by the agent on first start: one baked into an image makes every unit the same device, and one the unit cannot keep makes it a new device on every boot. See Agent Configuration for where that file lives.

Where to go next

esc