linbit-tunl -- open a shared terminal session so LINBIT support can assist on this system
linbit-tunl [--case CASE] [--token TOKEN] [--user USER]
[--restricted] [--tunnel]
[--attach] [--debug] [--target HOST:PORT] SESSION_ID
linbit-tunl connects to the LINBIT relay server and
creates a shared terminal session. LINBIT support engineers see the
customer's terminal in real time and can type commands (unless
restricted). An optional reverse-tunnel upgrade lets support SSH
directly into the customer system.
tmux is required for all modes except
--tunnel. The script creates a tmux session at a dedicated
socket with two visible panes: the customer's working shell
(main) on top and a chat pane (bottom) whose top rows are
pinned to show session status (connection state, support viewer count,
mode). The daemon that bridges tmux control-mode to the relay runs in a
separate hidden background window, not in a visible pane. If tmux is not
installed, only --tunnel mode is available; all other modes
exit with an error.
The customer retains full visibility and control:
tunl create-session on the relay).POST /api/v1/sign with a one-time token; the
relay CA signs it. Fallback: keyboard-interactive PAM authentication
using the session ID or a 6-digit token.ssh tunl@relay "linbit-tunl/1 id=SID host=... mode=share ...".
The relay's ForceCommand (relay-share.py produce) spawns a
per-session broker./tmp/linbit-tunl-{hash}/tmux.sock) with the two-pane
layout described above. The daemon runs in-process in a hidden
background window tagged @linbit-role=inner (created via
break-pane -d); it never appears as a visible pane.%output events from the main pane, and forwards them over
SSH stdin to the broker. Broker commands arrive on SSH stdout.--debug was specified or the exit was
abnormal.share (default) : Support can see the terminal, type in
the main shell, and request a reverse-tunnel upgrade. Upgrade requests
show a confirmation popup; the customer explicitly grants or denies each
one. The customer can pre-grant future requests at any time with
Ctrl-b u (equivalent to --tunnel behavior)
or block all requests via the menu.
--restricted : Watch-only. Support can see the terminal
and send chat messages but cannot type or request a tunnel.
--tunnel : Pre-open the reverse tunnel at session start
and auto-grant further requests without a confirmation popup. The only
mode that works without tmux.
When a reverse tunnel is granted, linbit-tunl adds a
bracketed block to TUNL_AUTHKEYS_FILE:
# linbit-tunl-begin SESSION_ID
ssh-ed25519 AAAA... engineer@linbit.com
# linbit-tunl-end SESSION_ID
On startup, stale blocks from any previous session are removed before a new block is added.
All hotkeys use the tmux prefix Ctrl-b (the tmux
default). The customer can pick a different prefix for the tunl outer
tmux by setting the environment variable TUNL_PREFIX before
invoking linbit-tunl.py, which is useful when nesting tmux:
setting TUNL_PREFIX=C-a (or any tmux key spec, e.g.
M-Space, F12) keeps the customer's own tmux
inside the shell pane fully usable with its default Ctrl-b. An
unrecognized value is rejected by tmux and silently falls back to Ctrl-b
with a warning on stderr. The cheat sheet (Ctrl-b ?),
status bar, and pane labels all render the active prefix.
| Key | Action |
|---|---|
| Ctrl-b Q | End session for good (orderly shutdown) |
| Ctrl-b c | Toggle focus between chat and shell panes |
| Ctrl-b C-c | Same as Ctrl-b c (chord variant; mirrors support side) |
| Ctrl-b u | Pre-grant tunnel / unblock requests |
| Ctrl-b U | Revoke tunnel (closes active tunnel, returns to confirm state) |
| Ctrl-b X | Block all future tunnel requests (no popup, auto-reject) |
| Enter (on a dead shell pane) | Respawn the shell |
| q (on a dead shell pane) | End session for good |
| Ctrl-b L / C-l | Reset pane layout |
| F1 / Ctrl-b Enter | Open main menu |
| Ctrl-b ? | Show project-specific cheat sheet |
| Ctrl-b z | Toggle zoom on the current pane |
Ctrl-b u behavior depends on current tunnel state:
Ctrl-b U revokes the active tunnel or clears pre-grant status, and returns to the default confirm state.
| Key | Action |
|---|---|
| c | Go to chat -- move focus to the chat pane (same as Ctrl-b c) |
| L | Reset layout -- rebuild split view from scratch (same as Ctrl-b L) |
| u | Grant tunnel -- pre-grant or unblock (same as Ctrl-b u) |
| U | Revoke tunnel -- close active tunnel, return to confirm state |
| X | Block tunnel requests -- reject all future requests without a popup |
| Q | End session for good (orderly shutdown) |
BRB (Be Right Back) is a chat slash command rather
than a menu entry. Type /brb in the chat pane to freeze
keystroke forwarding from support; /back to restore.
Support can still watch the terminal and chat while BRB is active. BRB
is not available while a tunnel is active -- revoke the tunnel first.
Enforcement is on the customer side: the daemon drops KEYS
commands locally regardless of relay behavior.
Ending the session: Q (End session for
good) triggers an orderly shutdown: the daemon closes the SSH connection
to the relay, deregisters the session, removes temporary files, and then
kills the tmux server (or the tmux window when running with
--attach). This is the only way to end a session.
The orderly shutdown has bounded internal waits and a watchdog: if
cleanup ever exceeds ten seconds, the daemon escalates by sending
SIGKILL to itself. One button, predictable. If Q ever
appears stuck for longer, kill the terminal externally and file a bug --
the daemon should not need an external rescue.
SESSION_ID : Four lowercase words separated by hyphens,
e.g. swift-fox-jumps-barrel. Provided by support. Required.
The same ID can be reused to reconnect to an existing relay session.
--case CASE : Support case number or short free-text
description. Included in the session context string and shown in
tunl list. Optional.
--token TOKEN : Six-digit pre-registration token for
certificate signing. Obtain from LINBIT support before invoking the
script. On reconnect after a network drop, the relay remembers the
session and does not require the token again.
--user USER : Username for the reverse-tunnel SSH
connection. Default: auto-detected from $USER or
$LOGNAME.
--restricted : Watch-only mode. Support can see the
terminal and send chat messages but cannot type in the main shell or
request a reverse tunnel.
--tunnel : Pre-open the reverse tunnel at session start
and auto-grant further tunnel requests without a confirmation popup. The
only mode that works without tmux.
--attach : Create a window inside the current tmux
session instead of starting a dedicated tmux server. Automatic when
re-run from inside an existing linbit-tunl tmux session.
--debug : Preserve the per-session temp directory
(/tmp/linbit-tunl-{hash}/) after exit. Without this flag,
the directory is preserved only on abnormal exit (error or short-lived
session) and removed on normal exit.
--target HOST:PORT : Forward the reverse tunnel to
HOST:PORT instead of localhost:22. Useful when the target
SSH daemon listens on a non-default port.
--help, -h : Print usage and exit.
--version, -V : Print version and exit.
TUNL_RELAY : Relay hostname. Default:
tunl.linbit.com.
TUNL_RELAY_PORT : Relay SSH port. Default:
22.
TUNL_USER : SSH username on the relay. Default:
tunl.
TUNL_API : Base URL for the relay API. Default:
https://tunl.linbit.com.
TUNL_AUTHKEYS_FILE : Path to the authorized_keys file
where support keys are installed during a tunnel upgrade. Default:
~/.ssh/authorized_keys.
TUNL_TARGET_HOST : Equivalent to the host part of
--target. Default: localhost.
TUNL_TARGET_PORT : Equivalent to the port part of
--target. Default: 22.
TUNL_RELAY_CA_PUBKEY : Override the embedded relay CA
public key. Intended for testing.
TUNL_COMPRESS : Enable SSH compression. Default:
1. Set to 0 to disable.
All per-session files live under
/tmp/linbit-tunl-{hash}/, where {hash} is the
first 12 hex digits of SHA-256(session_id). This keeps the session ID
out of /tmp directory listings. The directory is removed on
normal exit and preserved on error or with --debug.
/tmp/linbit-tunl-{hash}/tmux.sock : Dedicated tmux
socket for the session.
/tmp/linbit-tunl-{hash}/ssh-ctl.sock : SSH ControlMaster
socket for connection multiplexing.
/tmp/linbit-tunl-{hash}/debug.log : Append-only stderr
tee. All daemon stderr output is captured here.
/tmp/linbit-tunl-{hash}/ssh.log : Append-only SSH debug
log. Every line is timestamped and appended here. Survives reconnects.
Useful for diagnosing authentication failures or network errors.
/tmp/linbit-tunl-{hash}/chat.fifo : Named pipe for chat
pane input.
/tmp/linbit-tunl-{hash}/ctl.fifo : Named pipe for daemon
control commands (tunnel grant/revoke).
/tmp/linbit-tunl-{hash}/known_hosts : Temporary
known_hosts file trusting the relay CA.
/tmp/linbit-tunl-{hash}/sharekey/ : Ephemeral keypair
and CA-signed certificate for this session.
/tmp/linbit-tunl-{hash}/askpass.sh : SSH_ASKPASS helper
(KI/PAM fallback only; not created when using certs).
~/.ssh/authorized_keys (or
TUNL_AUTHKEYS_FILE) : Support keys are appended here when a
reverse tunnel is granted and removed on disconnect.
The session recording (asciinema v2 .cast file) is
written to a persistent location outside the session temp directory so
it survives normal cleanup. The location is chosen in order of
preference:
/var/log/linbit-tunl/linbit-tunl_{timestamp}_{hostname}.cast~/.local/share/linbit-tunl/linbit-tunl_{timestamp}_{hostname}.cast/tmp/linbit-tunl_{timestamp}_{hostname}.castThe first writable location is used. The path is printed on session
exit. Replay with asciinema play -i 2 <path> (caps
idle gaps at 2 seconds).
0 : Normal exit (Ctrl-b Q, SIGTERM, or clean
completion).
1 : Fatal error (relay unreachable, authentication
failure, invalid session ID, etc.).
130 : Daemon exited via SIGINT (delivered by Ctrl-b Q or
kill -INT).
143 : Daemon exited via SIGTERM.
Connect with a session ID and case number:
sudo linbit-tunl.py --case CASE-4711 swift-fox-jumps-barrel
Connect with a pre-registration token:
sudo linbit-tunl.py --token 123456 swift-fox-jumps-barrel
Point the reverse tunnel at a non-default SSH port:
sudo linbit-tunl.py --target localhost:2222 swift-fox-jumps-barrel
Watch-only mode (support cannot type or request a tunnel):
sudo linbit-tunl.py --restricted swift-fox-jumps-barrel
Attach inside an existing tmux session:
sudo linbit-tunl.py --attach swift-fox-jumps-barrel
End the session (from inside the tmux session):
Ctrl-b Q # disconnect from relay, kill tmux server
Ctrl-b K # kill the entire tmux session
From outside tmux:
kill -TERM $(pgrep -f linbit-tunl)
tunl(1), ssh(1), tmux(1)
docs/architecture.html -- trust model, auth paths, wire
protocol