Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ssh-obi

Developed by Human-life Information Platforms Institute (Menhera.org).

ssh-obi keeps SSH shells alive when the connection drops, when a laptop sleeps, or when the client moves between networks.

It is designed to feel like plain SSH:

  • Use the same destination names, keys, jump hosts, and SSH config you already use.
  • Keep using your local terminal scrollback, search, selection, and copy/paste.
  • Reconnect to the same remote shell after a network break.
  • Keep several independent sessions on the same remote account.
  • Install per user, without a root service or a custom network port.

ssh-obi is intentionally not a terminal window manager. It does not implement panes, tabs, or in-band escape commands. If you want window management, run tmux or another multiplexer inside the remote shell.

Status

ssh-obi v0.1.3 is the current release. It is available on crates.io, tagged as v0.1.3 on GitHub, and distributed as release tarballs from https://obi.menhera.org/.

The v0.1.3 release adds MOTD printing before new session shells start and caps automatic reconnect retries with a small exponential backoff. The 0.1 wire protocol baseline remains unchanged.

The documentation on this site is the user-facing source for the published bootstrap scripts, release tarballs, install flow, and supported platforms.

Quick Examples

Connect to a host:

ssh-obi user@example.com

Create a new session even if free sessions already exist:

ssh-obi --new user@example.com

List existing sessions without attaching:

ssh-obi --list user@example.com

List sessions from inside the remote account:

ssh-obi-server --list

Detach the currently attached client for a known session from your local machine:

ssh-obi --detach --session abc123 user@example.com

Or detach from inside the remote shell without killing it:

ssh-obi-server --detach

What To Expect

  • A network disconnect does not kill the remote shell.
  • Remote output continues to be collected while you are detached.
  • Recent output is replayed on reconnect.
  • Some recently displayed output may appear twice after reconnect.
  • New sessions show the remote host MOTD before the shell prompt, unless ~/.hushlogin suppresses it.
  • Windows is a client-only platform. Remote servers are Unix-like systems.

Getting Started

This page assumes you already have a local ssh-obi client. If you do not, see Installation.

First Connect

Run:

ssh-obi user@example.com

The client starts the system ssh binary and prepares the remote side over the same SSH connection. If a compatible server component is already installed, the session starts immediately.

If a compatible server is already installed at ~/.ssh-obi/bin, at ~/.cargo/bin, or on the remote PATH, ssh-obi uses it. If the server component is missing or incompatible and a prebuilt tarball exists for the remote platform, ssh-obi asks before installing it into ~/.ssh-obi/bin on the remote account. No root access is needed for the built-in install path.

After installation, ssh-obi attaches to a new or existing session.

When a new session is created, the remote host MOTD is printed before the shell starts, unless the remote account has ~/.hushlogin.

Session Selection

When you connect, ssh-obi looks for sessions owned by the same remote user.

If no free session exists, a new session is created.

If exactly one free session exists, the client attaches to it automatically.

If multiple free sessions exist, the client prompts locally:

Select a session to attach:

  #   INIT                 DETACH               WHAT
  1   2026-05-01 09:14     2026-05-02 11:02     bash
  2   2026-05-01 14:30     2026-05-02 09:55     vim notes.md
  3   2026-05-02 10:11     2026-05-02 10:48     cargo watch
  n   (new session)

>

Busy sessions are shown by --list, but they are not selectable in the interactive picker.

Detach Without Killing The Shell

From inside the remote shell:

ssh-obi-server --detach

This detaches the client. The shell keeps running. The local client exits with status 0 and does not reconnect.

Closing the laptop, losing Wi-Fi, or killing the local SSH connection is different: the client treats that as ambiguous and attempts to reconnect.

Reconnect Behavior

After the first successful attach, the client knows the session id. If the SSH connection disappears without a graceful detach or shell exit report, the client reconnects and asks for the same session.

If the old broker is still attached when reconnect starts, the reconnecting client asks that stale client to detach and then retries the attach.

Reconnect retries use short exponential backoff delays: 125ms, 250ms, 500ms, 1s, then 2s for later attempts. The client gives up after 10 reconnect attempts.

