linbit-tunl: quickref

linbit-tunl quick reference

Customer: open a support session

Support provides the exact command to run (they generated the session ID on the relay). Example:

# Default: support can type in your shell; tunnel requests show a confirmation popup.
linbit-tunl swift-fox-jumps-barrel

# Watch-only: support cannot type in your shell or request a tunnel.
linbit-tunl --restricted swift-fox-jumps-barrel

# Pre-open tunnel at start (auto-grant, no popup; also works without tmux).
linbit-tunl --tunnel swift-fox-jumps-barrel

# With a pre-registration token (required when relay enforces token auth):
linbit-tunl swift-fox-jumps-barrel --token 482917

# Preserve session logs after exit (for troubleshooting):
linbit-tunl --debug swift-fox-jumps-barrel

Customer tmux layout (2 visible panes)

+----------------------------------------------------------------------------+
|                                                                            |
| [SHELL] user@host:~#  (customer's working shell, focused)                  |
|                                                                            |
+----------------------------------------------------------------------------+
| [LINBIT SHARE] session-id  *  relay state  | viewers | mode  (pinned)      |
| [optional second pinned row: support note / warning]                       |
+----------------------------------------------------------------------------+
| customer> _                            (chat input; pinned status above)   |
+----------------------------------------------------------------------------+

(plus one hidden background window for the daemon; never visible)

Hotkeys: Ctrl-b Q=end session (orderly shutdown with watchdog)
         Ctrl-b c=toggle chat/shell  F1 or Ctrl-b Enter=menu  Ctrl-b z=zoom
         Ctrl-b u=pre-grant tunnel / unblock  Ctrl-b U=revoke tunnel
         Ctrl-b X=block tunnel requests
         on dead shell pane: Enter=restart  q=end session
         Ctrl-b L / C-l=reset layout
# Observe the session from another terminal (in addition to the above)
tmux -S /tmp/linbit-tunl-0f05bf25e11e/tmux.sock attach -r

Support: connect to a session

# Create a session -- relay generates the session ID and prints the customer command
tunl create-session --case CASE-4711 --customer "Acme Corp"
tunl create-session --case CASE-4711   # customer name optional

# Generate a one-time token for the customer (when relay requires it)
tunl expect
tunl expect --ttl 30      # custom TTL in minutes

# List active and pending sessions (pending = created by support, not yet connected)
tunl list

# Interactively pick a session and connect (includes pending sessions)
tunl pick

# Show details for one session
tunl show swift-fox-jumps-barrel

# Join the customer's session (shared tmux: window 0=main, 1=chat)
tunl join swift-fox-jumps-barrel

# Join read-only (watch but do not type)
tunl join --read-only swift-fox-jumps-barrel

# Join with debug logging (preserve session dir after exit)
tunl join --debug swift-fox-jumps-barrel

# SSH directly to customer (also available from join session menu: Ctrl-b Enter, s)
tunl ssh swift-fox-jumps-barrel

# Open customer-routed browser session (Chrome + SOCKS5; named profile)
# Also available from join session menu: Ctrl-b Enter, b
tunl browser swift-fox-jumps-barrel
tunl browser swift-fox-jumps-barrel --profile acme-prod

# Detach from join tmux -- session stays alive, customer stays connected, rejoin later
Ctrl-b d
# Close join session -- kills local tunl-join-* tmux; customer stays connected
Ctrl-b Q

Relay: operations

# Check relay-api is running
systemctl status linbit-tunl-api

# View active sessions directly (on the relay)
curl -s http://127.0.0.1:8080/api/v1/sessions | python3 -m json.tool

# View relay info (host key, API version)
curl -s http://127.0.0.1:8080/api/v1/info

# Manually remove a stale session
curl -X DELETE http://127.0.0.1:8080/api/v1/sessions/swift-fox-jumps-barrel

# Create a pre-registration token manually (on the relay)
curl -s -X POST http://127.0.0.1:8080/api/v1/expected \
    -H 'Content-Type: application/json' \
    -d '{"ttl_minutes": 60}'

# Revoke a token before it is used
curl -X DELETE http://127.0.0.1:8080/api/v1/expected/482917

# Add a support engineer's key (so they can use 'tunl join/ssh')
echo "ssh-ed25519 AAAA... engineer@linbit.com" >> /home/support/.ssh/authorized_keys

# Reload after config changes
systemctl reload ssh

Data flow (share mode via relay-share.py broker)

Customer system                     Relay                     Support laptop
---------------                     -----                     --------------
linbit-tunl.py                      sshd ForceCommand:        tunl join SID
  tmux session                        relay-share.py:           local tmux (2 windows)
    main (shell, top)                  broker per session          0: main (VT stream)
    chat (bottom, pinned status)       fan-out VT stream           1: chat (with pinned
    (hidden inner window)                                              status header)
  |                                  |                        |
  ssh (control-mode stream) -------> broker <---------------- ssh (consume)
                                       |
                                    SOCKS5 / tunnel:          join menu: SSH / proxy / browser
                                    ssh -R port:localhost:22    cssh / cscp / cproxy (proxy-env)

Session creation flow

Support                          relay-api            Customer
-------                          ---------            --------
tunl create-session
  POST /api/v1/pending
  <-- {id, customer_cmd}
  print customer command
                                                  linbit-tunl swift-fox-...
