Files
porthole/src/commands/close.rs
Overlord 1ee8b122f3 Squashed (migration)
(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
2026-08-14 12:48:28 +02:00

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)
}