rusty-autossh 0.1.0

Keep an SSH tunnel alive across drops — a Rust port of Carson Harding's `autossh 1.4g` SSH connection supervisor. Tokio-based supervisor, `-M <port>` monitor-port heartbeat (or `-M 0` exit-only respawn), `AUTOSSH_*` env-var surface incl. `AUTOSSH_MESSAGE` byte-identical wire format, Unix `-f` daemonize + Windows `DETACHED_PROCESS` analogue, SIGTERM/SIGUSR1/SIGHUP handling on Unix, byte-equal Strict-mode upstream compatibility, and a typed library API.
Documentation

rusty-autossh

crates.io docs.rs CI MSRV 1.85 License: MIT OR Apache-2.0

Keep an SSH tunnel alive across network drops — a Rust port of Carson Harding's autossh 1.4g connection supervisor.

rusty-autossh spawns ssh(1) as a child process, optionally monitors the tunnel via the upstream-compatible -M <port> heartbeat (16-byte ASCII timestamp + newline; byte-identical wire format), and respawns the ssh child on death, probe timeout, or signal-triggered force-restart. Honors the full AUTOSSH_* environment-variable surface, supports Unix daemonization (-f double-fork via the daemonize crate), and ships a Windows DETACHED_PROCESS self-respawn analogue.

Install

From crates.io

cargo install rusty-autossh

Pre-built binaries (cargo binstall)

cargo binstall rusty-autossh

Direct download

GitHub Releases ship pre-built archives for the five DDR-003 targets:

  • Linux x86_64-unknown-linux-gnu
  • Linux aarch64-unknown-linux-gnu
  • macOS x86_64-apple-darwin
  • macOS aarch64-apple-darwin
  • Windows x86_64-pc-windows-msvc

Usage

Monitor-port heartbeat (the classic upstream pattern)

rusty-autossh -M 20000 -L 8080:localhost:80 user@host

Opens TCP listeners on 127.0.0.1:20000 and 127.0.0.1:20001, sends a 16-byte ASCII timestamp + newline every AUTOSSH_POLL seconds (default 600), respawns ssh if the round-trip fails or ssh exits non-zero.

-M 0 no-monitor mode (the modern 2026 pattern)

rusty-autossh -M 0 -o "ServerAliveInterval=30" -o "ServerAliveCountMax=3" user@host

No TCP listeners; respawn ssh only on non-zero exit. Pair with ssh's own keepalives in ~/.ssh/config. Clean exit (status 0) terminates the supervisor.

Daemonize with -f + write PID + log files

AUTOSSH_PIDFILE=/tmp/auto.pid AUTOSSH_LOGFILE=/tmp/auto.log \
    rusty-autossh -f -M 0 -L 5432:db.internal:5432 jumpbox

Foreground exits cleanly; daemon child runs detached. -f unconditionally forces AUTOSSH_GATETIME=0 to match upstream (silences password prompts in detached mode; non-zero gate can cause the detached child to abort on its own quick exit).

One-shot mode

rusty-autossh -1 -M 0 user@host

Exit non-zero on first failure instead of entering the retry loop. Useful for systemd-supervised wrappers.

Flag reference (Default mode)

Short flags follow upstream exactly. Long-form Rust-native flags add ergonomic aliases:

Short Long alias Effect
-M <P[:E]> --monitor-port Monitor port (or port:echo single-listener)
-f --background Daemonize (forces AUTOSSH_GATETIME=0)
-1 --one-shot Exit non-zero on first failure
-V --version Print version
--poll <S> Override AUTOSSH_POLL (poll interval seconds)
--first-poll <S> Override AUTOSSH_FIRST_POLL
--gate-time <S> Override AUTOSSH_GATETIME
--max-start <N> Override AUTOSSH_MAXSTART (-1 = unlimited)
--max-lifetime <S> Override AUTOSSH_MAXLIFETIME
--ssh-path <P> Override AUTOSSH_PATH
--pid-file <P> Override AUTOSSH_PIDFILE
--log-file <P> Override AUTOSSH_LOGFILE
--debug Enable debug-level tracing
--log-level <L> Set log level (trace/debug/info/...)
--strict Force Strict-mode upstream-compat
--no-strict Force Default mode (overrides env + argv[0])

