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

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.