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.shbootstrap.batbootstrap.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-zigbuildpluszig: 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, defaultcross.CARGO_ZIGBUILD: cargo-zigbuild command, defaultcargo-zigbuild.CARGO_XWIN: cargo-xwin command, defaultcargo-xwin.OUT_DIR: output directory for tarballs, default current directory.RELEASE_TARGET_ROOT: Cargo target root, defaulttarget/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, defaultnightly.LLVM_LIB: explicitllvm-libpath 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.v1replay.v1detach.v1session-list.v1exit-code.v1initial-window-size.v1keepalive.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.