SSH configuration path (#84)

SSH configuration
This commit is contained in:
Christian Visintin
2021-12-11 21:43:23 +01:00
parent 9284f101c8
commit e6ba4d8430
18 changed files with 155 additions and 9 deletions

View File

@@ -52,16 +52,21 @@ pub struct UserInterfaceConfig {
pub check_for_updates: Option<bool>, // @! Since 0.3.3
pub prompt_on_file_replace: Option<bool>, // @! Since 0.7.0; Default True
pub group_dirs: Option<String>,
pub file_fmt: Option<String>, // Refers to local host (for backward compatibility)
pub remote_file_fmt: Option<String>, // @! Since 0.5.0
pub notifications: Option<bool>, // @! Since 0.7.0; Default true
/// file fmt. Refers to local host (for backward compatibility)
pub file_fmt: Option<String>,
pub remote_file_fmt: Option<String>, // @! Since 0.5.0
pub notifications: Option<bool>, // @! Since 0.7.0; Default true
pub notification_threshold: Option<u64>, // @! Since 0.7.0; Default 512MB
}
#[derive(Deserialize, Serialize, Debug, Default)]
/// Contains configuratio related to remote hosts
pub struct RemoteConfig {
pub ssh_keys: HashMap<String, PathBuf>, // Association between host name and path to private key
/// Ssh configuration path. If NONE, won't be read
pub ssh_config: Option<String>,
/// Association between host name and path to private key
/// NOTE: this parameter must stay as last: <https://github.com/alexcrichton/toml-rs/issues/142>
pub ssh_keys: HashMap<String, PathBuf>,
}
impl Default for UserInterfaceConfig {
@@ -99,7 +104,10 @@ mod tests {
String::from("192.168.1.31"),
PathBuf::from("/tmp/private.key"),
);
let remote: RemoteConfig = RemoteConfig { ssh_keys: keys };
let remote: RemoteConfig = RemoteConfig {
ssh_keys: keys,
ssh_config: Some(String::from("~/.ssh/config")),
};
let ui: UserInterfaceConfig = UserInterfaceConfig {
default_protocol: String::from("SFTP"),
text_editor: PathBuf::from("nano"),
@@ -130,6 +138,10 @@ mod tests {
.unwrap(),
PathBuf::from("/tmp/private.key")
);
assert_eq!(
cfg.remote.ssh_config.as_deref().unwrap(),
String::from("~/.ssh/config")
);
assert_eq!(cfg.user_interface.default_protocol, String::from("SFTP"));
assert_eq!(cfg.user_interface.text_editor, PathBuf::from("nano"));
assert_eq!(cfg.user_interface.show_hidden_files, true);

View File

@@ -197,6 +197,11 @@ mod tests {
assert_eq!(cfg.user_interface.notifications.unwrap(), false);
assert_eq!(cfg.user_interface.notification_threshold.unwrap(), 1024);
assert_eq!(cfg.user_interface.group_dirs, Some(String::from("last")));
// Remote
assert_eq!(
cfg.remote.ssh_config.as_deref(),
Some("/home/omar/.ssh/config")
);
assert_eq!(
cfg.user_interface.file_fmt,
Some(String::from("{NAME} {PEX}"))
@@ -244,6 +249,7 @@ mod tests {
assert!(cfg.user_interface.remote_file_fmt.is_none());
assert!(cfg.user_interface.notifications.is_none());
assert!(cfg.user_interface.notification_threshold.is_none());
assert!(cfg.remote.ssh_config.is_none());
// Verify keys
assert_eq!(
*cfg.remote
@@ -322,6 +328,9 @@ mod tests {
notifications = false
notification_threshold = 1024
[remote]
ssh_config = "/home/omar/.ssh/config"
[remote.ssh_keys]
"192.168.1.31" = "/home/omar/.ssh/raspberry.key"
"192.168.1.32" = "/home/omar/.ssh/beaglebone.key"

View File

@@ -36,6 +36,7 @@ use remotefs::client::{
ssh::{ScpFs, SftpFs, SshOpts},
};
use remotefs::RemoteFs;
use std::path::PathBuf;
/// Remotefs builder
pub struct Builder;
@@ -117,6 +118,9 @@ impl Builder {
if let Some(password) = params.password {
opts = opts.password(password);
}
if let Some(config_path) = config_client.get_ssh_config() {
opts = opts.config_file(PathBuf::from(config_path));
}
opts
}

View File

@@ -239,6 +239,18 @@ impl ConfigClient {
self.config.user_interface.notification_threshold = Some(value);
}
// Remote params
/// Get ssh config path
pub fn get_ssh_config(&self) -> Option<&str> {
self.config.remote.ssh_config.as_deref()
}
/// Set ssh config path
pub fn set_ssh_config(&mut self, p: Option<String>) {
self.config.remote.ssh_config = p;
}
// SSH Keys
/// Save a SSH key into configuration.
@@ -655,6 +667,20 @@ mod tests {
assert_eq!(client.get_notification_threshold(), 64);
}
#[test]
fn test_system_config_remote_ssh_config() {
let tmp_dir: TempDir = TempDir::new().ok().unwrap();
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
.ok()
.unwrap();
assert_eq!(client.get_ssh_config(), None); // Null ?
client.set_ssh_config(Some(String::from("/tmp/ssh_config")));
assert_eq!(client.get_ssh_config(), Some("/tmp/ssh_config"));
client.set_ssh_config(None);
assert_eq!(client.get_ssh_config(), None);
}
#[test]
fn test_system_config_ssh_keys() {
let tmp_dir: TempDir = TempDir::new().ok().unwrap();

View File

@@ -391,6 +391,7 @@ impl BookmarkName {
impl Component<Msg, NoUserEvent> for BookmarkName {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
match ev {
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => Some(Msg::CloseSaveBookmark),
Event::Keyboard(KeyEvent {
code: Key::Left, ..
}) => {

View File

@@ -368,6 +368,43 @@ impl Component<Msg, NoUserEvent> for RemoteFileFmt {
}
}
#[derive(MockComponent)]
pub struct SshConfig {
component: Input,
}
impl SshConfig {
pub fn new(value: &str) -> Self {
Self {
component: Input::default()
.borders(
Borders::default()
.color(Color::LightBlue)
.modifiers(BorderType::Rounded),
)
.foreground(Color::LightBlue)
.input_type(InputType::Text)
.placeholder(
"~/.ssh/config",
Style::default().fg(Color::Rgb(128, 128, 128)),
)
.title("SSH configuration path", Alignment::Left)
.value(value),
}
}
}
impl Component<Msg, NoUserEvent> for SshConfig {
fn on(&mut self, ev: Event<NoUserEvent>) -> Option<Msg> {
handle_input_ev(
self,
ev,
Msg::Config(ConfigMsg::SshConfigBlurDown),
Msg::Config(ConfigMsg::SshConfigBlurUp),
)
}
}
#[derive(MockComponent)]
pub struct TextEditor {
component: Input,
@@ -448,7 +485,7 @@ fn handle_input_ev(
}
Event::Keyboard(KeyEvent {
code: Key::Char(ch),
modifiers: KeyModifiers::NONE,
modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT,
}) => {
component.perform(Cmd::Type(ch));
Some(Msg::Config(ConfigMsg::ConfigChanged))

View File

@@ -35,7 +35,7 @@ mod theme;
pub(super) use commons::{ErrorPopup, Footer, Header, Keybindings, QuitPopup, SavePopup};
pub(super) use config::{
CheckUpdates, DefaultProtocol, GroupDirs, HiddenFiles, LocalFileFmt, NotificationsEnabled,
NotificationsThreshold, PromptOnFileReplace, RemoteFileFmt, TextEditor,
NotificationsThreshold, PromptOnFileReplace, RemoteFileFmt, SshConfig, TextEditor,
};
pub(super) use ssh::{DelSshKeyPopup, SshHost, SshKeys, SshUsername};
pub(super) use theme::*;

View File

@@ -36,7 +36,10 @@ impl SetupActivity {
pub(super) fn save_config(&mut self) -> Result<(), String> {
match self.config().write_config() {
Ok(_) => Ok(()),
Err(err) => Err(format!("Could not save configuration: {}", err)),
Err(err) => {
error!("Could not save configuration: {}", err);
Err(format!("Could not save configuration: {}", err))
}
}
}

View File

@@ -75,6 +75,7 @@ enum IdConfig {
NotificationsThreshold,
PromptOnFileReplace,
RemoteFileFmt,
SshConfig,
TextEditor,
}
@@ -167,6 +168,8 @@ pub enum ConfigMsg {
PromptOnFileReplaceBlurUp,
RemoteFileFmtBlurDown,
RemoteFileFmtBlurUp,
SshConfigBlurDown,
SshConfigBlurUp,
TextEditorBlurDown,
TextEditorBlurUp,
}

View File

@@ -80,11 +80,13 @@ impl SetupActivity {
CommonMsg::RevertChanges => match self.layout {
ViewLayout::Theme => {
if let Err(err) = self.action_reset_theme() {
error!("Failed to reset theme: {}", err);
self.mount_error(err);
}
}
ViewLayout::SshKeys | ViewLayout::SetupForm => {
if let Err(err) = self.action_reset_config() {
error!("Failed to reset config: {}", err);
self.mount_error(err);
}
}
@@ -92,6 +94,7 @@ impl SetupActivity {
CommonMsg::SaveAndQuit => {
// Save changes
if let Err(err) = self.action_save_all() {
error!("Failed to save config: {}", err);
self.mount_error(err.as_str());
}
// Exit
@@ -99,6 +102,7 @@ impl SetupActivity {
}
CommonMsg::SaveConfig => {
if let Err(err) = self.action_save_all() {
error!("Failed to save config: {}", err);
self.mount_error(err.as_str());
}
self.umount_save_popup();
@@ -173,7 +177,7 @@ impl SetupActivity {
.is_ok());
}
ConfigMsg::NotificationsThresholdBlurDown => {
assert!(self.app.active(&Id::Config(IdConfig::TextEditor)).is_ok());
assert!(self.app.active(&Id::Config(IdConfig::SshConfig)).is_ok());
}
ConfigMsg::NotificationsThresholdBlurUp => {
assert!(self
@@ -203,6 +207,12 @@ impl SetupActivity {
.is_ok());
}
ConfigMsg::TextEditorBlurUp => {
assert!(self.app.active(&Id::Config(IdConfig::SshConfig)).is_ok());
}
ConfigMsg::SshConfigBlurDown => {
assert!(self.app.active(&Id::Config(IdConfig::TextEditor)).is_ok());
}
ConfigMsg::SshConfigBlurUp => {
assert!(self
.app
.active(&Id::Config(IdConfig::NotificationsThreshold))
@@ -230,6 +240,7 @@ impl SetupActivity {
}
SshMsg::EditSshKey(i) => {
if let Err(err) = self.edit_ssh_key(i) {
error!("Failed to edit ssh key: {}", err);
self.mount_error(err.as_str());
}
}

View File

@@ -119,6 +119,7 @@ impl SetupActivity {
Constraint::Length(3), // Remote Format input
Constraint::Length(3), // Notifications enabled
Constraint::Length(3), // Notifications threshold
Constraint::Length(3), // Ssh config
Constraint::Length(1), // Filler
]
.as_ref(),
@@ -144,6 +145,8 @@ impl SetupActivity {
f,
ui_cfg_chunks_col2[3],
);
self.app
.view(&Id::Config(IdConfig::SshConfig), f, ui_cfg_chunks_col2[4]);
// Popups
self.view_popups(f);
});
@@ -261,6 +264,17 @@ impl SetupActivity {
vec![]
)
.is_ok());
// Ssh config
assert!(self
.app
.remount(
Id::Config(IdConfig::SshConfig),
Box::new(components::SshConfig::new(
self.config().get_ssh_config().unwrap_or("")
)),
vec![]
)
.is_ok());
}
/// Collect values from input and put them into the configuration
@@ -332,5 +346,19 @@ impl SetupActivity {
{
self.config_mut().set_notification_threshold(bytes);
}
if let Ok(State::One(StateValue::String(mut path))) =
self.app.state(&Id::Config(IdConfig::SshConfig))
{
if path.is_empty() {
self.config_mut().set_ssh_config(None);
} else {
// Replace '~' with home path
if path.starts_with('~') {
let home_dir = dirs::home_dir().unwrap_or_else(|| PathBuf::from("/root"));
path = path.replacen('~', &home_dir.to_string_lossy(), 1);
}
self.config_mut().set_ssh_config(Some(path));
}
}
}
}