On reattach, recent output is replayed first, then live forwarding resumes. The replay buffer is bounded, so old history belongs in your local terminal scrollback.

Installation

ssh-obi publishes release tarballs at https://obi.menhera.org/. The bootstrap scripts download those tarballs and install binaries into ~/.ssh-obi/bin on Unix-like systems or %USERPROFILE%\.ssh-obi\bin on Windows.

The crate is also published on crates.io as ssh-obi.

Signature verification is not implemented for the MVP. The bootstrap trusts HTTPS.

Local Client Install From crates.io

If you already have a Rust toolchain, install the local client with:

cargo install ssh-obi

This installs the client binary, ssh-obi. It does not install the remote server binary by default. For normal use, let the client bootstrap or update the remote server component when you connect.

For remote platforms where no prebuilt server tarball is published, install a server-capable build in the remote account with:

cargo install --features server-bin ssh-obi

The attach bootstrap checks ~/.cargo/bin/ssh-obi-server directly, so this works even when that directory is not on PATH.

OpenBSD does not have prebuilt ssh-obi release tarballs. On OpenBSD remote hosts, install a Rust toolchain first, then run:

cargo install --features server-bin ssh-obi

After that, connect normally; the bootstrap will find the Cargo-installed ssh-obi-server.

Unix Install

To preinstall or update a Unix-like account without starting a session:

wget -O - https://obi.menhera.org/bootstrap.sh | sh -s -- --install

The sh -s -- --install form is deliberate and portable:

  • -s tells a POSIX shell to read the script from standard input.
  • The first -- ends shell option parsing.
  • The second --install is passed to the bootstrap script.

Do not use sh - -- --install. Portable /bin/sh implementations treat the first -- as a script filename, not as “read from stdin”.

The Unix installer:

  1. Detects the OS and CPU.
  2. Downloads release-<target>.tar.gz.
  3. Extracts ssh-obi-server and, when present, ssh-obi.
  4. Installs binaries into ~/.ssh-obi/bin.
  5. Adds that directory to shell startup files used by non-interactive SSH: ~/.bashrc, ~/.zshenv, and fish conf.d.
  6. Prints OBI-INSTALL-COMPLETE in install-only mode.

The bootstrap does not edit ~/.bash_profile, because ssh-obi needs paths available to non-interactive remote commands.

You can run the installer repeatedly to update the installed binaries. It will not add duplicate ssh-obi PATH entries to shell startup files.

Install During Connect

Most users do not need to run the Unix bootstrap manually. The local client handles the remote check during normal connection:

ssh-obi user@example.com

If the remote server component is missing or incompatible, the local client asks for confirmation and installs it into that remote account.

During normal attach, the bootstrap probes for an existing compatible server in this order:

  1. ~/.ssh-obi/bin/ssh-obi-server
  2. ~/.cargo/bin/ssh-obi-server
  3. ssh-obi-server found on PATH

The PATH probe supports distro-packaged installs. The direct Cargo path supports remote platforms where cargo install --features server-bin ssh-obi is the practical server install path.

Windows Client Install

Windows can run the client only. It cannot run the remote server.

PowerShell is preferred:

powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://obi.menhera.org/bootstrap.ps1 | iex"

cmd.exe cannot execute a batch file directly from an HTTPS URL. Download the batch file, then run it:

curl.exe -fsSL -o "%TEMP%\ssh-obi-bootstrap.bat" https://obi.menhera.org/bootstrap.bat && "%TEMP%\ssh-obi-bootstrap.bat" --install

Both Windows bootstraps:

  • Support x86_64 Windows.
  • Download release-x86_64-pc-windows-msvc.tar.gz.
  • Install ssh-obi.exe into %USERPROFILE%\.ssh-obi\bin.
  • Add that directory to the user’s PATH.
  • Print OBI-INSTALL-COMPLETE.
  • Never start a server.

Restart the terminal if ssh-obi.exe is not found immediately after PATH updates.

Windows Terminal is recommended for interactive use. ssh-obi.exe enables Windows virtual terminal input while attached, so arrow keys and other line-editing keys are forwarded to the remote shell correctly in terminals that support that console mode.