(optional:)                                         ssh tunl@relay "linbit-tunl/1 id=..."
tunl expect                                         relay-share.py produce:
  POST /api/v1/expected                               register session
  <-- {token}                                          spawn broker
  share token --> customer                             bridge SSH <-> broker socket
                                                     customer side:
                                                       2-pane tmux session
                                                       (main + chat with pinned status)
                                                       control-mode -> broker
tunl join swift-fox-...
  ssh support@relay
  relay-share.py consume swift-fox-...
  broker fans out VT stream

Token is consumed on first use. Reconnects (same session ID) bypass token check automatically.


Session ID format

Four lowercase words in adjective-noun-verb-noun order, hyphen-separated. Example: swift-fox-jumps-barrel. ~1.6x10^9 combinations (200^4) from four curated word lists of ~200 words each. Generated by the relay on tunl create-session; support reads it to the customer over the phone or pastes it into a ticket.


Key paths

Path Description
/etc/linbit-tunl/support-keys.pub Support staff SSH public keys
/etc/linbit-tunl/tunl-ca.pub Customer certificate CA public key
/etc/linbit-tunl/tunl-host-ca.pub Relay host certificate CA public key
/etc/linbit-tunl/tunl-principals Allowed certificate principals (tunl-customer)
/var/lib/linbit-tunl/tunl.db SQLite database (sessions + uploads)
/var/lib/linbit-tunl/share/ Broker socket directory (or /run/linbit-tunl/)
/var/log/linbit-tunl/ Per-session audit logs and cast files
/usr/local/lib/linbit-tunl/ Installed relay scripts
/etc/ssh/sshd_config.d/tunl.conf sshd restrictions for tunl/support users
/etc/pam.d/linbit-tunl PAM service for KI auth fallback

Ports

Port Description
443 Relay public ingress; nginx muxes by ClientHello: SSH bytes pass through to the relay sshd (customer + support SSH), TLS goes to the landing page and the public API
80 Relay HTTP (redirects to 443; also used for ACME)
8080 relay-api (localhost only on the relay host; reached via SSH forward or via the public-API allowlist on nginx)
30000-39999 Reverse tunnel ports (relay localhost, on-demand)
1080 Local SOCKS5 proxy (support laptop, via tunl ssh or join menu)

Environment overrides -- linbit-tunl.py

Variable Default Description
TUNL_RELAY tunl.linbit.com Relay hostname
TUNL_RELAY_PORT 22 Relay SSH port (set to 443 against the production relay, where nginx muxes 443 by ClientHello and forwards SSH bytes through)
TUNL_USER tunl Relay SSH username
TUNL_API https://tunl.linbit.com Relay API URL (cert signing, host key)
TUNL_AUTHKEYS_FILE ~/.ssh/authorized_keys Where to install support keys (tunnel mode)
TUNL_TARGET_HOST localhost Tunnel forward destination host
TUNL_TARGET_PORT 22 Tunnel forward destination port
TUNL_RELAY_CA_PUBKEY (embedded) Relay host CA public key (for testing)
TUNL_COMPRESS 1 SSH compression (set 0 to disable)
TUNL_CUSTOMER_COMPANY `` Organisation name; words > 2 chars become @mention targets in chat
TUNL_PREFIX C-b tmux prefix for the customer-side outer tmux (e.g. C-a, M-Space) -- useful when the shell pane runs nested tmux

Environment overrides -- tunl.py

Variable Default Description
TUNL_RELAY tunl.linbit.com Relay hostname
TUNL_RELAY_PORT 22 Relay SSH port (set to 443 against the production relay, where nginx muxes 443 by ClientHello and forwards SSH bytes through)
TUNL_SUPPORT_USER support Support SSH username on relay
TUNL_API_PORT 8080 relay-api port on relay localhost
TUNL_SOCKS_PORT 1080 Local SOCKS5 port for tunl ssh / join menu
TUNL_SSH_LOG /tmp/tunl-ssh.log SSH debug log
TUNL_DISPLAY_NAME $USER Display name shown to customer
TUNL_SUPPORT_COMPANY linbit Company name; words become @mention targets in chat
TUNL_RELAY_HOST_CA_PUBKEY (embedded) Relay host CA public key (for testing)
TUNL_PREFIX C-b tmux prefix for the join-session tmux (e.g. C-a, M-Space) -- useful when running nested under your own tmux

Environment overrides -- relay-api

Variable Default Description
TUNL_DB /var/lib/linbit-tunl/tunl.db SQLite database path
TUNL_SUPPORT_KEYS /etc/linbit-tunl/support-keys.pub Support keys file
TUNL_HOST_KEY_FILE /etc/ssh/ssh_host_ed25519_key.pub Relay SSH host public key
TUNL_API_BIND 127.0.0.1 Listen address
TUNL_API_PORT 8080 Listen port
TUNL_RELAY_HOSTNAME tunl.linbit.com Relay name in /api/v1/info
TUNL_REQUIRE_PENDING 1 (on) Require support to pre-register session ID
TUNL_REQUIRE_TOKEN (off) Require 6-digit one-time token
TUNL_TOKEN_TTL_MINUTES 60 Token lifetime in minutes

Environment overrides -- relay-share.py

Variable Default Description
TUNL_SHARE_DIR /run/linbit-tunl Broker socket directory
TUNL_API http://127.0.0.1:8080 relay-api base URL
TUNL_LOG /var/log/linbit-tunl Audit log directory
SSH_ORIGINAL_COMMAND (sshd) Customer context string
SSH_CONNECTION (sshd) Client IP info