diff --git a/CHANGELOG.md b/CHANGELOG.md index 03610e5..209b631 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added +- New `environment` session type that prints out environment variable + definitions for use in an interactive session or to evaluate - Shell support introduced for ssh & distant servers, including a new shell command for distant cli - Support for JSON communication of ssh auth during launch (cli) - Add windows and unix metadata files to overall metadata response data ### Changed +- Default session type for CLI (launch, action, etc) is `environment` - Replace cbor library with alternative as old cbor lib has been abandoned - Refactor some request & response types to work with new cbor lib - Updated cli to always include serde dependency @@ -27,7 +30,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Github actions no longer use paths-filter so every PR & commit will test everything - ## [0.15.1] - 2021-11-15 ### Added - `--key-from-stdin` option to listen cli command to read key from stdin diff --git a/Cargo.lock b/Cargo.lock index 6d24bbf..cfd8942 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -409,6 +409,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.1" @@ -418,6 +424,40 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00d6d2ea26e8b151d99093005cb442fb9a37aeaca582a03ec70946f49ab5ed9" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + [[package]] name = "crossbeam-utils" version = "0.8.5" @@ -518,6 +558,7 @@ dependencies = [ "serde_json", "structopt", "strum", + "sysinfo", "terminal_size", "termwiz", "tokio", @@ -1004,9 +1045,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.103" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" +checksum = "e74d72e0f9b65b5b4ca49a346af3976df0f9c61d550727f349ecd559f251a26c" [[package]] name = "libssh-rs" @@ -1105,6 +1146,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a64a92489e2744ce060c349162be1c5f33c6969234104dbd99ddb5feb08b8c15" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "mio" version = "0.7.13" @@ -1604,6 +1654,31 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rayon" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + [[package]] name = "redox_syscall" version = "0.2.10" @@ -2012,6 +2087,21 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "sysinfo" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d82ade9d6621d4ca052a00bb6ea9ed513d223cba75a84625c5e9c0698ab6f5" +dependencies = [ + "cfg-if 1.0.0", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "winapi", +] + [[package]] name = "tempfile" version = "3.2.0" diff --git a/Cargo.toml b/Cargo.toml index 43f6400..2a73515 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ ssh2 = ["distant-ssh2"] derive_more = { version = "0.99.16", default-features = false, features = ["display", "from", "error", "is_variant"] } distant-core = { version = "=0.16.0", path = "distant-core", features = ["structopt"] } flexi_logger = "0.18.0" +indoc = "1.0.3" log = "0.4.14" once_cell = "1.8.0" rand = { version = "0.8.4", features = ["getrandom"] } @@ -34,6 +35,7 @@ serde = { version = "1.0.126", features = ["derive"] } serde_json = "1.0.64" structopt = "0.3.22" strum = { version = "0.21.0", features = ["derive"] } +sysinfo = "0.23.2" tokio = { version = "1.12.0", features = ["full"] } terminal_size = "0.1.17" termwiz = "0.15.0" @@ -45,6 +47,9 @@ distant-ssh2 = { version = "=0.16.0", path = "distant-ssh2", features = ["serde" [target.'cfg(unix)'.dependencies] fork = "0.1.18" +# [target.'cfg(windows)'.dependencies] +# sysinfo = "0.23.2" + [dev-dependencies] assert_cmd = "2.0.0" assert_fs = "1.0.4" diff --git a/README.md b/README.md index 8a77d0d..a93bc3d 100644 --- a/README.md +++ b/README.md @@ -68,17 +68,22 @@ Launch a remote instance of `distant`. Calling `launch` will do the following: 1. Ssh into the specified host (in the below example, `my.example.com`) 2. Execute `distant listen --host ssh` on the remote machine 3. Receive on the local machine the credentials needed to connect to the server -4. Depending on the options specified, store/use the session settings so +4. Depending on the options specified, print/store/use the session settings so future calls to `distant action` can connect ```bash # Connects to my.example.com on port 22 via SSH to start a new session +# and print out information to configure your system to talk to it distant launch my.example.com +# NOTE: If you are using sh, bash, or zsh, you can automatically set the + appropriate environment variables using the following +eval "$(distant launch my.example.com)" + # After the session is established, you can perform different operations # on the remote machine via `distant action {command} [args]` distant action copy path/to/file new/path/to/file -distant action run -- echo 'Hello, this is from the other side' +distant action spawn -- echo 'Hello, this is from the other side' ``` ## License diff --git a/distant-core/src/client/session/info.rs b/distant-core/src/client/session/info.rs index e12ea02..a13d012 100644 --- a/distant-core/src/client/session/info.rs +++ b/distant-core/src/client/session/info.rs @@ -137,6 +137,11 @@ impl SessionInfo { Ok(SocketAddr::from((addr, self.port))) } + /// Converts the session's key to a hex string + pub fn key_to_unprotected_string(&self) -> String { + self.key.unprotected_to_hex_key() + } + /// Converts to unprotected string that exposes the key in the form of /// `DISTANT CONNECT ` pub fn to_unprotected_string(&self) -> String { diff --git a/src/environment.rs b/src/environment.rs new file mode 100644 index 0000000..e59404a --- /dev/null +++ b/src/environment.rs @@ -0,0 +1,142 @@ +use distant_core::SessionInfo; +use std::{ffi::OsStr, path::Path}; + +/// Prints out shell-specific environment information +pub fn print_environment(info: &SessionInfo) { + inner_print_environment(&info.host, info.port, &info.key_to_unprotected_string()) +} + +/// Prints out shell-specific environment information +#[cfg(unix)] +fn inner_print_environment(host: &str, port: u16, key: &str) { + match parent_exe_name() { + // If shell is csh or tcsh, we want to print differently + Some(s) if s.eq_ignore_ascii_case("csh") || s.eq_ignore_ascii_case("tcsh") => { + formatter::print_csh_string(host, port, key) + } + + // If shell is fish, we want to print differently + Some(s) if s.eq_ignore_ascii_case("fish") => formatter::print_fish_string(host, port, key), + + // Otherwise, we assume that the shell is compatible with sh (e.g. bash, dash, zsh) + _ => formatter::print_sh_string(host, port, key), + } +} + +/// Prints out shell-specific environment information +#[cfg(windows)] +fn inner_print_environment(host: &str, port: u16, key: &str) { + match parent_exe_name() { + // If shell is powershell, we want to print differently + Some(s) if s.eq_ignore_ascii_case("powershell") => { + formatter::print_powershell_string(host, port, key) + } + + // Otherwise, we assume that the shell was cmd.exe + _ => formatter::print_cmd_exe_string(host, port, key), + } +} + +/// Retrieve the name of the parent process that spawned us +fn parent_exe_name() -> Option { + use sysinfo::{Pid, PidExt, Process, ProcessExt, System, SystemExt}; + + let mut system = System::new(); + + // Get our own process pid + let pid = Pid::from_u32(std::process::id()); + + // Update our system's knowledge about our process + system.refresh_process(pid); + + // Get our parent process' pid and update sustem's knowledge about parent process + let maybe_parent_pid = system.process(pid).and_then(Process::parent); + if let Some(pid) = maybe_parent_pid { + system.refresh_process(pid); + } + + maybe_parent_pid + .and_then(|pid| system.process(pid)) + .map(Process::exe) + .and_then(Path::file_name) + .map(OsStr::to_string_lossy) + .map(|s| s.to_string()) +} + +mod formatter { + use indoc::printdoc; + + /// Prints out a {csh,tcsh}-specific example of setting environment variables + #[cfg(unix)] + pub fn print_csh_string(host: &str, port: u16, key: &str) { + printdoc! {r#" + setenv DISTANT_HOST "{host}" + setenv DISTANT_PORT "{port}" + setenv DISTANT_KEY "{key}" + "#, + host = host, + port = port, + key = key, + } + } + + /// Prints out a fish-specific example of setting environment variables + #[cfg(unix)] + pub fn print_fish_string(host: &str, port: u16, key: &str) { + printdoc! {r#" + # Please export the following variables to use with actions + set -gx DISTANT_HOST {host} + set -gx DISTANT_PORT {port} + set -gx DISTANT_KEY {key} + "#, + host = host, + port = port, + key = key, + } + } + + /// Prints out an sh-compliant example of setting environment variables + #[cfg(unix)] + pub fn print_sh_string(host: &str, port: u16, key: &str) { + printdoc! {r#" + # Please export the following variables to use with actions + export DISTANT_HOST="{host}" + export DISTANT_PORT="{port}" + export DISTANT_KEY="{key}" + "#, + host = host, + port = port, + key = key, + } + } + + /// Prints out a powershell example of setting environment variables + #[cfg(windows)] + pub fn print_powershell_string(host: &str, port: u16, key: &str) { + printdoc! {r#" + # Please export the following variables to use with actions + $Env:DISTANT_HOST = "{host}" + $Env:DISTANT_PORT = "{port}" + $Env:DISTANT_KEY = "{key}" + "#, + host = host, + port = port, + key = key, + } + } + + /// Prints out a command prompt example of setting environment variables + #[cfg(windows)] + pub fn print_cmd_exe_string(host: &str, port: u16, key: &str) { + printdoc! {r#" + REM Please export the following variables to use with actions + SET DISTANT_HOST="{host}" + SET DISTANT_PORT="{port}" + SET DISTANT_KEY="{key}" + "#, + host = host, + port = port, + key = key, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 7eea3c3..a43b096 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ mod buf; mod constants; +mod environment; mod exit; mod link; mod msg; diff --git a/src/opt.rs b/src/opt.rs index 3126627..b520732 100644 --- a/src/opt.rs +++ b/src/opt.rs @@ -359,6 +359,17 @@ impl FromStr for BindAddress { )] #[strum(serialize_all = "snake_case")] pub enum SessionOutput { + /// Session will be exposed as a series of environment variables + /// + /// * `DISTANT_HOST=` + /// * `DISTANT_PORT=` + /// * `DISTANT_KEY=` + /// + /// Note that this does not actually create the environment variables, + /// but instead prints out a message detailing how to set the environment + /// variables, which can be evaluated to set them + Environment, + /// Session is in a file in the form of `DISTANT CONNECT ` File, @@ -378,16 +389,9 @@ pub enum SessionOutput { } impl Default for SessionOutput { - /// For unix-based systems, output defaults to a socket - #[cfg(unix)] + /// Default to environment output fn default() -> Self { - Self::Socket - } - - /// For non-unix-based systems, output defaults to a file - #[cfg(not(unix))] - fn default() -> Self { - Self::File + Self::Environment } } @@ -431,16 +435,9 @@ pub enum SessionInput { } impl Default for SessionInput { - /// For unix-based systems, input defaults to a socket - #[cfg(unix)] - fn default() -> Self { - Self::Socket - } - - /// For non-unix-based systems, input defaults to a file - #[cfg(not(unix))] + /// Default to environment output fn default() -> Self { - Self::File + Self::Environment } } diff --git a/src/subcommand/launch.rs b/src/subcommand/launch.rs index f6dbb56..caae9ed 100644 --- a/src/subcommand/launch.rs +++ b/src/subcommand/launch.rs @@ -1,4 +1,5 @@ use crate::{ + environment, exit::{ExitCode, ExitCodeError}, msg::{MsgReceiver, MsgSender}, opt::{CommonOpt, Format, LaunchSubcommand, SessionOutput}, @@ -51,6 +52,10 @@ pub fn run(cmd: LaunchSubcommand, opt: CommonOpt) -> Result<(), Error> { // Handle sharing resulting session in different ways match session_output { + SessionOutput::Environment => { + debug!("Outputting session to environment"); + environment::print_environment(&session) + } SessionOutput::File => { debug!("Outputting session to {:?}", session_file); rt.block_on(async { SessionInfoFile::new(session_file, session).save().await })?