You can run either Windows bootstrap repeatedly to update ssh-obi.exe. The installer only adds the install directory to the user PATH when it is not already present.

Manual Install From A Tarball

Manual install is also possible:

mkdir -p "$HOME/.ssh-obi/bin"
gzip -dc release-x86_64-unknown-linux-musl.tar.gz | tar -xf - -C "$HOME/.ssh-obi/bin"

For Unix server-capable targets, the tarball contains:

  • LICENSE-APACHE
  • LICENSE-MPL
  • ssh-obi
  • ssh-obi-server

For Windows client-only targets, the tarball contains:

  • LICENSE-APACHE
  • LICENSE-MPL
  • ssh-obi.exe

Uninstall

Remove the install directory:

rm -rf "$HOME/.ssh-obi"

Then remove the ssh-obi PATH lines from any shell startup files that the installer updated.

On Windows, delete %USERPROFILE%\.ssh-obi and remove that directory from the user PATH.

Connecting

Basic Commands

Attach to an existing free session or create one if none exists:

ssh-obi user@example.com

Always create a new session:

ssh-obi --new user@example.com

Attach to a specific session:

ssh-obi --session abc123 user@example.com

List sessions and exit:

ssh-obi --list user@example.com

List sessions directly on the server host:

ssh-obi-server --list

Detach the attached client for a specific session and exit:

ssh-obi --detach --session abc123 user@example.com

Detach from inside the remote shell:

ssh-obi-server --detach

Using SSH Options

ssh-obi invokes the system ssh binary and forwards common OpenSSH options before the destination.

Examples:

ssh-obi -p 2222 user@example.com
ssh-obi -i ~/.ssh/work_ed25519 user@example.com
ssh-obi -J bastion.example.com user@app.example.com
ssh-obi -F ~/.ssh/config work-host
ssh-obi -o StrictHostKeyChecking=accept-new user@example.com
ssh-obi -vv user@example.com

Common passthrough options include:

  • Standalone flags such as -4, -6, -A, -a, -C, -q, -T, -t, -X, -x, -Y, and -v/-vv/-vvv.
  • Options with values such as -p, -i, -J, -F, -o, -l, -L, -R, -D, and -W.

ssh-obi manages the remote command itself. Remote command arguments are not supported:

ssh-obi user@example.com uptime

Use plain ssh for one-shot remote commands.

Picking A Session

Free sessions are selectable. Busy sessions may be displayed for awareness but are not assigned picker numbers.

Columns:

  • INIT: when the session was created.
  • DETACH: when a client last detached, or - if it has never detached.
  • WHAT: best-effort foreground command detected for the session.

The WHAT column is only a hint.

New sessions print the remote host MOTD before the shell starts. The MOTD sources are /run/motd.dynamic, /etc/motd, and readable non-empty files in /etc/motd.d/. Create ~/.hushlogin on the remote account to suppress this MOTD output.

ssh-obi-server --list is the local server-host equivalent. It lists all alive sessions for the current remote user, free or busy, and includes session IDs. When run from inside an ssh-obi session, the current session is marked with * in the CUR column. When run outside an ssh-obi session, no row is marked current.

When The Remote Shell Exits

If the remote shell exits while attached, the local client mirrors the exit status where possible.

If the connection is lost before the client receives a shell-exit report, the client reconnects first. If the session is gone or no exit status can be recovered, the client reports failure rather than guessing success.

When an automatic reconnect targets a known session and that session still reports an attached client, ssh-obi sends a detach request for that same session and retries the attach. Normal first-time attach attempts do not detach busy sessions automatically.

Reconnect retries use 125ms, 250ms, 500ms, 1s, and then capped 2s delays. The client gives up after 10 attempts.

A deliberate detach through ssh-obi-server --detach is graceful. The client exits with status 0 and does not reconnect.

Sessions

An ssh-obi session is one long-lived remote shell. The session can outlive many SSH connections.

Creating Sessions

Use ssh-obi user@host to attach to a free session or create one when none is available.

