use anyhow::Result;
use std::path::{Path, PathBuf};
pub const INCLUDE_LINE: &str = "Include ~/.ssh/config.d/omh";
pub fn host_alias(repo: &str, session: &str) -> String {
format!("omh-{repo}-{session}")
}
pub fn port(repo: &str, session: &str) -> u16 {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
repo.hash(&mut h);
session.hash(&mut h);
const LOW: u32 = 49152;
const SPAN: u32 = 65535 - LOW;
(LOW + (h.finish() % SPAN as u64) as u32) as u16
}
pub fn config_block(alias: &str, port: u16, key: &Path) -> String {
format!(
"Host {alias}\n \
HostName 127.0.0.1\n \
Port {port}\n \
User agent\n \
IdentityFile {}\n \
IdentitiesOnly yes\n \
StrictHostKeyChecking no\n \
UserKnownHostsFile /dev/null\n \
LogLevel ERROR\n",
key.display()
)
}
pub fn write_hosts(path: &Path, blocks: &[String]) -> Result<()> {
std::fs::create_dir_all(path.parent().unwrap())?;
let mut out =
String::from("# Generated by omh. Do not edit — rewritten on every `omh code`.\n\n");
for block in blocks {
out.push_str(block);
out.push('\n');
}
std::fs::write(path, out)?;
Ok(())
}
pub fn ensure_include(ssh_config: &Path) -> Result<()> {
let existing = std::fs::read_to_string(ssh_config).unwrap_or_default();
if existing.lines().any(|l| l.trim() == INCLUDE_LINE) {
return Ok(());
}
std::fs::create_dir_all(ssh_config.parent().unwrap())?;
let mut out = String::from(INCLUDE_LINE);
out.push_str("\n\n");
out.push_str(&existing);
std::fs::write(ssh_config, out)?;
Ok(())
}
pub fn url(alias: &str) -> String {
format!("ssh://{alias}/work")
}
pub fn ensure_key(dir: &Path) -> Result<PathBuf> {
let key = dir.join("id_ed25519");
if key.exists() {
return Ok(key);
}
std::fs::create_dir_all(dir)?;
let out = std::process::Command::new("ssh-keygen")
.args(["-t", "ed25519", "-N", "", "-C", "omh", "-f"])
.arg(&key)
.output()?;
if !out.status.success() {
anyhow::bail!(
"ssh-keygen: {}",
String::from_utf8_lossy(&out.stderr).trim()
);
}
Ok(key)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn aliases_are_unique_per_repo_and_session() {
assert_ne!(host_alias("a", "s01"), host_alias("b", "s01"));
assert_ne!(host_alias("a", "s01"), host_alias("a", "s02"));
}
#[test]
fn ports_are_stable_across_calls() {
assert_eq!(port("repo", "s01"), port("repo", "s01"));
}
#[test]
fn ports_differ_between_sessions_and_repos() {
assert_ne!(port("repo", "s01"), port("repo", "s02"));
assert_ne!(port("alpha", "s01"), port("beta", "s01"));
}
#[test]
fn ports_land_in_the_ephemeral_range() {
for session in ["s01", "s02", "s99", "doctor"] {
let p = port("some-repo", session);
assert!((49152..=65535).contains(&p), "{session} → {p}");
}
}
#[test]
fn the_host_block_binds_loopback_only() {
let block = config_block("omh-x-s01", 49200, Path::new("/k/id_ed25519"));
assert!(block.contains("HostName 127.0.0.1"), "got: {block}");
assert!(!block.contains("0.0.0.0"));
}
#[test]
fn the_host_block_names_the_alias_port_user_and_key() {
let block = config_block("omh-x-s01", 49200, Path::new("/k/id_ed25519"));
assert!(block.contains("Host omh-x-s01"));
assert!(block.contains("Port 49200"));
assert!(block.contains("User agent"));
assert!(block.contains("/k/id_ed25519"));
}
#[test]
fn ephemeral_host_keys_do_not_trip_known_hosts() {
let block = config_block("omh-x-s01", 49200, Path::new("/k"));
assert!(block.contains("StrictHostKeyChecking no"));
assert!(block.contains("UserKnownHostsFile /dev/null"));
}
#[test]
fn the_managed_file_is_replaced_wholesale() {
let d = tempfile::tempdir().unwrap();
let f = d.path().join("config.d/omh");
write_hosts(&f, &["Host a\n".into()]).unwrap();
write_hosts(&f, &["Host b\n".into()]).unwrap();
let body = std::fs::read_to_string(&f).unwrap();
assert!(body.contains("Host b"));
assert!(!body.contains("Host a"), "stale sessions must not linger");
}
#[test]
fn the_managed_file_says_it_is_generated() {
let d = tempfile::tempdir().unwrap();
let f = d.path().join("omh");
write_hosts(&f, &[]).unwrap();
let body = std::fs::read_to_string(&f).unwrap();
assert!(body.to_lowercase().contains("generated"), "got: {body}");
}
#[test]
fn adding_the_include_preserves_the_users_config() {
let d = tempfile::tempdir().unwrap();
let cfg = d.path().join("config");
std::fs::write(&cfg, "Host work\n User me\n").unwrap();
ensure_include(&cfg).unwrap();
let body = std::fs::read_to_string(&cfg).unwrap();
assert!(body.contains("Host work"), "existing config survived");
assert!(body.contains("User me"));
assert!(body.contains(INCLUDE_LINE));
}
#[test]
fn adding_the_include_twice_does_not_duplicate_it() {
let d = tempfile::tempdir().unwrap();
let cfg = d.path().join("config");
ensure_include(&cfg).unwrap();
ensure_include(&cfg).unwrap();
let body = std::fs::read_to_string(&cfg).unwrap();
assert_eq!(body.matches(INCLUDE_LINE).count(), 1);
}
#[test]
fn the_include_goes_at_the_top() {
let d = tempfile::tempdir().unwrap();
let cfg = d.path().join("config");
std::fs::write(&cfg, "Host work\n User me\n").unwrap();
ensure_include(&cfg).unwrap();
let body = std::fs::read_to_string(&cfg).unwrap();
assert!(
body.find(INCLUDE_LINE) < body.find("Host work"),
"Include must precede Host blocks:\n{body}"
);
}
}