Skip to main content

run

Function run 

Source
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):

  1. Init — SIGINT/SIGTERM handlers, bootstrap tracing (stderr, error-only).
  2. Platform (UTF-8 / TTY).
  3. Parse — clap derive.
  4. Validate / configure — reload log filter from -v only (ambient RUST_LOG ignored), terminal, i18n.
  5. Execute — subcommand dispatch (bounded SSH timeouts + cancel flags).
  6. 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 TT (consume) only when needed.
  • Config override paths: APIs take Option<&Path> (resolve_config_path, find_by_name, winning_layer, read_active_vps) so one CLI PathBuf is shared without clone at every hop.
  • Local ConfigFile maps: remove the VpsRecord for one-shot exec/sudo/su/health when the file is discarded after load (move, not clone).
  • Errors that own stderr: move output.stderr into SshCliError::CommandFailed.
  • Secrets: Option::take / SecretString move; never clone passwords for convenience.
  • Shared SSH client in tunnel: Arc<dyn SshClientTrait> + Arc::clone (refcount only).
  • No Rc / RefCell / Arc<RefCell<_>> / static mut in product code.
  • Lifetimes: elision preferred; no 'static escapes for non-global data.
  • unsafe only at OS boundaries (console, signals env tests) with // SAFETY:.

§Interior mutability policy (Rules Rust)

  • Prefer no interior mutability: reorganize ownership first.
  • Process flags: static AtomicBool with documented Ordering (signals, quiet/json).
  • One-shot init: OnceLock (locale, color, log reload handle) — not lazy_static.
  • Composite process state: single std::sync::Mutex + poison recovery with log (secrets); never hold across .await.
  • No RefCell / Rc / Arc<RefCell<_>> / static mut in 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 TaskTracker tree.

§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::Value only at dynamic edges (meta command-tree, flexible success-field maps).
  • Import of vps export --json strips 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::client capture 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: PathBuf only; Windows reserved names; component ≤255; MAX_PATH guard without \\?\; Unicode NFC normalization for comparisons.
  • Permissions: Unix 0o600 behind #[cfg(unix)] only (no ACL leakage).
  • Config home: directories::ProjectDirs + optional --config-dir (no SSH_CLI_HOME store).
  • 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_locale call; never portable raw LANG.
  • Parse / negotiate: unic-langid + fluent-langneg against i18n::Language::AVAILABLE.
  • State: one immutable std::sync::OnceLock language per process (no mid-session mix).
  • Overrides: --lang > XDG lang file (locale set) > system > en (SSH_CLI_LANG not a store).
  • UI copy: human success/status/cancel via i18n::Message; agent JSON + errors::SshCliError Display stay stable English (pipe/agent contract).
  • MVP: en + pt-BR only; optional locales behind i18n-* 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 (--all or --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_hosts mutations take exclusive flock + reload-merge.
  • Selection: vps::HostSelection + vps::resolve_host_jobs is the single path that builds fan-out jobs (G-PAR-31). Batch JSON when selection is All/Named even if one name (G-PAR-36).
  • Gate: tokio::sync::Semaphore in concurrency; acquire_owned + RAII permit drop; JoinSet for dynamic fan-out; never unbounded spawn loops.
  • Budget: min(cpus×4, free_ram×50%/16MiB) clamped 1..=64; override --max-concurrency (auto formula pre-parse; no env store).
  • Runtime: multi_thread workers from concurrency::worker_threads; max_blocking_threads capped; 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 CancellationToken trees (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 = abort on release; opt-level = "z" for publish footprint; release-fast/release-lto for local CPU A/B. No target-cpu=native on published artifacts.
  • Allocator: system default on glibc; optional mimalloc via musl-allocator (measure before making default).

§Logging / tracing policy (Rules Rust — logs com tracing e rotação)

  • Facade: tracing only. Product code never uses println!/dbg! for diagnostics; agent data is emitted only via output.
  • Install once: telemetry::bootstrap_logs before clap parse, then telemetry::initialize_logs reloads EnvFilter (reload::Layer).
  • Sink: stderr text with targets + thread names; default filter error.
  • Bridge: tracing-log so russh/keyring log records 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, and const before 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 owned String is required (i18n, error payloads).
    • format_args! + output::write_line_fmt / output::write_stderr_fmt / writeln! for stream emission — never write_*(&format!(…)).
    • matches! for boolean pattern checks; env!/concat! for version wire (cli long version); include_str! only in tests that audit source.
  • Forbidden in product paths: todo!, unimplemented!, dbg!, and panic! for recoverable errors (tests may panic! on fixture mismatch).
  • Not applicable: custom declarative/proc macro hygiene, trybuild UI suites, $crate export crates — there is no macro surface to publish.

§Stream architecture (G-IO-11)