196 lines
6.0 KiB
Rust
196 lines
6.0 KiB
Rust
use anyhow::{ Result, bail };
|
|
use crate::{ ConfigManager, config::Host, input };
|
|
use dialoguer::{ Select, Input, theme::ColorfulTheme };
|
|
use serde::{ Serialize, Deserialize };
|
|
use ssh_key::PrivateKey;
|
|
use std::fs;
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct PlatformKey {
|
|
pub file: String,
|
|
pub display: String,
|
|
}
|
|
|
|
impl std::fmt::Display for PlatformKey {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.write_str(&self.display)
|
|
}
|
|
}
|
|
|
|
|
|
// todo: GPG support
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
|
pub enum Platform {
|
|
SSH
|
|
}
|
|
|
|
impl std::fmt::Display for Platform {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.write_str(match self {
|
|
Platform::SSH => "SSH",
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Platform {
|
|
pub fn all() -> Vec<Platform> {
|
|
vec![
|
|
Self::SSH
|
|
]
|
|
}
|
|
|
|
pub fn new_host(&self, config: &mut ConfigManager) -> Result<Host> {
|
|
match self {
|
|
Platform::SSH => self.ssh_new_host(config)
|
|
}
|
|
}
|
|
|
|
pub fn get_keys(&self, config: &ConfigManager) -> Vec<PlatformKey> {
|
|
match self {
|
|
Platform::SSH => self.ssh_get_keys(config),
|
|
}
|
|
}
|
|
pub fn run(&self, host: &mut Host, config: &mut ConfigManager) -> Result<()> {
|
|
match self {
|
|
Platform::SSH => self.ssh_run(host, config),
|
|
}
|
|
}
|
|
|
|
|
|
// --- --- ---
|
|
// SSH
|
|
// --- --- ---
|
|
|
|
fn ssh_new_config(&self, host: String, port: i16, keyid: PlatformKey) -> String {
|
|
format!(r#"# Generated by Keyman
|
|
|
|
Host {host}
|
|
HostName {host}
|
|
Port {port}
|
|
IdentityFile {}
|
|
"#, keyid.file)
|
|
}
|
|
|
|
// New host
|
|
fn ssh_new_host(&self, config: &mut ConfigManager) -> Result<Host> {
|
|
println!("- Creating new host entry");
|
|
let keyid = input::get_key(config, self.to_owned())?;
|
|
|
|
let userhost = loop {
|
|
let inp: String = Input::with_theme(&ColorfulTheme::default())
|
|
.with_prompt("User@Host")
|
|
.interact_text()?;
|
|
|
|
let split = inp.split("@");
|
|
let split: Vec<String> = split.map(|a| a.to_string()).collect();
|
|
|
|
if split.len() < 2 {
|
|
bail!("Incorrect user@host given. Example: paul@example.com:34; <user>@<host>[:port]");
|
|
} else {
|
|
break split
|
|
}
|
|
};
|
|
|
|
let user: String = userhost.get(0).unwrap().to_string();
|
|
let mut host: String = userhost.get(1).unwrap().to_string();
|
|
let mut port: i16 = 22;
|
|
|
|
let _host = host.to_owned();
|
|
let h: Vec<&str> = _host.split(":").collect();
|
|
|
|
if h.len() > 1 {
|
|
let portstr = h.get(1).unwrap();
|
|
port = i16::from_str_radix(portstr, 10)?;
|
|
host = h.get(0).unwrap().to_string();
|
|
}
|
|
|
|
let config_dir = config.config_dir.join("hosts/").join(host.to_owned());
|
|
fs::create_dir_all(config_dir.to_owned()).expect(&format!("Couldnt create_dir_all on {:?}", config_dir.to_owned()));
|
|
let config_file = config_dir.join("config");
|
|
|
|
fs::write(config_file.to_owned(), self.ssh_new_config(host.to_owned(), port, keyid.to_owned())).expect(&format!("Couldn't write config at {:?}", config_file.to_owned()));
|
|
|
|
Ok(Host {
|
|
platform: Platform::SSH,
|
|
id: keyid.file,
|
|
user,
|
|
host,
|
|
port,
|
|
config: config_file.to_owned().to_string_lossy().to_string()
|
|
})
|
|
}
|
|
|
|
// Get keys
|
|
fn ssh_get_keys(&self, config: &ConfigManager) -> Vec<PlatformKey> {
|
|
let mut keys = vec![];
|
|
let Ok(dir) = std::fs::read_dir(config.search_path.to_owned()) else { panic!("Couldn't read {:?}", config.search_path.to_owned()) };
|
|
|
|
for file in dir {
|
|
let Ok(f) = file else { continue };
|
|
|
|
match PrivateKey::read_openssh_file(&f.path()) {
|
|
Ok(p) => keys.push(PlatformKey {
|
|
file: format!("{}", f.path().to_string_lossy()),
|
|
display: format!("{}: {}", p.fingerprint(ssh_key::HashAlg::Sha256), f.file_name().to_string_lossy())
|
|
}),
|
|
// Not a private key
|
|
Err(_) => continue
|
|
};
|
|
}
|
|
|
|
keys
|
|
}
|
|
|
|
// Run
|
|
fn ssh_run(&self, host: &mut Host, config: &mut ConfigManager) -> Result<()> {
|
|
Ok(loop {
|
|
let action = Select::with_theme(&ColorfulTheme::default())
|
|
.with_prompt(format!("{}@{}", host.user.to_owned(), host.host.to_owned()))
|
|
.item("Connect")
|
|
.item("Edit config")
|
|
.item("Edit definition")
|
|
.item("Delete")
|
|
.item("Back")
|
|
.default(0)
|
|
.interact()?;
|
|
|
|
match action {
|
|
0 => { // Connect
|
|
std::process::Command::new("ssh")
|
|
.arg("-F")
|
|
.arg(host.config.to_owned())
|
|
.arg("-l")
|
|
.arg(host.user.to_owned())
|
|
.arg(host.host.to_owned()).spawn()?.wait()?;
|
|
}
|
|
1 => { // Edit config
|
|
let editor = std::env::var("EDITOR").expect("EDITOR is not set");
|
|
std::process::Command::new(editor)
|
|
.arg(host.config.to_owned())
|
|
.spawn()?.wait()?;
|
|
}
|
|
2 => { // Edit definition
|
|
let edited_host = host.edit()?;
|
|
config.configs.remove(host);
|
|
config.configs.insert(edited_host);
|
|
config.save();
|
|
}
|
|
3 => { // Delete
|
|
config.configs.remove(host);
|
|
config.save();
|
|
break;
|
|
} // Back
|
|
4 => break,
|
|
_ => ()
|
|
}
|
|
})
|
|
}
|
|
|
|
|
|
// --- --- ---
|
|
// GPG
|
|
// --- --- ---
|
|
}
|
|
|