linbit-tunl: tunl

tunl(1) -- LINBIT support tunnel CLI

NAME

tunl -- manage linbit-tunl sessions from the support side

SYNOPSIS

tunl list
tunl pick
tunl show SESSION_ID
tunl join [--read-only] [--debug] [SESSION_ID]
tunl browser [--profile NAME] [--kill] SESSION_ID
tunl ssh [SESSION_ID]
tunl upgrade SESSION_ID
tunl create-session [SESSION_ID] [--case CASE] [--customer NAME] [--expires-in MINUTES] [--join]
tunl expect [--ttl MINUTES]
tunl close [SESSION_ID]
tunl cancel [SESSION_ID]
tunl detach [SESSION_ID]
tunl help

DESCRIPTION

tunl is the support-side CLI for managing linbit-tunl sessions. It runs on support staff laptops or jumpboxes.

Every subcommand that queries or modifies sessions reaches the relay API through an SSH local port-forward to the relay's localhost. The relay API (relay-api) listens on 127.0.0.1 only and is never exposed to the internet; tunl forwards a local ephemeral port to 127.0.0.1:TUNL_API_PORT on the relay for each API call.

Authentication

tunl connects to the relay as TUNL_SUPPORT_USER (default: support). Configure the relay's /home/support/.ssh/authorized_keys with support staff personal keys. A standard ~/.ssh/config stanza works:

Host tunl.linbit.com
    User support
    IdentityFile ~/.ssh/id_ed25519

SUBCOMMANDS

tunl list

List active and pending sessions. Pending sessions (created by support but not yet connected by a customer) are shown with a pending mode label.

Output columns:

Column Description
SESSION ID Four-word session identifier
MODE Session mode: share, share+tunnel, pending
HOST Customer hostname (or customer name for pending sessions)
CASE Support case number (if provided)
AGE Time since registration or creation

tunl pick

Interactive numbered menu of all active and pending sessions. Select by number or by session ID prefix.

When only one session exists it is selected automatically and the table is skipped.

At the action prompt (varies by session type):

For pending sessions, only i (info) and q (quit) are offered.

tunl show SESSION_ID

Print full details for one session, including registration time, mode, relay port, tunnel port (if active), hostname, IP addresses, customer name, case number, user, and remote IP.

For pending sessions, prints the session ID, status, case, customer name, and age.

Exits with status 1 if the session is not found.

tunl join [--read-only] [--debug] [SESSION_ID]

Open a local tmux session showing the customer's terminal in real-time via the relay broker:

Window Name Purpose
0 main Full VT stream of the customer's shell
1 chat Bidirectional chat; session info in pinned header
2+ -- Extra windows opened from the menu (ssh, proxy, ...)

Two SSH connections from the support side connect to the broker (one for the main VT stream, one for chat-plus-status). Each is an independent SSH connection with ControlPath=none to prevent the user's ~/.ssh/config from multiplexing them over an unrelated master. In the main window, keystrokes are forwarded to the customer's shell. In the chat window, messages typed by support are displayed on the customer's side.

With --read-only, keystrokes are not forwarded in either window.

With --debug, the per-session temp directory (/tmp/tunl-{sid}/) is preserved after the join session ends instead of being cleaned up.

If SESSION_ID is omitted, the relay is queried and a session is selected automatically (or prompted if multiple exist). If the selected session is still pending, tunl join waits for the customer to connect before setting up the local tmux.

Key bindings within the join session. All hotkeys use the tmux prefix Ctrl-b by default; export TUNL_PREFIX (e.g. TUNL_PREFIX=C-a, M-Space, or any tmux key spec) before tunl join to pick a different prefix for the join-session tmux. Useful when nesting under a tmux you already run on your workstation -- the inner tmux keeps its default Ctrl-b. An unrecognized value is rejected by tmux and falls back to Ctrl-b with a warning on stderr. The cheat sheet (Ctrl-b ?), pane labels, and console banner all render the active prefix. The setting is per-side: support and customer pick their prefixes independently.

