shellm/src/data/paths.rs

113 lines
3.3 KiB
Rust

//! Path utilities — mirrors the original `~/.secpanel/` layout.
//!
//! (The on-disk directory is intentionally kept as `~/.secpanel/` for
//! backwards compatibility with the original Tcl/Tk secpanel and with
//! prior shellm versions — see `secpanel_dir()` below.)
use std::path::{Path, PathBuf};
use directories::ProjectDirs;
/// Application version (matches the package version).
pub const SP_VERSION: &str = "1.1.0";
/// Return the base shellm data directory (`~/.secpanel`).
///
/// On the original Tcl/Tk secpanel this was always `$env(HOME)/.secpanel`.
/// We keep the same path on Linux/Mac. On Windows we fall back to
/// `%APPDATA%/secpanel` via the `directories` crate, but secpanel has always
/// been a Unix-first tool so this is mostly a defensive fallback.
///
/// (The directory name is kept as `.secpanel` for backwards compatibility —
/// renaming it would orphan existing users' profiles, configs, and history.)
pub fn secpanel_dir() -> PathBuf {
if let Some(home) = dirs::home_dir() {
return home.join(".secpanel");
}
if let Some(pd) = ProjectDirs::from("", "", "secpanel") {
return pd.data_dir().to_path_buf();
}
PathBuf::from(".secpanel")
}
pub fn config_file() -> PathBuf {
secpanel_dir().join("config")
}
pub fn profiles_dir() -> PathBuf {
secpanel_dir().join("profiles")
}
pub fn profile_file(name: &str) -> PathBuf {
profiles_dir().join(format!("{name}.profile"))
}
pub fn runfiles_dir() -> PathBuf {
secpanel_dir().join(".runfiles")
}
pub fn trace_log() -> PathBuf {
runfiles_dir().join("trace.log")
}
pub fn history_file() -> PathBuf {
secpanel_dir().join("history")
}
pub fn init_marker() -> PathBuf {
secpanel_dir().join(".init")
}
pub fn ssh_dir() -> PathBuf {
dirs::home_dir()
.map(|h| h.join(".ssh"))
.unwrap_or_else(|| PathBuf::from(".ssh"))
}
pub fn known_hosts_file() -> PathBuf {
ssh_dir().join("known_hosts")
}
pub fn reports_dir() -> PathBuf {
secpanel_dir().join("reports")
}
/// Ensure that the standard shellm directory tree exists with 0700 perms
/// on the root. Returns the path to the shellm dir on success.
pub fn ensure_secpanel_tree() -> std::io::Result<PathBuf> {
let root = secpanel_dir();
if !root.exists() {
std::fs::create_dir_all(&root)?;
// Best-effort chmod 700. On non-Unix this is a no-op.
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o700))?;
}
log::info!("Created shellm dir {} (chmod 700)", root.display());
}
std::fs::create_dir_all(profiles_dir())?;
std::fs::create_dir_all(runfiles_dir())?;
Ok(root)
}
/// Best-effort lookup of an executable on `PATH`.
pub fn which(prog: &str) -> Option<PathBuf> {
if Path::new(prog).is_absolute() {
return if Path::new(prog).exists() {
Some(PathBuf::from(prog))
} else {
None
};
}
std::env::var_os("PATH").and_then(|paths| {
std::env::split_paths(&paths)
.map(|p| p.join(prog))
.find(|p| p.is_file())
})
}
/// Like `which` but returns the input unchanged if not found.
pub fn which_or(prog: &str, fallback: &str) -> String {
which(prog).map(|p| p.to_string_lossy().into_owned()).unwrap_or_else(|| fallback.to_string())
}