Use ssh-obi --new user@host to always create a new session.

Use ssh-obi --session ID user@host to attach to a specific session.

New sessions start the remote user’s shell as a login shell, using the usual leading-dash argv[0] convention such as -bash or -zsh. This lets shell startup behavior match interactive SSH more closely.

Before the shell starts, new sessions print the remote host MOTD. ssh-obi prints readable non-empty /run/motd.dynamic and /etc/motd files, followed by readable non-empty files in /etc/motd.d/ in filename order. A ~/.hushlogin file in the remote user’s home directory suppresses this MOTD output.

New sessions also start in the remote user’s home directory. TERM is forwarded from the local client when it is useful; if it is missing or dumb, ssh-obi uses xterm-256color.

When both sides support initial-window-size.v1, the client sends the current terminal size before creating or attaching to a session. New remote PTYs start with that size, and reattaches apply the size before replaying buffered output.

Busy Sessions

A session can have only one attached client. If another client is already attached, the session is busy.

Busy sessions are still visible in --list. You can also ask a known busy session to detach its current client:

ssh-obi --detach --session ID user@host

On the server host itself, ssh-obi-server --list lists all alive sessions for the current Unix user. If it is run inside an ssh-obi session, that session is marked in the CUR column. If it is run outside an ssh-obi session, no session is marked current.

During automatic reconnect, ssh-obi already knows the session it is trying to recover. If that session is still marked busy because the previous broker has not fully gone away, the reconnecting client asks the stale attached client to detach and then retries the attach. Reconnect retries use capped exponential backoff: 125ms, 250ms, 500ms, 1s, then 2s for later attempts, with a maximum of 10 attempts.

Detach

Detach means “drop the client, keep the shell”.

When the network drops, the remote shell keeps running and waits for another client.

When the user runs:

ssh-obi-server --detach

the local client exits cleanly and does not reconnect.

The shell is not sent SIGHUP.

Output While Detached

Remote output continues to be collected while detached.

This prevents commands that print output from getting stuck just because no client is attached.

Detached output is kept in a bounded replay buffer. When the buffer fills, old bytes are evicted.

Reconnect And Replay

After the first attach, the client knows the session id. On an ambiguous disconnect, it starts a fresh SSH connection and requests that same session.

If that reconnect attempt finds the session busy, the client sends a detach control request for the same session and retries. Manual first-time attaches still report a busy session rather than detaching another client automatically. Reconnect retries stop after 10 attempts.

The session sends recent output, then resumes live forwarding. This can duplicate bytes the local terminal already displayed before the disconnect. That is acceptable and expected.

Anything older than the replay buffer belongs in the local terminal scrollback.

Shell Exit

When the shell exits, the session ends. A later ssh-obi user@host invocation will not list that session.

Platforms and Downloads

Local Client Platforms

Supported local client platforms:

  • Linux
  • macOS
  • FreeBSD, NetBSD, and illumos where release artifacts are published
  • Windows x86_64

The client requires a working system ssh binary.

On Windows, use Windows Terminal or another console that supports Windows virtual terminal input. This lets special keys such as arrows, Home/End, and other line-editing keys reach the remote shell as normal terminal escape sequences.

Remote Server Platforms

Supported remote server platforms:

  • Linux
  • macOS
  • FreeBSD
  • OpenBSD when installed from Cargo or a distro package
  • NetBSD
  • illumos
  • Other Unix-like systems where ssh-obi-server can be built from source and installed with Cargo or a distro package.

The server component is not supported on Windows.

systemd-based Linux distributions are supported, but systemd is not required. No system-wide service install is required. On Linux systems with systemd and a user bus, new PTY children are placed into a transient user scope on a best-effort basis; non-systemd Linux systems continue without that step.

Downloads

Release files are served from https://obi.menhera.org/.

The tarball list below is the prebuilt artifact set. Some Unix-like platforms, such as OpenBSD, are intentionally supported through a locally installed ssh-obi-server rather than a prebuilt tarball. Install with Cargo or a distro package, then connect normally; the bootstrap will use a compatible server from ~/.cargo/bin/ssh-obi-server or from PATH.