Binding Action
Ctrl-b 0/1 Switch between main (0) and chat (1) windows
Ctrl-b C-c Toggle main/chat window (mirrors customer-side Ctrl-b c)
F1 / Ctrl-b Enter Main menu
Ctrl-b ? Project-specific cheat sheet
Ctrl-b t Open SSH window to customer (tunl ssh) -- not available in restricted mode
Ctrl-b z Toggle customer-side zoom
Ctrl-b d Detach (leave session running, disconnect your client)
Ctrl-b Q Close join session (kills local tmux; customer stays connected)
Enter On a dead pane: reconnect; on a live pane: pass through
Key Action
s SSH to customer (opens tunl ssh in a new window)
x Proxy shell (SSH + SOCKS5 proxy env vars in a new window)
b Browser with throwaway profile (proxied Chrome/Chromium)
B Browser -- choose or create a named persistent profile
c Go to chat (switch to window 1)
z Toggle customer-side zoom
R Force my dimensions (bypasses smallest-wins, temporary)
d Detach (resume with tmux attach)
Q Close my window (customer continues)

Items s/x/b/B are not shown in restricted sessions (no tunnel available).

Detach vs. Close join session: d (Detach) disconnects your tmux client from tunl-join-SESSION_ID but leaves that tmux session running. Other engineers attached to the same session stay connected. You can reattach later with tmux attach -t tunl-join-SESSION_ID.

Q (Close join session) kills tunl-join-SESSION_ID entirely -- all windows, all SSH connections to the broker. The relay session and the customer's connection are unaffected; the customer stays connected and other engineers can still join. Use this when you are done with your local view of the session.

The chat window has monitor-activity enabled so its tab blinks when the customer sends a message.

The local tmux session is named tunl-join-SESSION_ID, allowing multiple engineers to join the same session concurrently from the same machine.

tunl browser [--profile NAME] [--kill] SESSION_ID

