mirror of
https://github.com/veeso/termscp.git
synced 2026-09-27 06:21:22 -07:00
feat: Import bookmarks from ssh config with a CLI command
Use import-ssh-hosts to import all the possible hosts by the configured ssh config or the default one on your machine closes #331
This commit is contained in:
Generated
+288
-259
File diff suppressed because it is too large
Load Diff
+1
-29
@@ -448,35 +448,7 @@ impl ActivityManager {
|
|||||||
// -- misc
|
// -- misc
|
||||||
|
|
||||||
fn init_bookmarks_client(keyring: bool) -> Result<Option<BookmarksClient>, String> {
|
fn init_bookmarks_client(keyring: bool) -> Result<Option<BookmarksClient>, String> {
|
||||||
// Get config dir
|
crate::support::bookmarks_client(keyring)
|
||||||
match environment::init_config_dir() {
|
|
||||||
Ok(path) => {
|
|
||||||
// If some configure client, otherwise do nothing; don't bother users telling them that bookmarks are not supported on their system.
|
|
||||||
if let Some(config_dir_path) = path {
|
|
||||||
let bookmarks_file: PathBuf =
|
|
||||||
environment::get_bookmarks_paths(config_dir_path.as_path());
|
|
||||||
// Initialize client
|
|
||||||
BookmarksClient::new(
|
|
||||||
bookmarks_file.as_path(),
|
|
||||||
config_dir_path.as_path(),
|
|
||||||
16,
|
|
||||||
keyring,
|
|
||||||
)
|
|
||||||
.map(Option::Some)
|
|
||||||
.map_err(|e| {
|
|
||||||
format!(
|
|
||||||
"Could not initialize bookmarks (at \"{}\", \"{}\"): {}",
|
|
||||||
bookmarks_file.display(),
|
|
||||||
config_dir_path.display(),
|
|
||||||
e
|
|
||||||
)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => Err(err),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize configuration client
|
/// Initialize configuration client
|
||||||
|
|||||||
+24
-2
@@ -15,6 +15,9 @@ use crate::system::logging::LogLevel;
|
|||||||
|
|
||||||
pub enum Task {
|
pub enum Task {
|
||||||
Activity(NextActivity),
|
Activity(NextActivity),
|
||||||
|
/// Import ssh hosts from the specified ssh config file, or from the default location
|
||||||
|
/// and save them as bookmarks.
|
||||||
|
ImportSshHosts(Option<PathBuf>),
|
||||||
ImportTheme(PathBuf),
|
ImportTheme(PathBuf),
|
||||||
InstallUpdate,
|
InstallUpdate,
|
||||||
Version,
|
Version,
|
||||||
@@ -72,7 +75,8 @@ pub struct Args {
|
|||||||
#[argh(subcommand)]
|
#[argh(subcommand)]
|
||||||
pub enum ArgsSubcommands {
|
pub enum ArgsSubcommands {
|
||||||
Config(ConfigArgs),
|
Config(ConfigArgs),
|
||||||
LoadTheme(LoadThemeArgs),
|
ImportSshHosts(ImportSshHostsArgs),
|
||||||
|
ImportTheme(ImportThemeArgs),
|
||||||
Update(UpdateArgs),
|
Update(UpdateArgs),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,10 +90,20 @@ pub struct ConfigArgs {}
|
|||||||
#[argh(subcommand, name = "update")]
|
#[argh(subcommand, name = "update")]
|
||||||
pub struct UpdateArgs {}
|
pub struct UpdateArgs {}
|
||||||
|
|
||||||
|
#[derive(FromArgs)]
|
||||||
|
/// import ssh hosts from the specified ssh config file, or from the default location
|
||||||
|
/// and save them as bookmarks.
|
||||||
|
#[argh(subcommand, name = "import-ssh-hosts")]
|
||||||
|
pub struct ImportSshHostsArgs {
|
||||||
|
#[argh(positional)]
|
||||||
|
/// optional ssh config file; if not specified, the default location will be used
|
||||||
|
pub ssh_config: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(FromArgs)]
|
#[derive(FromArgs)]
|
||||||
/// import the specified theme
|
/// import the specified theme
|
||||||
#[argh(subcommand, name = "theme")]
|
#[argh(subcommand, name = "theme")]
|
||||||
pub struct LoadThemeArgs {
|
pub struct ImportThemeArgs {
|
||||||
#[argh(positional)]
|
#[argh(positional)]
|
||||||
/// theme file
|
/// theme file
|
||||||
pub theme: PathBuf,
|
pub theme: PathBuf,
|
||||||
@@ -118,6 +132,14 @@ impl RunOpts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn import_ssh_hosts(ssh_config: Option<PathBuf>, keyring: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
task: Task::ImportSshHosts(ssh_config),
|
||||||
|
keyring,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn import_theme(theme: PathBuf) -> Self {
|
pub fn import_theme(theme: PathBuf) -> Self {
|
||||||
Self {
|
Self {
|
||||||
task: Task::ImportTheme(theme),
|
task: Task::ImportTheme(theme),
|
||||||
|
|||||||
+17
-2
@@ -22,7 +22,7 @@ extern crate log;
|
|||||||
extern crate magic_crypt;
|
extern crate magic_crypt;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use self::activity_manager::{ActivityManager, NextActivity};
|
use self::activity_manager::{ActivityManager, NextActivity};
|
||||||
@@ -72,7 +72,10 @@ fn main() -> MainResult<()> {
|
|||||||
fn parse_args(args: Args) -> Result<RunOpts, String> {
|
fn parse_args(args: Args) -> Result<RunOpts, String> {
|
||||||
let run_opts = match args.nested {
|
let run_opts = match args.nested {
|
||||||
Some(ArgsSubcommands::Update(_)) => RunOpts::update(),
|
Some(ArgsSubcommands::Update(_)) => RunOpts::update(),
|
||||||
Some(ArgsSubcommands::LoadTheme(args)) => RunOpts::import_theme(args.theme),
|
Some(ArgsSubcommands::ImportSshHosts(subargs)) => {
|
||||||
|
RunOpts::import_ssh_hosts(subargs.ssh_config, !args.wno_keyring)
|
||||||
|
}
|
||||||
|
Some(ArgsSubcommands::ImportTheme(args)) => RunOpts::import_theme(args.theme),
|
||||||
Some(ArgsSubcommands::Config(_)) => RunOpts::config(),
|
Some(ArgsSubcommands::Config(_)) => RunOpts::config(),
|
||||||
None => {
|
None => {
|
||||||
let mut run_opts: RunOpts = RunOpts::default();
|
let mut run_opts: RunOpts = RunOpts::default();
|
||||||
@@ -127,6 +130,7 @@ fn parse_args(args: Args) -> Result<RunOpts, String> {
|
|||||||
/// Run task and return rc
|
/// Run task and return rc
|
||||||
fn run(run_opts: RunOpts) -> MainResult<()> {
|
fn run(run_opts: RunOpts) -> MainResult<()> {
|
||||||
match run_opts.task {
|
match run_opts.task {
|
||||||
|
Task::ImportSshHosts(ssh_config) => run_import_ssh_hosts(ssh_config, run_opts.keyring),
|
||||||
Task::ImportTheme(theme) => run_import_theme(&theme),
|
Task::ImportTheme(theme) => run_import_theme(&theme),
|
||||||
Task::InstallUpdate => run_install_update(),
|
Task::InstallUpdate => run_install_update(),
|
||||||
Task::Activity(activity) => {
|
Task::Activity(activity) => {
|
||||||
@@ -145,6 +149,17 @@ fn print_version() -> MainResult<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn run_import_ssh_hosts(ssh_config_path: Option<PathBuf>, keyring: bool) -> MainResult<()> {
|
||||||
|
support::import_ssh_hosts(ssh_config_path, keyring)
|
||||||
|
.map(|_| {
|
||||||
|
println!("SSH hosts have been successfully imported!");
|
||||||
|
})
|
||||||
|
.map_err(|err| {
|
||||||
|
eprintln!("{err}");
|
||||||
|
err.into()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn run_import_theme(theme: &Path) -> MainResult<()> {
|
fn run_import_theme(theme: &Path) -> MainResult<()> {
|
||||||
match support::import_theme(theme) {
|
match support::import_theme(theme) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
|||||||
+37
-1
@@ -2,11 +2,14 @@
|
|||||||
//!
|
//!
|
||||||
//! this module exposes some extra run modes for termscp, meant to be used for "support", such as installing themes
|
//! this module exposes some extra run modes for termscp, meant to be used for "support", such as installing themes
|
||||||
|
|
||||||
// mod
|
mod import_ssh_hosts;
|
||||||
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
pub use self::import_ssh_hosts::import_ssh_hosts;
|
||||||
use crate::system::auto_update::{Update, UpdateStatus};
|
use crate::system::auto_update::{Update, UpdateStatus};
|
||||||
|
use crate::system::bookmarks_client::BookmarksClient;
|
||||||
use crate::system::config_client::ConfigClient;
|
use crate::system::config_client::ConfigClient;
|
||||||
use crate::system::environment;
|
use crate::system::environment;
|
||||||
use crate::system::notifications::Notification;
|
use crate::system::notifications::Notification;
|
||||||
@@ -83,3 +86,36 @@ fn get_config_client() -> Option<ConfigClient> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Init [`BookmarksClient`].
|
||||||
|
pub fn bookmarks_client(keyring: bool) -> Result<Option<BookmarksClient>, String> {
|
||||||
|
// Get config dir
|
||||||
|
match environment::init_config_dir() {
|
||||||
|
Ok(path) => {
|
||||||
|
// If some configure client, otherwise do nothing; don't bother users telling them that bookmarks are not supported on their system.
|
||||||
|
if let Some(config_dir_path) = path {
|
||||||
|
let bookmarks_file: PathBuf =
|
||||||
|
environment::get_bookmarks_paths(config_dir_path.as_path());
|
||||||
|
// Initialize client
|
||||||
|
BookmarksClient::new(
|
||||||
|
bookmarks_file.as_path(),
|
||||||
|
config_dir_path.as_path(),
|
||||||
|
16,
|
||||||
|
keyring,
|
||||||
|
)
|
||||||
|
.map(Option::Some)
|
||||||
|
.map_err(|e| {
|
||||||
|
format!(
|
||||||
|
"Could not initialize bookmarks (at \"{}\", \"{}\"): {}",
|
||||||
|
bookmarks_file.display(),
|
||||||
|
config_dir_path.display(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
use std::fs::File;
|
||||||
|
use std::io::BufReader;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use ssh2_config::{Host, HostClause, ParseRule, SshConfig};
|
||||||
|
|
||||||
|
use crate::filetransfer::params::GenericProtocolParams;
|
||||||
|
use crate::filetransfer::{FileTransferParams, FileTransferProtocol, ProtocolParams};
|
||||||
|
|
||||||
|
/// Import ssh hosts from the specified ssh config file, or from the default location
|
||||||
|
/// and save them as bookmarks.
|
||||||
|
pub fn import_ssh_hosts(ssh_config: Option<PathBuf>, keyring: bool) -> Result<(), String> {
|
||||||
|
// get config client
|
||||||
|
let cfg_client = super::get_config_client()
|
||||||
|
.ok_or_else(|| String::from("Could not import ssh hosts: could not load configuration"))?;
|
||||||
|
|
||||||
|
// resolve ssh_config
|
||||||
|
let ssh_config = ssh_config.or_else(|| cfg_client.get_ssh_config().map(PathBuf::from));
|
||||||
|
|
||||||
|
// load bookmarks client
|
||||||
|
let mut bookmarks_client = super::bookmarks_client(keyring)?
|
||||||
|
.ok_or_else(|| String::from("Could not import ssh hosts: could not load bookmarks"))?;
|
||||||
|
|
||||||
|
// load ssh config
|
||||||
|
let ssh_config = match ssh_config {
|
||||||
|
Some(p) => {
|
||||||
|
debug!("Importing ssh hosts from file: {}", p.display());
|
||||||
|
let mut reader = BufReader::new(
|
||||||
|
File::open(&p)
|
||||||
|
.map_err(|e| format!("Could not open ssh config file {}: {e}", p.display()))?,
|
||||||
|
);
|
||||||
|
SshConfig::default().parse(&mut reader, ParseRule::ALLOW_UNKNOWN_FIELDS)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
debug!("Importing ssh hosts from default location");
|
||||||
|
SshConfig::parse_default_file(ParseRule::ALLOW_UNKNOWN_FIELDS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.map_err(|e| format!("Could not parse ssh config file: {e}"))?;
|
||||||
|
|
||||||
|
// iter hosts and add bookmarks
|
||||||
|
ssh_config
|
||||||
|
.get_hosts()
|
||||||
|
.iter()
|
||||||
|
.flat_map(host_to_params)
|
||||||
|
.for_each(|(name, params)| {
|
||||||
|
debug!("Adding bookmark for host: {name} with params: {params:?}");
|
||||||
|
bookmarks_client.add_bookmark(name, params, false)
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("Imported ssh hosts");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to derive [`FileTransferParams`] from the specified ssh host.
|
||||||
|
fn host_to_params(host: &Host) -> impl Iterator<Item = (String, FileTransferParams)> {
|
||||||
|
host.pattern
|
||||||
|
.iter()
|
||||||
|
.filter_map(|pattern| host_pattern_to_params(host, pattern))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to derive [`FileTransferParams`] from the specified ssh host and pattern.
|
||||||
|
fn host_pattern_to_params(
|
||||||
|
host: &Host,
|
||||||
|
pattern: &HostClause,
|
||||||
|
) -> Option<(String, FileTransferParams)> {
|
||||||
|
debug!("Processing host with pattern: {pattern:?}",);
|
||||||
|
if pattern.negated || pattern.pattern.contains('*') || pattern.pattern.contains('?') {
|
||||||
|
debug!("Skipping host with pattern: {pattern}",);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let address = host
|
||||||
|
.params
|
||||||
|
.host_name
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or(pattern.pattern.as_str())
|
||||||
|
.to_string();
|
||||||
|
debug!("Resolved address for pattern {pattern}: {address}");
|
||||||
|
let port = host.params.port.unwrap_or(22);
|
||||||
|
debug!("Resolved port for pattern {pattern}: {port}");
|
||||||
|
let username = host.params.user.clone();
|
||||||
|
debug!("Resolved username for pattern {pattern}: {username:?}");
|
||||||
|
|
||||||
|
Some((
|
||||||
|
pattern.to_string(),
|
||||||
|
FileTransferParams::new(
|
||||||
|
FileTransferProtocol::Sftp,
|
||||||
|
ProtocolParams::Generic(
|
||||||
|
GenericProtocolParams::default()
|
||||||
|
.address(address)
|
||||||
|
.port(port)
|
||||||
|
.username(username),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user