pub mod app_config;
pub mod snippets;
pub mod ssh_config;
use anyhow::Context;
use serde::{Deserialize, Serialize};
use crate::ssh::client::{Host, HostSource};
use crate::utils::platform;
#[derive(Debug, Default, Serialize, Deserialize)]
struct HostsFile {
#[serde(default)]
hosts: Vec<Host>,
}
pub fn load_hosts() -> anyhow::Result<Vec<Host>> {
let path = platform::hosts_config_path().context("Cannot determine hosts config path")?;
if !path.exists() {
return Ok(Vec::new());
}
let content = std::fs::read_to_string(&path)
.with_context(|| format!("Failed to read {}", path.display()))?;
let file: HostsFile =
toml::from_str(&content).with_context(|| format!("Failed to parse {}", path.display()))?;
Ok(file.hosts)
}
pub fn save_hosts(hosts: &[Host]) -> anyhow::Result<()> {
let dir = platform::app_config_dir().context("Cannot determine app config directory")?;
std::fs::create_dir_all(&dir)
.with_context(|| format!("Failed to create directory {}", dir.display()))?;
let path = dir.join("hosts.toml");
let manual: Vec<Host> = hosts
.iter()
.filter(|h| h.source == HostSource::Manual)
.cloned()
.collect();
let file = HostsFile { hosts: manual };
let content = toml::to_string_pretty(&file).context("Failed to serialise hosts")?;
let tmp_path = path.with_extension("toml.tmp");
std::fs::write(&tmp_path, content)
.with_context(|| format!("Failed to write {}", tmp_path.display()))?;
std::fs::rename(&tmp_path, &path).with_context(|| {
format!(
"Failed to rename {} to {}",
tmp_path.display(),
path.display()
)
})?;
Ok(())
}
pub fn load_all_hosts() -> anyhow::Result<Vec<Host>> {
let manual = load_hosts()?;
let mut ssh_hosts: Vec<Host> = Vec::new();
if let Some(ssh_path) = platform::ssh_config_path() {
if ssh_path.exists() {
match ssh_config::load_from_file(&ssh_path) {
Ok(h) => ssh_hosts = h,
Err(e) => tracing::warn!("SSH config parse error: {}", e),
}
}
}
let manual_names: std::collections::HashSet<String> =
manual.iter().map(|h| h.name.clone()).collect();
let renamed_ssh_hosts: std::collections::HashSet<String> = manual
.iter()
.filter_map(|h| h.original_ssh_host.clone())
.collect();
let mut all = manual;
for h in ssh_hosts {
if !manual_names.contains(&h.name) && !renamed_ssh_hosts.contains(&h.name) {
all.push(h);
}
}
Ok(all)
}