Subshell Docs
Nodes

Adding a Node

Mint a setup key, hand the machine the command or the values it renders, name the node on the machine itself, and get the row online.

Adding a node is three steps: mint a setup key on the control plane, hand the target machine what the dialog renders (a one-liner for a terminal, or two values for the Subshell Client app), and watch the row come online. This page walks the terminal path end to end, what the command actually does on the machine, and the one trap that bites most first attempts: the server URL.

1. Mint a setup key

Open the Nodes page and press Add node. The mint asks nothing, not even a name. It used to open with a "Node name" field, but that text only ever labeled the key: the one-liner named the machine from its own hostname whatever you had typed, so the question moved to where the answer is (step 3, on the machine). Press Create setup key and the dialog renders a copy-ready install command with the key baked in.

A setup key is an nsk_… code: single-use, expired 24 hours after it is created, and revocable by deleting it. An admin can see and revoke every key on the instance: the Setup keys card has a switch to list all accounts' keys, each row naming its creator, and the same Revoke button closes a foreign row. For everyone else, deleting stays the creator's act: the delete answers "not found" for every row the caller did not mint. It is not shown once. The Setup keys list on the same page keeps every key you minted readable in full, with a copy button, until it is used, expires, or is revoked, so closing the dialog mid-copy costs nothing. That trade is worth saying plainly: to list a key the server stores it in plaintext. The exposure is bounded by what the credential is (it enrolls exactly one machine, works once, and is inert after 24 hours or the moment you delete it), and the list is scoped: you see the keys you minted, not anyone else's, unless you are an admin and ask for the whole instance. Minting and revoking are audited, by key id; the key text never reaches the audit trail.

Who may mint one is an instance setting. By default anyone signed in can add their own machines; the switch on the Settings page narrows it to admins only. Turning it off means "stop handing out new keys": keys already minted stay usable until they expire or are deleted.

2. Run the command on the target machine

The dialog renders something like:

curl -fsSL "http://your-server:3080/install.sh?setup_key=<your-key>" | bash

On the target machine this:

  1. Checks for tmux and warns (with the right install command for the platform) if it is missing. A node without tmux can run no subshell at all. The install itself continues; enrollment is what refuses.
  2. Downloads the node binary for your platform (linux-x64, linux-arm64, or darwin-arm64; Intel Macs are refused by name) from that same server, to ~/.local/bin/subshell.
  3. Verifies the published .sha256 digest of the download before the file is ever made executable, and replaces nothing until the digest matches. A corrupt or partial download leaves any existing install untouched.
  4. Runs subshell setup, which asks what to call the machine, enrols it, and offers the background service, as step 3 describes.

If the server has no binary for your platform, it usually downloads one from the project's own public release on first request and serves it to you. When that is impossible (an air-gapped instance), the command says so rather than failing obscurely, and names the fallback: install the node binary another way and run subshell setup by hand.

The server address is baked into the command

The row you pick in the dialog's Address the node dials dropdown is the address the node will dial forever. Choose one the new machine can actually reach, usually your VPN or tailnet address, before copying: the choice rides along in the command (a server= argument the server accepts only for addresses it already trusts) and is baked into the script when the machine downloads it. A remote machine pointed at localhost enrolls, then dials itself and sits there offline. If the instance knows no other address, the dropdown has only that one row, so set the base URL first. See Networking.

3. subshell setup, the one verb

The one-liner ends at a single CLI verb, and the same verb is the whole path on a machine that already has the binary:

subshell setup --server http://your-server:3080 --key <your-key>

It checks for tmux FIRST, before it asks you anything and before your single-use key is spent, so a machine that could host no pane never costs you a key or an answer. Then it asks "Name this node": this is where a node gets its name. The answer is prefilled with the machine's hostname, so Enter accepts what the box calls itself and typing replaces it. It then generates an identity keypair and a link-encryption keypair on that machine, enrols with the control plane, and writes its config 0600. The node's long-lived key is stored only there, and never echoed, not even with --json. From its first connect, every byte the machine and the plane exchange rides a link both ends encrypt themselves. The node's traffic is not readable off the network just because this server runs on plain http://. Then it asks its second question: "Run the node in the background and start it at login?" Answer yes and it installs a systemd user unit on Linux or a launchd agent on macOS; --no-service (or SUBSHELL_NO_SERVICE=1 for the piped install) skips the question, and subshell service install does it later.

--name <n> answers the name question ahead of time, and anything that cannot ask must bring it: setup requires --name under --yes, under --json, or when there is no terminal to prompt. A piped one-liner takes no arguments, so its spelling is an environment variable on the bash side of the pipe:

curl -fsSL "http://your-server:3080/install.sh?setup_key=<your-key>" | SUBSHELL_NODE_NAME="mac mini" bash

The hostname is a prompt default, never an assumed answer. Cancel the name question and nothing is enrolled; your single-use key is still unspent.

On Linux, a user service dies at logout unless the user lingers. When lingering is off, or logind never answers, the install prints the command that fixes it: loginctl enable-linger $USER. It may need an administrator's rights to run.

4. The Desktop App path

No terminal on the machine? The dialog's reveal switches from Terminal to Desktop App and shows two values instead of a command: the server address and the setup key, each with its own copy button. In Subshell Client, open Window → This machine…, connect to that address, and paste both on its Enroll step. The app asks what to call the machine, installs the node binary it ships with (nothing is downloaded) and enrols. The unpublished-binary warning and the air-gapped detour are the terminal path's concerns; the app carries its own copy whatever this server has published. Subshell Client as a Node walks the assistant.

5. Online

The node appears in the list. Opening its page (or pressing Re-check) has the control plane probe it for the agent CLIs this instance offers (a node is only ever offered the harnesses actually installed on it), and then you can launch subshells there like on any other machine. Finish with the decisions every fleet owner should make deliberately: who may launch on it, and whether to restrict its directories.

If it does not come online

  • "The setup key was rejected": invalid, expired, or already used. Mint a fresh one and rerun the command; nothing was consumed on the machine's side except the download.
  • The node runs but the row stays offline: almost always the baked loopback URL, or the machine cannot reach the address. subshell status --probe on the node dials the stored address and names it.
  • A node started by hand dies when you close the SSH session: that is subshell run, the foreground daemon; install the background service (step 3).

See also

Edit on GitHub

Last updated on

On this page