OpenBSD has no prebuilt binaries. On OpenBSD, install a Rust toolchain and run:

cargo install --features server-bin ssh-obi

Server-capable tarballs:

  • release-x86_64-unknown-linux-musl.tar.gz
  • release-aarch64-unknown-linux-musl.tar.gz
  • release-riscv64gc-unknown-linux-musl.tar.gz
  • release-powerpc64le-unknown-linux-musl.tar.gz
  • release-s390x-unknown-linux-musl.tar.gz
  • release-x86_64-apple-darwin.tar.gz
  • release-aarch64-apple-darwin.tar.gz
  • release-x86_64-unknown-freebsd.tar.gz
  • release-x86_64-unknown-netbsd.tar.gz
  • release-x86_64-unknown-illumos.tar.gz

Client-only tarballs:

  • release-x86_64-pc-windows-msvc.tar.gz

Server-capable tarballs contain ssh-obi and ssh-obi-server. Windows client-only tarballs contain ssh-obi.exe.

Out Of Scope

  • Android.
  • Windows remote servers.
  • Terminal pane/window management.
  • UDP transport.
  • Screen-state replication.

Troubleshooting

ssh-obi cannot find ssh

Install OpenSSH client tools and make sure ssh is on PATH. ssh-obi uses the system ssh binary and does not include its own SSH implementation.

Remote install reports unsupported target

The Unix bootstrap chooses a tarball from uname -s and uname -m. If it prints an unsupported target error, no release tarball is currently published for that OS/CPU pair.

Use Platform Support to check the published target list.

If the platform can build Rust code, install the server on the remote account instead:

cargo install --features server-bin ssh-obi

The bootstrap probes ~/.cargo/bin/ssh-obi-server directly and also accepts a compatible ssh-obi-server on PATH, so Cargo-installed and distro-packaged servers can be used without a prebuilt tarball.

Windows install succeeds but ssh-obi.exe is not found

The Windows bootstrap updates the user’s PATH. Existing terminals may not see that change.

Open a new terminal and try:

ssh-obi.exe --help

Arrow keys do not work from Windows

Use Windows Terminal or another console with Windows virtual terminal input support. While attached, ssh-obi.exe enables that mode so special keys are sent to the remote PTY as escape sequences.

To check whether key bytes are reaching the remote shell, run:

cat -v

