2023-05-30 09:26:01 +05:30
use anyhow ::Result ;
2023-05-30 08:57:09 +05:30
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 {
2023-05-30 09:20:20 +05:30
println! ( " Incorrect user@host given. Example: paul@example.com:34; <user>@<host>[:port] " ) ;
2023-05-30 08:57:09 +05:30
} 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
// --- --- ---
}