All unrecognized post-rusty-autossh tokens (or anything after --) are passed through to ssh verbatim.

Library API

rusty-autossh ships a typed library API. Add to your Cargo.toml:

[dependencies]
rusty-autossh = { version = "0.1", default-features = false }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
use rusty_autossh::{SshSupervisorBuilder, MonitorMode};

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), rusty_autossh::AutosshError> {
    let mut supervisor = SshSupervisorBuilder::new()
        .ssh_args(vec!["-L".into(), "8080:localhost:80".into(), "user@host".into()])
        .monitor_mode(MonitorMode::Active { port: 20000, echo: None })
        .build()?;
    supervisor.run().await
}

Cargo Features

The CLI-only dependencies (clap, clap_complete, anstyle, tracing-*, daemonize, atomicwrites, windows-sys) are gated behind a default-on cli feature. Library consumers depending with default-features = false get only the tokio-based supervisor core + thiserror + socket2. The dep-tree discipline is enforced by an integration test (tests/library_api.rs::default_features_off_excludes_cli_deps).

Compatibility statement

rusty-autossh aims for byte-equal stderr against upstream autossh 1.4g under Strict mode (activated by --strict, RUSTY_AUTOSSH_STRICT=1, or argv[0]=autossh via symlink). Default mode adds Rust-native long-form flags, structured tracing logs, and clap-styled diagnostics.

Intentional divergences vs upstream

  • Windows -f uses CreateProcessW(DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP) self-respawn instead of fork (Windows has no fork). The foreground process closes monitor-port listeners before exit; the detached child re-binds.
  • AUTOSSH_NTSERVICE (Cygwin NT-service mode) is NOT implemented. Use sc.exe create or NSSM against the foreground rusty-autossh if you want a full Windows Service.
  • SIGUSR1 / SIGHUP have no Windows equivalents. Documented workaround: taskkill /PID <ssh-pid> triggers respawn via the SIGCHLD-equivalent code path.
  • Windows ssh-child termination uses TerminateProcess (immediate) with NO 10-second SIGTERM grace window. Unix retains SIGTERM + 10s + SIGKILL.
  • No built-in alerting (Slack/email/PagerDuty) — pipe structured tracing logs to your own alerting stack.
  • No mosh-style roaming — autossh's design respawns a fresh ssh on tunnel death, dropping in-flight bytes.
  • ssh binary is delegated — rusty-autossh spawns the system ssh; it does not implement the SSH protocol. Crates like russh/ssh2/thrussh cover that niche.

See COMPATIBILITY.md for the per-flag matrix.

Lockstep SemVer

The CLI binary and the library API ship from a single crate with lockstep SemVer: any breaking change to either surface (CLI flag removal, library type change) bumps the major version together. Additive variants on AutosshError and SupervisorEvent are MINOR via #[non_exhaustive].

AUTOSSH_* environment-variable reference

Variable Default Effect
AUTOSSH_POLL 600 Probe interval seconds
AUTOSSH_FIRST_POLL AUTOSSH_POLL Initial probe delay
AUTOSSH_GATETIME 30 Min lifetime before retry counts as failure
AUTOSSH_MAXSTART -1 Max retries (-1 = unlimited)
AUTOSSH_MAXLIFETIME 0 Self-terminate after N seconds (0 = unlimited)
AUTOSSH_DEBUG unset Enable debug logging
AUTOSSH_LOGFILE unset Append diagnostics to this file
AUTOSSH_LOGLEVEL info Log level
AUTOSSH_PIDFILE unset Write supervisor PID atomically
AUTOSSH_PATH unset Override ssh binary path (verbatim, no PATH fallback)
AUTOSSH_PORT unset Override -M <port> value
AUTOSSH_MESSAGE unset Append to heartbeat payload (single space separator)
RUSTY_AUTOSSH_STRICT unset =1 activates Strict mode (overridden by --no-strict)

License

Dual-licensed under either:

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.

Minimum Supported Rust Version (MSRV)

Rust 1.85 (edition 2024). Pinned via rust-toolchain.toml. The portfolio MSRV policy is current stable minus two minor releases at each port's release time; this is re-verified per release per portfolio §VI of project-instructions.md.