pub async fn run() -> Result<()>Expand description
Runs ssh-cli from the command-line arguments.
One-shot lifecycle (Rules Rust CLI one-shot — six phases):
- Init — SIGINT/SIGTERM handlers, bootstrap tracing (stderr, error-only).
- Platform (UTF-8 / TTY).
- Parse — clap derive.
- Validate / configure — reload log filter from
-vonly (ambientRUST_LOGignored), terminal, i18n. - Execute — subcommand dispatch (bounded SSH timeouts + cancel flags).
- Finalize / exit — handled by
main(flush + runtime shutdown + sysexits).
§Workload classification (resource economy + performance)
I/O-bound one-shot CLI (SSH/TCP, optional disk SCP). Not CPU-bound:
no Rayon on product paths (crypto/IO already on Tokio). Multi-host
fan-out is the default modus operandi for SSH ops that accept --all
(health-check, exec, sudo-exec, su-exec, scp) via
concurrency::map_bounded (Semaphore + JoinSet, cap from
--max-concurrency / auto CPU×RAM formula; no env-as-store).
Tunnel forwards use the same admission gate. Local TOML CRUD / locale /
completions stay sequential (work ≪ coordination). Heavy-memory
singletons use OnceLock / atomics (signals, locale, logs).
§Ownership / borrowing policy (Rules Rust)
- Prefer the least permission:
&T→&mut T→T(consume) only when needed. - Config override paths: APIs take
Option<&Path>(resolve_config_path,find_by_name,winning_layer,read_active_vps) so one CLIPathBufis shared withoutcloneat every hop. - Local
ConfigFilemaps:removetheVpsRecordfor one-shot exec/sudo/su/health when the file is discarded after load (move, not clone). - Errors that own stderr: move
output.stderrintoSshCliError::CommandFailed. - Secrets:
Option::take/SecretStringmove; never clone passwords for convenience. - Shared SSH client in tunnel:
Arc<dyn SshClientTrait>+Arc::clone(refcount only). - No
Rc/RefCell/Arc<RefCell<_>>/static mutin product code. - Lifetimes: elision preferred; no
'staticescapes for non-global data. unsafeonly at OS boundaries (console, signals env tests) with// SAFETY:.
§Interior mutability policy (Rules Rust)
- Prefer no interior mutability: reorganize ownership first.
- Process flags:
static AtomicBoolwith documentedOrdering(signals, quiet/json). - One-shot init:
OnceLock(locale, color, log reload handle) — notlazy_static. - Composite process state: single
std::sync::Mutex+ poison recovery with log (secrets); never hold across.await. - No
RefCell/Rc/Arc<RefCell<_>>/static mutin product code. - Tunnel deadline flag:
Arc<AtomicBool>only where two tasks must share a bit.
§Graceful shutdown policy (Rules Rust — one-shot minimum)
- Detect SIGINT/SIGTERM early; long ops poll
signals::should_stop. - Tunnel stops accepts, drains/aborts tracked forwards, then disconnects.
- Flush stdio; shut down Tokio runtime; exit 130 / 141 / 143.
- Not a daemon: no readiness probes, SIGHUP reload, or
TaskTrackertree.
§JSON wire policy (Rules Rust — JSON / NDJSON)
- Agent contracts are classic single-root JSON (object or array), RFC 8259 — not NDJSON/JSONL streams. One document per invocation on the data path.
- Emit compact UTF-8 (
serde_json::to_string) + trailing LF; no pretty-print, no BOM, no JSON5 on the machine wire. - Known payloads use typed DTOs in
json_wire;serde_json::Valueonly at dynamic edges (meta command-tree, flexible success-field maps). - Import of
vps export --jsonstrips BOM, caps size, Must-Ignore unknown fields. - Hand-versioned schemas live under
docs/schemas/; no runtime schema engine. - On-disk host registry remains TOML (not JSON config).
§Performance policy (Rules Rust)
- Measure before micro-optimizing; prefer algorithmic / allocation caps
(see
ssh::clientcapture byte cap) over#[inline(always)]guesses. - Publish default is size-min release (
opt-level = "z"+ fat LTO); local speed A/B uses--profile release-fast/release-lto(opt-level = 3). - Criterion covers local mask/paths only — not SSH flamegraphs.
§Multiplatform policy (Rules Rust — sistemas operacionais)
- Boot: Windows console UTF-8 (65001) +
ENABLE_VIRTUAL_TERMINAL_PROCESSING; Linux sandbox warn (Flatpak/Snap); runtime classify WSL/container/CI/Termux. - Paths:
PathBufonly; Windows reserved names; component ≤255; MAX_PATH guard without\\?\; Unicode NFC normalization for comparisons. - Permissions: Unix
0o600behind#[cfg(unix)]only (no ACL leakage). - Config home:
directories::ProjectDirs+ optional--config-dir(noSSH_CLI_HOMEstore). - Completions: clap_complete shells (Bash, Elvish, Fish, PowerShell, Zsh).
- Out of scope: browser discovery, WASM/WASI, Job Objects, seccomp default, macOS notarization inside the binary (release process — see CROSS_PLATFORM).
§i18n policy (Rules Rust — multi-idioma / locale do SO)
- Boot order: platform console + runtime detect → TTY/color → locale → rest.
- Detection: single
sys_locale::get_localecall; never portable rawLANG. - Parse / negotiate:
unic-langid+fluent-langnegagainsti18n::Language::AVAILABLE. - State: one immutable
std::sync::OnceLocklanguage per process (no mid-session mix). - Overrides:
--lang> XDGlangfile (locale set) > system >en(SSH_CLI_LANGnot a store). - UI copy: human success/status/cancel via
i18n::Message; agent JSON +errors::SshCliErrorDisplay stay stable English (pipe/agent contract). - MVP:
en+pt-BRonly; optional locales behindi18n-*features (stubs). - Out of scope for default binary: full Fluent FTL runtime, ICU calendars/collators, pseudolocalization, RTL shaping (reserved features; size-sensitive one-shot).
§Parallelism / multiprocessing policy (Rules Rust — paralelismo)
- Modus operandi: bounded concurrent I/O on every multi-target SSH surface
(
--allor--hosts a,b); sequential only when work is local/tiny (documented at each call site — G-PAR-28). - Session reuse (G-PAR-47): multi-file SCP on one host uses one SSH
session and serial transfers (auth once). Multi-host × multi-file (G-PAR-48)
bounds sessions via
map_bounded, reusing the session for all files. - TOFU (G-PAR-49):
known_hostsmutations take exclusive flock + reload-merge. - Selection:
vps::HostSelection+vps::resolve_host_jobsis the single path that builds fan-out jobs (G-PAR-31). Batch JSON when selection isAll/Namedeven if one name (G-PAR-36). - Gate:
tokio::sync::Semaphoreinconcurrency;acquire_owned+ RAII permit drop;JoinSetfor dynamic fan-out; never unboundedspawnloops. - Budget:
min(cpus×4, free_ram×50%/16MiB)clamped1..=64; override--max-concurrency(auto formula pre-parse; no env store). - Runtime: multi_thread workers from
concurrency::worker_threads;max_blocking_threadscapped; no nested runtimes; no Rayon. - Tunnel: one local bind + one SSH session per one-shot (G-PAR-30); multi-host tunnels = N invocations. Accepts still use JoinSet + Semaphore.
- N/A for this product: loom lock models, parking_lot deadlock detector,
systemd-run MemoryMax child scopes, OTEL available_permits metrics, hierarchical
CancellationTokentrees (one-shot uses atomic signal flags).
§Latency policy (Rules Rust — redução de latência)
- Identity: one-shot I/O-bound agent CLI. End-to-end latency is dominated by SSH/TCP RTT, not CPU nanoseconds. HFT budgets (P9999 ns, isolcpus, mlockall, huge pages, kernel bypass, PGO/BOLT pipelines) are out of scope.
- What we optimize: cold-start (capped Tokio workers), multi-host wall-clock via bounded fan-out, zero extra copies on exec capture happy path, non-blocking disk I/O on the async runtime (SCP), bounded capture RAM, cooperative cancel.
- What we do not claim: process-level P50/P99 histograms per release, HDR export, or coordinated-omission load tests — there is no long-lived server.
- Build: fat LTO +
codegen-units = 1+panic = aborton release;opt-level = "z"for publish footprint;release-fast/release-ltofor local CPU A/B. Notarget-cpu=nativeon published artifacts. - Allocator: system default on glibc; optional
mimallocviamusl-allocator(measure before making default).
§Logging / tracing policy (Rules Rust — logs com tracing e rotação)
- Facade:
tracingonly. Product code never usesprintln!/dbg!for diagnostics; agent data is emitted only viaoutput. - Install once:
telemetry::bootstrap_logsbefore clap parse, thentelemetry::initialize_logsreloadsEnvFilter(reload::Layer). - Sink: stderr text with targets + thread names; default filter
error. - Bridge:
tracing-logsorussh/keyringlogrecords appear under the same filter. - Not installed: OpenTelemetry, file rotation (
tracing-appender), admin log-level HTTP,tokio-console— out of product identity (one-shot agent CLI, zero telemetry, stdout = data).
§Macro policy (Rules Rust — macros)
- No product
macro_rules!/ proc-macro crates in this workspace. Prefer generics, traits, functions, andconstbefore inventing syntax. A thin rename macro over a function is an antipattern. - External derives only when justified:
clap/serde/thiserror(proc_macro_derive) generate type-driven boilerplate that functions cannot express; no hand-rolled derive crate. - Built-in std macros, idiomatically:
format!when an ownedStringis required (i18n, error payloads).format_args!+output::write_line_fmt/output::write_stderr_fmt/writeln!for stream emission — neverwrite_*(&format!(…)).matches!for boolean pattern checks;env!/concat!for version wire (clilong version);include_str!only in tests that audit source.
- Forbidden in product paths:
todo!,unimplemented!,dbg!, andpanic!for recoverable errors (tests maypanic!on fixture mismatch). - Not applicable: custom declarative/proc macro hygiene,
trybuildUI suites,$crateexport crates — there is no macro surface to publish.
§Stream architecture (G-IO-11)
- Binary path:
runparsesstd::env::argsand uses process stdio. - Library path:
run_with_argsaccepts a pre-parsedcli::CliArgs. - DI write primitives:
output::write_line_to,output::write_stderr_line_to,json_wire::write_json_line— passCursor/Vecin tests. - Exit mapping:
resolve_exit_codekeepsmainthin (flush + runtime shutdown +process::exitonly).