Then press Up. A working Windows client should print something like ^[[A. Press Ctrl-C to leave cat.

The picker shows (unknown) in WHAT

The WHAT column is best-effort. It depends on the remote OS and process table details. Failure to detect the foreground command does not affect session correctness.

ssh-obi deliberately avoids wrapping or instrumenting your shell to improve this field.

Output repeats after reconnect

This is expected. Reattach replays the session’s current bounded buffer before live forwarding resumes. ssh-obi does not maintain a per-client replay cursor.

Old output is missing after reconnect

The session replay buffer is bounded. Old output belongs in the local terminal scrollback.

Typing feels softer than plain SSH

ssh-obi is designed to stay close to plain SSH, but the interactive path is not byte-for-byte identical to an OpenSSH client attached directly to a remote PTY.

Input travels through the local ssh-obi client, the system ssh process, the remote broker, a Unix-domain socket, and the per-session daemon before it reaches the remote PTY. Output returns through the same layers in reverse. Each layer flushes promptly, and there is no intentional long sleep in the hot path.

On Unix clients, ssh-obi does intentionally coalesce immediately pending stdin bytes for a very short window before framing and sending them. This keeps paste and bursty input from becoming one protocol frame per byte, but it can add up to about 2 ms before an isolated keystroke is sent. That small batching window, plus normal SSH, network, scheduler, and terminal latency, can feel a little softer than direct SSH on very low-latency links.

This behavior is a latency/throughput tradeoff, not a correctness issue. It does not change the bytes the remote shell receives, introduce in-band escape commands, or change reconnect/replay semantics.

Reconnect eventually gives up

Automatic reconnect uses capped exponential backoff: 125ms, 250ms, 500ms, 1s, then 2s for later attempts. After 10 failed reconnect attempts, ssh-obi reports failure instead of retrying forever.

A session is busy

A session can have only one attached client. If another client is already attached, a second attach attempt gets SessionBusy.

During automatic reconnect, ssh-obi treats SessionBusy specially only for the exact session it is trying to recover: it asks the stale attached client to detach and then retries. Manual attaches still leave the busy session alone.

Use:

ssh-obi --list user@example.com

to inspect sessions, or:

ssh-obi --detach --session ID user@example.com

to ask the session to detach the attached client for a known session.

MOTD is shown when a session starts

New sessions print the remote host MOTD before the shell starts. This includes readable non-empty /run/motd.dynamic and /etc/motd files, plus readable non-empty files in /etc/motd.d/.

To suppress this output for the remote account, create:

touch ~/.hushlogin

A deliberate detach reconnects unexpectedly

Use the in-session helper:

ssh-obi-server --detach

This asks the remote session to detach and causes the client to exit gracefully. Simply closing the terminal, killing SSH, or losing the network is ambiguous, so the client reconnects.

PowerShell bootstrap cannot run

Use a command that bypasses the current process policy:

powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://obi.menhera.org/bootstrap.ps1 | iex"

If that is blocked by local policy, download the script and inspect it before running it in an allowed shell.

Changelog

v0.1.3

Released for the 0.1.x protocol line.

Added

  • New sessions print the remote host MOTD before the shell starts, using /run/motd.dynamic, /etc/motd, and readable non-empty files under /etc/motd.d/. A user ~/.hushlogin suppresses this MOTD output.

Changed

  • Automatic reconnect retries now use a small capped exponential backoff: 125ms, 250ms, 500ms, 1s, then 2s, with a maximum of 10 attempts.

Notes

  • The protocol baseline remains 0.1.

v0.1.2

Released for the 0.1.x protocol line.

Added

  • On Linux systems booted with systemd and a user bus, newly spawned PTY children are moved into a transient user scope before exec. This follows the same systemd StartTransientUnit shape used by tmux >= 3.2 for cgroup detaching, and falls back cleanly when systemd is unavailable.
  • During automatic reconnect, if the target session still reports SessionBusy, the client asks the stale attached client to detach and then retries the reconnect.
  • The Unix bootstrap reports an explicit OpenBSD message when no compatible server is already installed. OpenBSD has no prebuilt release tarballs; install a Rust toolchain and run cargo install --features server-bin ssh-obi.
  • ssh-obi-server --list lists currently alive sessions for the remote user on the server host, including busy sessions and a marker for the current session when run inside an ssh-obi session.

Fixed

  • OpenBSD install-only bootstrap runs now complete successfully when a compatible ssh-obi-server already exists at ~/.cargo/bin/ssh-obi-server or on PATH.

Notes

  • The protocol baseline remains 0.1.
  • Non-systemd Linux systems and non-Linux Unix systems do not require any systemd tooling.

v0.1.1

Released for the 0.1.x protocol line.

Added

  • The client sends an initial terminal window size before attach and new-session requests when the remote server supports initial-window-size.v1.
  • New sessions create the remote PTY with the local terminal size when that size is available.
  • Reattaches apply the local terminal size before replaying buffered output.
  • The Unix bootstrap detects compatible ssh-obi-server binaries in three places before trying a tarball install: ~/.ssh-obi/bin/ssh-obi-server, ~/.cargo/bin/ssh-obi-server, and ssh-obi-server found on PATH.

Notes

  • The protocol baseline remains 0.1; initial-window-size.v1 is negotiated as a capability.
  • Platforms without a published release tarball can still be used as remote servers when a compatible ssh-obi-server is installed by Cargo or a distro package.

v0.1.0

Initial public release.

Added

  • Local ssh-obi client and Unix remote ssh-obi-server.
  • Per-user long-lived remote sessions.
  • Attach, new-session, list, and detach commands.
  • Reconnect to a known session after ambiguous disconnects.
  • Bounded replay of recent output on reattach.
  • Shell exit status forwarding.
  • Unix bootstrap install flow and Windows client-only bootstrap flow.

Developer Docs

This chapter is for contributors and release maintainers. The rest of this book is intentionally user-facing.

Repository Commands

Build the client:

cargo build --release

Build both client and server:

cargo build --release --features server-bin --bins

Run tests:

cargo test --features server-bin

Run clippy:

cargo clippy --all-targets --features server-bin -- -D warnings

Format:

cargo fmt --all

Documentation Site

The site is generated from docs-src/ with mdBook and published from docs/.

Use:

./build-docs.sh

Do not run mdbook build directly for published updates. mdBook overwrites docs/, so build-docs.sh copies every non-mdBook artifact needed by GitHub Pages after each build:

  • bootstrap.sh
  • bootstrap.bat
  • bootstrap.ps1
  • .nojekyll
  • release tarballs found in known release output locations

Release Builds

The current release is v0.1.3. It is published to crates.io and tagged on GitHub as v0.1.3. Release tarballs for the bootstrap installers are served from https://obi.menhera.org/.

Use:

./build-release.sh

Default build routing:

  • cross-rs: x86_64/aarch64 Linux musl, FreeBSD, illumos, NetBSD.
  • cargo-zigbuild plus zig: Darwin, riscv64 Linux musl, powerpc64le Linux musl.
  • cargo +nightly zigbuild -Z build-std=std,panic_abort: s390x Linux musl.
  • cargo-xwin: Windows x86_64 MSVC client.

Each target gets its own Cargo target directory under target/release-build/. This prevents host-side Cargo build-script executables from being reused across build environments with different libc baselines.

NetBSD uses a tiny target-only libexecinfo fallback because the cross-rs NetBSD image currently lacks that target library.

Windows uses the MSVC target because it produces a smaller and more compatible client binary than the GNU target in this release flow.

Useful environment variables:

  • CROSS: cross-rs command, default cross.
  • CARGO_ZIGBUILD: cargo-zigbuild command, default cargo-zigbuild.
  • CARGO_XWIN: cargo-xwin command, default cargo-xwin.
  • OUT_DIR: output directory for tarballs, default current directory.
  • RELEASE_TARGET_ROOT: Cargo target root, default target/release-build.
  • CROSS_SERVER_TARGETS: override cross-rs server target list.
  • ZIGBUILD_SERVER_TARGETS: override zigbuild server target list.
  • CLIENT_ONLY_TARGETS: override client-only target list.
  • ZIGBUILD_BUILD_STD_TOOLCHAIN: nightly toolchain for build-std targets, default nightly.
  • LLVM_LIB: explicit llvm-lib path for MSVC C build steps.

High-Level Architecture

There are three remote process roles:

  • Daemon: long-lived process that owns one PTY and one shell.
  • Broker: short-lived process started by SSH to connect the client to a daemon.
  • Detach helper: short-lived process run inside the remote shell by ssh-obi-server --detach.

The local client starts the system ssh binary, sends the bootstrap as the remote command, waits for OBI-SERVER-READY, negotiates capabilities, selects or creates a session, and then forwards terminal bytes.

The daemon must keep reading from the PTY while detached. Stopping reads can fill the kernel PTY buffer and block remote commands that print output.

Protocol Notes

Frames use:

msg_type: u8
flags:    u8
length:   u32, big endian
payload:  length bytes of CBOR

Maximum payload length is 1 MiB. Unknown message types within the size limit are skipped silently.

Feature negotiation is capability-based. Existing capability message formats are frozen once released; breaking changes require new capability names.

Current capability names include:

  • pty.v1
  • replay.v1
  • detach.v1
  • session-list.v1
  • exit-code.v1
  • initial-window-size.v1

initial-window-size.v1 lets the client send terminal dimensions before an attach or new-session request. The broker applies the size before replay on reattach, and new daemons create their PTY with that size when possible. The protocol baseline remains 0.1.

MOTD output is local to daemon startup and does not require a wire capability. The daemon collects readable non-empty /run/motd.dynamic, /etc/motd, and sorted /etc/motd.d/* files unless ~/.hushlogin exists, then writes those bytes to the PTY child before shell exec.