Open a SOCKS5 proxy through the reverse tunnel (via a shared SSH ControlMaster) and launch Chrome or Chromium with a named persistent profile. All browser traffic is routed through the SOCKS5 proxy (--proxy-server=socks5://...) so DNS resolves on the customer side.

Profile storage: ~/.local/share/linbit-tunl/profiles/tunl-PROFILE/

With --profile NAME, the named profile is used directly (created if it does not exist). Without --profile, an interactive menu is shown:

  1  tunl-acme-prod       (existing)
  2  tunl-beta-customer   (existing)
  n  Create new named profile
  t  Throwaway (deleted on exit)

Select profile [1-2/n/t]:

Named profiles persist across browser sessions (bookmarks, logins, history). Throwaway profiles are deleted when the browser exits.

With --kill, terminates a previously launched browser for this session (by saved PID) without starting a new connection.

If the session does not yet have a reverse tunnel, an upgrade is requested automatically.

tunl ssh [SESSION_ID]

SSH directly to the customer machine as the customer user via the reverse tunnel. If the session does not yet have a tunnel, requests an upgrade first (the relay broker picks a free port).

A shared SSH ControlMaster is established carrying a SOCKS5 proxy. When running inside tmux, a proxy-env window is opened with access information.

If SESSION_ID is omitted, the relay is queried and a session is selected automatically.

Use this for quick command-line work without the full join session UI. SSH and proxy shell access are also available from the join session menu (Ctrl-b Enter) as the s and x items respectively.

tunl upgrade SESSION_ID

Request a tunnel upgrade for a share session without connecting. The relay broker picks a free port and instructs the customer to open a reverse tunnel. Prints the confirmed relay port on success.

If the session already has an active tunnel, reports the existing port.

tunl create-session [SESSION_ID] [--case CASE] [--customer NAME] [--ad-hoc] [--expires-in MINUTES] [--join]

Pre-register a session on the relay. The relay generates a session ID if none is provided. Prints the session ID and the command the customer should run.

Options:

At least one of --case, --customer, or --ad-hoc is required. Sessions with no metadata at all are intentionally refused so that support reporting doesn't accumulate anonymous-looking records.

tunl expect [--ttl MINUTES]

Generate a one-time 6-digit pre-registration token for customer authentication. The token is consumed on first successful use.

[tunl] Pre-registration token created.
[tunl] Token:   482917
[tunl] Expires: 2024-04-15 15:30 UTC (60 minutes)
[tunl]
[tunl] Share this token with the customer (verbally or via case system).
[tunl] Customer runs:  sudo linbit-tunl.py --token 482917

Pass --ttl MINUTES to override the default TTL (default 60 minutes).

tunl close [SESSION_ID]

Terminate the customer's share session from the support side. Sends TERMINATE through the broker to the customer daemon, which kills the customer's tmux session. The session is deregistered automatically when the customer daemon exits.

Also cleans up local per-session temp files (ControlMaster socket, PID files, browser PID).

If SESSION_ID is omitted, the relay is queried and a session is selected interactively.

tunl cancel [SESSION_ID]

Remove a session from the relay registry.

For pending sessions: cancels the invitation before the customer connects. For active sessions: removes the registry entry without terminating the customer's process (the SSH connection and tmux survive until the customer disconnects normally).

Use tunl close to actively terminate the customer's session.

If SESSION_ID is omitted, the relay is queried and a session is selected interactively.

tunl detach [SESSION_ID]

Evict all current support clients (other engineers currently watching) of a share session. The customer's share session continues uninterrupted.

Useful when "changing seats": disconnects all watchers so a clean watch session can be started from a new machine.

If SESSION_ID is omitted, the relay is queried and a session is selected interactively.

tunl set-meta SESSION_ID [--case CASE] [--customer NAME]

Attach a case number and/or customer name to a session that doesn't have them yet (or fix a typo on an existing one). Useful for ad-hoc sessions that turn out to be tied to a real support ticket after the fact — the case (and downstream filesystem layout, slug-based filenames in archive bundles, etc.) starts reflecting the right metadata from that point on.

At least one of --case / --customer must be supplied (nothing to do otherwise). Both validate by the same rules as tunl create-session.

tunl cast SESSION_ID [--output FILE | -]

Download the relay-side asciinema recording for a session. Works for active, closed, and archived sessions transparently — for archived sessions the cast member is extracted from the session's .tar.gz / .tar.zst archive on the fly.

--output FILE writes to a file path (default: <SESSION_ID>.cast in the current directory); --output - streams the cast to stdout (binary) so it can be piped into asciinema play, cat, or another tool.

The endpoint behind this command is pod-internal; the call goes over the existing SSH port-forward.

tunl help

Print usage summary and exit.

ENVIRONMENT

TUNL_RELAY : Relay hostname. Default: tunl.linbit.com.

TUNL_RELAY_PORT : Relay SSH port. Default: 22.

TUNL_SUPPORT_USER : SSH username on the relay for support staff. Default: support.

TUNL_API_PORT : Port of the relay API on the relay's localhost. Default: 8080.

TUNL_SOCKS_PORT : Local port for the SOCKS5 proxy. Default: 1080. If the port is in use, the next free port in a range of 20 is selected automatically.

TUNL_FORWARD_AGENT : Set to 1, yes, or true to allow SSH agent forwarding to the customer system. Default: disabled.

TUNL_SSH_LOG : Path for SSH debug log output. Default: /tmp/tunl-ssh.log.

TUNL_DISPLAY_NAME : Display name shown to the customer in join/chat mode. Default: the value of git config user.name, falling back to $USER@hostname.

EXIT STATUS

0 : Success.

1 : Session not found, relay unreachable, SSH connection failed, or invalid arguments.

EXAMPLES

List active and pending sessions:

tunl list

Create a session and wait for the customer:

tunl create-session --case CASE-4711 --customer "Acme Corp" --join

Join an existing session:

tunl join swift-fox-jumps-barrel

Join in read-only mode (no keystroke forwarding):

tunl join --read-only swift-fox-jumps-barrel

SSH directly to the customer system:

tunl ssh swift-fox-jumps-barrel

Open a browser routed through the customer's network:

tunl browser swift-fox-jumps-barrel
tunl browser --profile acme-prod swift-fox-jumps-barrel
tunl browser --kill swift-fox-jumps-barrel

Generate a one-time customer token:

tunl expect --ttl 120

Terminate a session:

tunl close swift-fox-jumps-barrel

Cancel a pending session:

tunl cancel swift-fox-jumps-barrel

Override relay host for testing:

TUNL_RELAY=10.0.0.5 TUNL_RELAY_PORT=2222 tunl list

SEE ALSO

linbit-tunl(1), ssh(1), ssh_config(5)

docs/architecture.html -- trust model, session lifecycle, wire protocol