113 lines
3.3 KiB
Rust
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())
|
|
}
|