(initial) Align formatting and code style to project standards. Add live_test.sh: full automated integration suite against a real SSH server Show [name] for optional positionals, allow filtering import by name too Fix transfer rough edges: value-name hint, identity warnings, single-profile export Add transfer subcommand for bulk profile export/import Harden forced ssh flags: accept-new host keys, block multiplexing, tighten identity auth release build opts Standardize error messages, formatting, and code style. Refine --help text for consistency. Update comments for clarity and tone alignment. Normalize user-facing --help text for tone/format consistency Show real mapping grammar in --help, make --via repeatable, trim comments Match vmic's custom top-level --help renderer Implement porthole v0.1: profiles, supervised open/close, reconnect Fix --via/-J grammar and --force process-group kill added .gitignore
50 lines
1.7 KiB
Rust
50 lines
1.7 KiB
Rust
use crate::cli::CloseArgs;
|
|
use crate::error::Result;
|
|
use crate::{instance, profile, ui};
|
|
use std::time::{Duration, Instant};
|
|
|
|
const GRACEFUL_WAIT: Duration = Duration::from_secs(5);
|
|
|
|
pub fn run(args: CloseArgs) -> Result<()> {
|
|
let name = profile::normalize(&args.name);
|
|
profile::load(&name)?; // validate the profile itself exists
|
|
|
|
if close_instance(&name, args.force)? {
|
|
ui::ok(&format!("Closed '{name}'."));
|
|
} else {
|
|
ui::info(&format!("'{name}' is not open."));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Stops `name`'s supervisor if one is actually running, and clears any
|
|
/// stale instance file either way, shared with `remove` and `wipe`.
|
|
/// Returns whether anything was actually running.
|
|
pub fn close_instance(name: &str, force: bool) -> Result<bool> {
|
|
let Some(pid) = instance::running_pid(name)? else {
|
|
instance::delete(name)?; // clears a stale file left by a crash
|
|
return Ok(false);
|
|
};
|
|
|
|
if force {
|
|
// SIGKILL the whole process group (the supervisor is its own
|
|
// group leader via setsid), not just the supervisor pid; a plain
|
|
// single-pid SIGKILL would leave `ssh` orphaned.
|
|
unsafe { libc::kill(-pid, libc::SIGKILL) };
|
|
} else {
|
|
unsafe { libc::kill(pid, libc::SIGTERM) };
|
|
let deadline = Instant::now() + GRACEFUL_WAIT;
|
|
while instance::process_alive(pid) && Instant::now() < deadline {
|
|
std::thread::sleep(Duration::from_millis(100));
|
|
}
|
|
if instance::process_alive(pid) {
|
|
unsafe { libc::kill(-pid, libc::SIGKILL) };
|
|
}
|
|
}
|
|
|
|
// The supervisor removes its own instance file on a clean SIGTERM
|
|
// shutdown; this covers the force-killed case where it never got to.
|
|
instance::delete(name)?;
|
|
Ok(true)
|
|
}
|