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

The crate version is v0.3.1. It adds server-local targeted detach, so a user can list sessions and detach a selected non-current session. The 0.1 wire-protocol baseline remains compatible.

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 attached client from a listed session on that server:

ssh-obi-server --detach --session aaaaaaaa

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

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

Detach an existing client and connect to that session yourself:

ssh-obi --detach-connect user@example.com

With exactly one session it is selected automatically. With several sessions, the client shows a selector. Add --session aaaaaaaa to choose explicitly.

Print the installed crate version:

ssh-obi --obi-version

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.
  • Keepalive works automatically with v0.3.0 or newer peers; no SSH keepalive options are required.
  • 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, sufficiently recent server is already installed at ~/.ssh-obi/bin, at ~/.cargo/bin, or on the remote PATH, ssh-obi uses it. The bootstrap checks protocol compatibility and compares the server’s --obi-version output with the local client’s compiled version. A missing version option or an older server enters the install path. If 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.

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.

To take over an existing session, use ssh-obi --detach-connect user@host. The only session is selected automatically; multiple sessions produce a picker that includes both free and busy sessions. --session ID overrides selection.

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.

To detach another session on the same server account, first run ssh-obi-server --list, then use its ID:

ssh-obi-server --detach --session ID

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 continue indefinitely through temporary SSH, DNS, and routing failures. Delays are 1s, 2s, 4s, 8s, 16s, 32s, 64s, then at most 120s. The deadline uses suspend-aware monotonic machine time, so wall-clock changes do not affect it and an overdue attempt continues promptly after laptop resume.

While recovering, the client restores normal terminal mode and pauses its stdin reader. OpenSSH can therefore ask for key passphrases, passwords, host-key confirmation, and proxy authentication on subsequent connections.

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

On Unix-like systems this installs both ssh-obi and ssh-obi-server; the server-bin feature is enabled by default. A client-only source build can use --no-default-features --bin ssh-obi. For normal use, the client can still bootstrap or update the remote server component when you connect.

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 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, lacks --obi-version, reports an older package version than the local client, or has an incompatible protocol, 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 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 aaaaaaaa 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 listed session on the server host:

ssh-obi-server --detach --session aaaaaaaa

Detach the attached client for a specific session and exit:

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

Detach an existing client and connect to that session:

ssh-obi --detach-connect user@example.com

The only session is selected automatically. If several sessions exist, a selector includes both busy and free sessions. --session aaaaaaaa overrides selection.

Print the client package version and exit:

ssh-obi --obi-version

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. Use ssh-obi-server --detach --session ID to detach the attached client from any listed session, including a non-current session. This does not terminate the remote shell.

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 continue indefinitely through temporary SSH transport, DNS, and routing failures. Delays are 1s, 2s, 4s, 8s, 16s, 32s, 64s, then at most 120s. Suspend-aware monotonic machine time controls the deadline, so changing the wall clock has no effect and overdue attempts continue promptly after laptop resume. During recovery the client pauses its stdin reader and restores normal terminal mode, allowing OpenSSH prompts to interact with the user.

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. To detach the attached client from a listed session, including a non-current one, run:

ssh-obi-server --detach --session ID

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.

For an explicit takeover, use:

ssh-obi --detach-connect user@host

The only existing session is automatic; several sessions produce a selector. --session ID chooses the target directly. A transient busy result while the old broker clears is handled as recovery rather than a fatal attach error.

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.

From another shell on the same server account, ssh-obi-server --detach --session ID sends the same graceful detach request to the named session. The session ID can be obtained with ssh-obi-server --list.

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 continue indefinitely through temporary SSH, DNS, and routing failures, using delays of 1s, 2s, 4s, 8s, 16s, 32s, 64s, then at most 120s. Deadlines count suspend-aware monotonic machine time rather than wall time. Normal terminal mode is restored during recovery so OpenSSH can interactively request credentials or confirmation.

With v0.3.0 peers, keepalive.v1 is negotiated automatically. The client sends a heartbeat every 15 seconds and reconnects if the broker does not answer within 45 seconds. The broker also releases the session if client heartbeats stop for 45 seconds. No SSH keepalive options are required. Older peers do not negotiate this behavior.

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 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 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 keeps retrying

Automatic reconnect deliberately continues through temporary SSH transport, DNS, and routing failures. Its capped exponential delays are 1s, 2s, 4s, 8s, 16s, 32s, 64s, then 120s for later attempts. Suspend-aware monotonic machine time controls each deadline, so an overdue attempt proceeds promptly after a sleeping laptop resumes.

During recovery, ssh-obi pauses its stdin reader and restores the terminal’s normal mode. Passphrase, password, host-key, and proxy prompts from OpenSSH can therefore interact normally. Interrupt the client if you no longer want it to recover the session.

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.

To detach and immediately connect yourself, use:

ssh-obi --detach-connect user@example.com

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.3.1

The wire-protocol baseline remains 0.1.

Added

  • ssh-obi-server --detach --session ID detaches the attached client from a selected session on the same server host. Use ssh-obi-server --list to find session IDs, including non-current sessions.

Notes

  • Bare ssh-obi-server --detach retains its existing behavior and targets the session identified by the remote shell environment.

v0.3.0

The wire-protocol baseline remains 0.1.

Added

  • Negotiated keepalive.v1 heartbeats work automatically for attached sessions; users do not need to configure unfamiliar SSH options.
  • The client sends a ping every 15 seconds and requires the matching pong within 45 seconds. An unresponsive connection enters the existing reconnect flow.
  • A broker that negotiated keepalive releases its daemon attachment when no client ping arrives for 45 seconds, making the session available for reconnect.

Notes

  • Older clients and brokers do not negotiate keepalive.v1 and retain the previous connection behavior.

v0.2.1

The wire-protocol baseline remains 0.1.

Added

  • --detach-connect selects and takes over an existing session. --session ID overrides automatic selection.
  • --obi-version reports the package version from the client and server.
  • Remote bootstrap compares the server package version with the local client’s compiled version. Missing support or an older server enters the install path.

Changed

  • Reconnection retries temporary SSH, DNS, and routing failures indefinitely, with capped exponential delays from 1s through 120s.
  • Retry deadlines use suspend-aware monotonic machine time, and the terminal is restored for interactive OpenSSH prompts during recovery.
  • server-bin is enabled by default, so cargo install ssh-obi installs the remote server on OpenBSD without extra feature flags.

Fixed

  • Login shells inherit the launching process’s umask rather than being forced to umask 0000. Daemon sockets retain explicit owner-only permissions inside their private per-user directory.

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 --no-default-features --bin ssh-obi

Build both client and server:

cargo build --release --bins

Run tests:

cargo test --all-targets

Run clippy:

cargo clippy --all-targets -- -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 crate version is v0.3.1. The wire-protocol baseline remains 0.1. 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 control process run as ssh-obi-server --detach. It targets the current shell through its environment or a same-user session socket through --session ID.

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
  • keepalive.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.

keepalive.v1 adds Ping and Pong frames carrying an echoed nonce. The client sends a ping every 15 seconds and treats a missing pong after 45 seconds as an ambiguous disconnect. The broker answers pings itself and independently closes its daemon connection if negotiated client pings stop for 45 seconds. This keeps heartbeat behavior independent of the version of a persistent session daemon.

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.