Managing a Node
Restart, service controls, logs and config for an enrolled node, from the browser.
Most nodes are headless: a mini in a closet, a box on your tailnet, a cloud instance you only ever SSH into, or never. The node's page in the browser is therefore the only place left to ask about the machine: drive its service, read its log, flip its debug logging, or point it at a different control plane. These are not one permission, so this page names the gate on each verb rather than letting you assume.
What follows is about enrolled nodes. The control plane's own machine (the row named Server) refuses every one of these controls: it is the same host as the plane, and its service is managed from Server Settings → Service instead. And none of it reaches a node whose owner account has been disabled: the machine is disconnected the moment the account is disabled and refused at the door until it is re-enabled, so until then it simply shows offline (Users and roles).
The service controls
The Service card reports what the machine's service manager says and offers five verbs: start, stop, restart, install, uninstall the background node. All of them are browser acts over a cookie session (an API key, even a powerful one, is refused here), and each sends a signed command that the node carries out against its own service manager. That command rides the node's own socket, and since protocol 14 that socket is encrypted end to end at the application layer: TLS in front of the server is not required for this leg.
| Verb | Who may use it |
|---|---|
| start, install | owner or an edit grantee |
| restart | owner or an edit grantee |
| stop, uninstall | owner only |
The split is structural, not a nicety. Every command reaches the node over the node's own socket, so stop and uninstall end the very connection that would carry their undo: from a browser, a stopped node cannot be started again, and an uninstalled service cannot be reinstalled; only someone with a shell on that machine can fix it (subshell service install). That is why they sit with the owner: an edit grantee is trusted to interrupt a machine they were shared, not to make it unreachable.
restart briefly takes every subshell running there offline, the owner's and other grantees' included. The plane already runs arbitrary work on the node, so this adds no new reach, only new availability to think about. The node will refuse a verb when the request cannot mean what it says: the node is offline, it is not running under a service manager at all, nothing is installed, its binary is too old, or, for restart, when the service definition would close the live panes rather than leave them running, unless you explicitly pass force. Refusals are answered as reasons, not error codes.
The runtime report on the node's page (how the node is supervised, where its config, log, and binary live) appears only for an online node viewed by the owner or an edit grantee. A view grantee may launch on the machine; that does not make its paths their business.
Reading the log
Logs serves a slice of the node's own log file: bounded, self-replacing, and written by the node precisely so a browser can read a headless machine's output. Owner or edit, same gate as the service card. It records launches and their failures, command refusals, and connection problems. It never records pane content, and never command arguments, which is what keeps a log read from becoming a credential read, since a launch's arguments carry the subshell's bearer token.
Debug logging on a node is a switch on the same page: owner or edit. It is not owner-only because it is reversible and narrow: it changes what the machine writes to its own bounded file, and the same call turns it back. One refusal: if the operator set SUBSHELL_DEBUG_LOGGING in the node's environment, the environment wins and the page refuses to write a setting the next boot would mask. (It currently reveals little: the node's debug vocabulary is still thin. The mechanism exists ahead of the lines.)
How long pane logs live
Every pane's transcript (everything typed at that terminal, pasted tokens included) is streamed to a log file under the node's own data dir. The plane commands its deletion when you delete a subshell, but a node that was offline for that delete keeps the transcript on its own disk. So the node ages them out itself: a pass at boot and an hourly pass after it delete the logs of subshells that are no longer running. A running pane's log is never swept (it is what a browser replays from), and a liveness probe that cannot answer counts the pane running, because a sweep deletes.
The window belongs to the machine the files sit on, so the control plane has no toggle for it. The node's own loopback dashboard does: run the agent on the machine, open http://127.0.0.1:3090, and edit Pane log retention under Settings. A save writes the same config.json fields a hand-edit would, and the next hourly sweep uses them, so no restart is involved. Two honest limits: a node that booted with 0 days and 0 hours scheduled no sweep at all, so leaving keep-forever does take a restart; and a variable set in the node's environment wins over every write, so the page shows that variable and refuses to change the field it forces, exactly as the debug-logging switch does. Below the dashboard, the same two settings can be written by hand or by the environment:
| Setting | Default | Effect |
|---|---|---|
SUBSHELL_LOG_RETENTION_DAYS / logRetentionDays in config.json | 1 | Days a non-running log survives. |
SUBSHELL_LOG_RETENTION_HOURS / logRetentionHours in config.json | 0 | The hours half: a log goes once older than days × 24h + hours. |
0 days and 0 hours together keep everything forever. The one-day default is deliberately shorter than the server's own 30-day window: a node is not where typed transcripts should accrete.
Repointing at a different control plane
Config lets the owner replace the address the node dials, the browser twin of running subshell configure --server <url> on the machine. This one deserves care:
- It keeps the machine's identity: the same node id, the same key, the same pinned signing key, and the same encryption link (its own keypair and the plane's pinned public half). It spends no setup key and mints no second row, and the new plane must already hold the key the node pins or every command it sends fails verification. In practice it only works between two names for one plane.
- But the node dials the newly named host carrying this plane's node key. Repointing discloses that credential to whatever address was typed, so treat it as naming a host you trust, not as editing a URL.
- Loopback addresses are refused outright (nobody is standing at a headless machine to notice it dialing itself), and the change takes effect when the node restarts, because choosing when is the operator's act, not the page's.
- It is audited, naming the new value (the plane never knew the old one: which address a node dials lives in that machine's own config).
Contrast with re-enrolling from the desktop app: enrolling overwrites the config, mints a second node row, and discards the old key. Repointing does none of that.
See also
- Maintenance Mode: stop launches without stopping the node.
- Updating a Node: the other act that restarts the node.
- Server Logs and Debug Logging: the same posture, for the plane's own log.
- The threat model: every verb's permission, refusal, and audit event.
Last updated on
