mirror of https://github.com/chipsenkbeil/distant
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
668 lines
20 KiB
Rust
668 lines
20 KiB
Rust
use crate::{
|
|
constants::{
|
|
SERVER_CONN_MSG_CAPACITY_STR, SESSION_FILE_PATH_STR, SESSION_SOCKET_PATH_STR, TIMEOUT_STR,
|
|
},
|
|
exit::ExitCodeError,
|
|
subcommand,
|
|
};
|
|
use derive_more::{Display, Error, From, IsVariant};
|
|
use distant_core::{PortRange, RequestData};
|
|
use once_cell::sync::Lazy;
|
|
use std::{
|
|
env,
|
|
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr},
|
|
path::PathBuf,
|
|
str::FromStr,
|
|
time::Duration,
|
|
};
|
|
use structopt::StructOpt;
|
|
use strum::{EnumString, EnumVariantNames, IntoStaticStr, VariantNames};
|
|
|
|
static USERNAME: Lazy<String> = Lazy::new(whoami::username);
|
|
|
|
/// Options and commands to apply to binary
|
|
#[derive(Debug, StructOpt)]
|
|
#[structopt(name = "distant")]
|
|
pub struct Opt {
|
|
#[structopt(flatten)]
|
|
pub common: CommonOpt,
|
|
|
|
#[structopt(subcommand)]
|
|
pub subcommand: Subcommand,
|
|
}
|
|
|
|
impl Opt {
|
|
/// Loads options from CLI arguments
|
|
pub fn load() -> Self {
|
|
Self::from_args()
|
|
}
|
|
}
|
|
|
|
#[derive(
|
|
Copy,
|
|
Clone,
|
|
Debug,
|
|
Display,
|
|
PartialEq,
|
|
Eq,
|
|
IsVariant,
|
|
IntoStaticStr,
|
|
EnumString,
|
|
EnumVariantNames,
|
|
)]
|
|
#[strum(serialize_all = "snake_case")]
|
|
pub enum LogLevel {
|
|
Off,
|
|
Error,
|
|
Warn,
|
|
Info,
|
|
Debug,
|
|
Trace,
|
|
}
|
|
|
|
impl LogLevel {
|
|
pub fn to_log_level_filter(self) -> log::LevelFilter {
|
|
match self {
|
|
Self::Off => log::LevelFilter::Off,
|
|
Self::Error => log::LevelFilter::Error,
|
|
Self::Warn => log::LevelFilter::Warn,
|
|
Self::Info => log::LevelFilter::Info,
|
|
Self::Debug => log::LevelFilter::Debug,
|
|
Self::Trace => log::LevelFilter::Trace,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Contains options that are common across subcommands
|
|
#[derive(Debug, StructOpt)]
|
|
pub struct CommonOpt {
|
|
/// Quiet mode, suppresses all logging (shortcut for log level off)
|
|
#[structopt(short, long, global = true)]
|
|
pub quiet: bool,
|
|
|
|
/// Log level to use throughout the application
|
|
#[structopt(
|
|
long,
|
|
global = true,
|
|
case_insensitive = true,
|
|
default_value = LogLevel::Info.into(),
|
|
possible_values = LogLevel::VARIANTS
|
|
)]
|
|
pub log_level: LogLevel,
|
|
|
|
/// Log output to disk instead of stderr
|
|
#[structopt(long, global = true)]
|
|
pub log_file: Option<PathBuf>,
|
|
|
|
/// Represents the maximum time (in seconds) to wait for a network
|
|
/// request before timing out; a timeout of 0 implies waiting indefinitely
|
|
#[structopt(short, long, global = true, default_value = &TIMEOUT_STR)]
|
|
pub timeout: f32,
|
|
}
|
|
|
|
impl CommonOpt {
|
|
/// Creates a new duration representing the timeout in seconds
|
|
pub fn to_timeout_duration(&self) -> Duration {
|
|
Duration::from_secs_f32(self.timeout)
|
|
}
|
|
}
|
|
|
|
/// Contains options related sessions
|
|
#[derive(Debug, StructOpt)]
|
|
pub struct SessionOpt {
|
|
/// Represents the location of the file containing session information,
|
|
/// only useful when the session is set to "file"
|
|
#[structopt(long, default_value = &SESSION_FILE_PATH_STR)]
|
|
pub session_file: PathBuf,
|
|
|
|
/// Represents the location of the session's socket to communicate across,
|
|
/// only useful when the session is set to "socket"
|
|
#[structopt(long, default_value = &SESSION_SOCKET_PATH_STR)]
|
|
pub session_socket: PathBuf,
|
|
}
|
|
|
|
/// Contains options related ssh
|
|
#[derive(Clone, Debug, StructOpt)]
|
|
pub struct SshConnectionOpts {
|
|
/// Host to use for connection to when using SSH method
|
|
#[structopt(name = "ssh-host", long, default_value = "localhost")]
|
|
pub host: String,
|
|
|
|
/// Port to use for connection when using SSH method
|
|
#[structopt(name = "ssh-port", long, default_value = "22")]
|
|
pub port: u16,
|
|
|
|
/// Alternative user for connection when using SSH method
|
|
#[structopt(name = "ssh-user", long)]
|
|
pub user: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, StructOpt)]
|
|
pub enum Subcommand {
|
|
/// Performs some action on a remote machine
|
|
Action(ActionSubcommand),
|
|
|
|
/// Launches the server-portion of the binary on a remote machine
|
|
Launch(LaunchSubcommand),
|
|
|
|
/// Begins listening for incoming requests
|
|
Listen(ListenSubcommand),
|
|
|
|
/// Specialized treatment of running a remote LSP process
|
|
Lsp(LspSubcommand),
|
|
}
|
|
|
|
impl Subcommand {
|
|
/// Runs the subcommand, returning the result
|
|
pub fn run(self, opt: CommonOpt) -> Result<(), Box<dyn ExitCodeError>> {
|
|
match self {
|
|
Self::Action(cmd) => subcommand::action::run(cmd, opt)?,
|
|
Self::Launch(cmd) => subcommand::launch::run(cmd, opt)?,
|
|
Self::Listen(cmd) => subcommand::listen::run(cmd, opt)?,
|
|
Self::Lsp(cmd) => subcommand::lsp::run(cmd, opt)?,
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns true if subcommand simplifies to acting as a proxy for a remote process
|
|
pub fn is_remote_process(&self) -> bool {
|
|
match self {
|
|
Self::Action(cmd) => cmd
|
|
.operation
|
|
.as_ref()
|
|
.map(|req| req.is_proc_run())
|
|
.unwrap_or_default(),
|
|
Self::Lsp(_) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Represents the method to use in communicating with a remote machine
|
|
#[derive(
|
|
Copy,
|
|
Clone,
|
|
Debug,
|
|
Display,
|
|
PartialEq,
|
|
Eq,
|
|
IsVariant,
|
|
IntoStaticStr,
|
|
EnumString,
|
|
EnumVariantNames,
|
|
)]
|
|
#[strum(serialize_all = "snake_case")]
|
|
pub enum Method {
|
|
/// Launch/connect to a distant server running on a remote machine
|
|
Distant,
|
|
|
|
/// Connect to an SSH server running on a remote machine
|
|
#[cfg(feature = "ssh2")]
|
|
Ssh,
|
|
}
|
|
|
|
impl Default for Method {
|
|
fn default() -> Self {
|
|
Self::Distant
|
|
}
|
|
}
|
|
|
|
/// Represents the format for data communicated to & from the client
|
|
#[derive(
|
|
Copy,
|
|
Clone,
|
|
Debug,
|
|
Display,
|
|
PartialEq,
|
|
Eq,
|
|
IsVariant,
|
|
IntoStaticStr,
|
|
EnumString,
|
|
EnumVariantNames,
|
|
)]
|
|
#[strum(serialize_all = "snake_case")]
|
|
pub enum Format {
|
|
/// Sends and receives data in JSON format
|
|
Json,
|
|
|
|
/// Commands are traditional shell commands and output responses are
|
|
/// inline with what is expected of a program's output in a shell
|
|
Shell,
|
|
}
|
|
|
|
/// Represents subcommand to execute some operation remotely
|
|
#[derive(Debug, StructOpt)]
|
|
#[structopt(verbatim_doc_comment)]
|
|
pub struct ActionSubcommand {
|
|
/// Represents the format that results should be returned
|
|
///
|
|
/// Currently, there are two possible formats:
|
|
///
|
|
/// 1. "json": printing out JSON for external program usage
|
|
///
|
|
/// 2. "shell": printing out human-readable results for interactive shell usage
|
|
#[structopt(
|
|
short,
|
|
long,
|
|
case_insensitive = true,
|
|
default_value = Format::Shell.into(),
|
|
possible_values = Format::VARIANTS
|
|
)]
|
|
pub format: Format,
|
|
|
|
/// Method to communicate with a remote machine
|
|
#[structopt(
|
|
short,
|
|
long,
|
|
case_insensitive = true,
|
|
default_value = Method::default().into(),
|
|
possible_values = Method::VARIANTS
|
|
)]
|
|
pub method: Method,
|
|
|
|
/// Represents the medium for retrieving a session for use in performing the action
|
|
#[structopt(
|
|
long,
|
|
case_insensitive = true,
|
|
default_value = SessionInput::default().into(),
|
|
possible_values = SessionInput::VARIANTS
|
|
)]
|
|
pub session: SessionInput,
|
|
|
|
/// Contains additional information related to sessions
|
|
#[structopt(flatten)]
|
|
pub session_data: SessionOpt,
|
|
|
|
/// SSH connection settings when method is ssh
|
|
#[structopt(flatten)]
|
|
pub ssh_connection: SshConnectionOpts,
|
|
|
|
/// If specified, commands to send are sent over stdin and responses are received
|
|
/// over stdout (and stderr if mode is shell)
|
|
#[structopt(short, long)]
|
|
pub interactive: bool,
|
|
|
|
/// Operation to send over the wire if not in interactive mode
|
|
#[structopt(subcommand)]
|
|
pub operation: Option<RequestData>,
|
|
}
|
|
|
|
/// Represents options for binding a server to an IP address
|
|
#[derive(Copy, Clone, Debug, Display, PartialEq, Eq, IsVariant)]
|
|
pub enum BindAddress {
|
|
#[display(fmt = "ssh")]
|
|
Ssh,
|
|
#[display(fmt = "any")]
|
|
Any,
|
|
Ip(IpAddr),
|
|
}
|
|
|
|
#[derive(Clone, Debug, Display, From, Error, PartialEq, Eq)]
|
|
pub enum ConvertToIpAddrError {
|
|
AddrParseError(AddrParseError),
|
|
#[display(fmt = "SSH_CONNECTION missing 3rd argument (host ip)")]
|
|
MissingSshAddr,
|
|
VarError(env::VarError),
|
|
}
|
|
|
|
impl BindAddress {
|
|
/// Converts address into valid IP; in the case of "any", will leverage the
|
|
/// `use_ipv6` flag to determine if binding should use ipv4 or ipv6
|
|
pub fn to_ip_addr(self, use_ipv6: bool) -> Result<IpAddr, ConvertToIpAddrError> {
|
|
match self {
|
|
Self::Ssh => {
|
|
let ssh_connection = env::var("SSH_CONNECTION")?;
|
|
let ip_str = ssh_connection
|
|
.split(' ')
|
|
.nth(2)
|
|
.ok_or(ConvertToIpAddrError::MissingSshAddr)?;
|
|
let ip = ip_str.parse::<IpAddr>()?;
|
|
Ok(ip)
|
|
}
|
|
Self::Any if use_ipv6 => Ok(IpAddr::V6(Ipv6Addr::UNSPECIFIED)),
|
|
Self::Any => Ok(IpAddr::V4(Ipv4Addr::UNSPECIFIED)),
|
|
Self::Ip(addr) => Ok(addr),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromStr for BindAddress {
|
|
type Err = AddrParseError;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
match s.trim() {
|
|
"ssh" => Ok(Self::Ssh),
|
|
"any" => Ok(Self::Any),
|
|
"localhost" => Ok(Self::Ip(IpAddr::V4(Ipv4Addr::LOCALHOST))),
|
|
x => Ok(Self::Ip(x.parse::<IpAddr>()?)),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Represents the means by which to share the session from launching on a remote machine
|
|
#[derive(
|
|
Copy,
|
|
Clone,
|
|
Debug,
|
|
Display,
|
|
PartialEq,
|
|
Eq,
|
|
IntoStaticStr,
|
|
IsVariant,
|
|
EnumString,
|
|
EnumVariantNames,
|
|
)]
|
|
#[strum(serialize_all = "snake_case")]
|
|
pub enum SessionOutput {
|
|
/// Session is in a file in the form of `DISTANT DATA <host> <port> <key>`
|
|
File,
|
|
|
|
/// Special scenario where the session is not shared but is instead kept within the
|
|
/// launch program, causing the program itself to listen on stdin for input rather
|
|
/// than terminating
|
|
Keep,
|
|
|
|
/// Session is stored and retrieved over anonymous pipes (stdout/stdin)
|
|
/// in form of `DISTANT DATA <host> <port> <key>`
|
|
Pipe,
|
|
|
|
/// Special scenario where the session is not shared but is instead kept within the
|
|
/// launch program, where the program now listens on a unix socket for input
|
|
#[cfg(unix)]
|
|
Socket,
|
|
}
|
|
|
|
impl Default for SessionOutput {
|
|
/// For unix-based systems, output defaults to a socket
|
|
#[cfg(unix)]
|
|
fn default() -> Self {
|
|
Self::Socket
|
|
}
|
|
|
|
/// For non-unix-based systems, output defaults to a file
|
|
#[cfg(not(unix))]
|
|
fn default() -> Self {
|
|
Self::File
|
|
}
|
|
}
|
|
|
|
/// Represents the means by which to consume a session when performing an action
|
|
#[derive(
|
|
Copy,
|
|
Clone,
|
|
Debug,
|
|
Display,
|
|
PartialEq,
|
|
Eq,
|
|
IntoStaticStr,
|
|
IsVariant,
|
|
EnumString,
|
|
EnumVariantNames,
|
|
)]
|
|
#[strum(serialize_all = "snake_case")]
|
|
pub enum SessionInput {
|
|
/// Session is in a environment variables
|
|
///
|
|
/// * `DISTANT_HOST=<host>`
|
|
/// * `DISTANT_PORT=<port>`
|
|
/// * `DISTANT_KEY=<key>`
|
|
Environment,
|
|
|
|
/// Session is in a file in the form of `DISTANT DATA <host> <port> <key>`
|
|
File,
|
|
|
|
/// Session is stored and retrieved over anonymous pipes (stdout/stdin)
|
|
/// in form of `DISTANT DATA <host> <port> <key>`
|
|
Pipe,
|
|
|
|
/// Session is stored and retrieved from the initializeOptions of the initialize request
|
|
/// that is first sent for an LSP through
|
|
Lsp,
|
|
|
|
/// Session isn't directly available but instead there is a process listening
|
|
/// on a unix socket that will forward requests and responses
|
|
#[cfg(unix)]
|
|
Socket,
|
|
}
|
|
|
|
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))]
|
|
fn default() -> Self {
|
|
Self::File
|
|
}
|
|
}
|
|
|
|
/// Represents subcommand to launch a remote server
|
|
#[derive(Debug, StructOpt)]
|
|
pub struct LaunchSubcommand {
|
|
/// Represents the medium for sharing the session upon launching on a remote machine
|
|
#[structopt(
|
|
long,
|
|
default_value = SessionOutput::default().into(),
|
|
possible_values = SessionOutput::VARIANTS
|
|
)]
|
|
pub session: SessionOutput,
|
|
|
|
/// Contains additional information related to sessions
|
|
#[structopt(flatten)]
|
|
pub session_data: SessionOpt,
|
|
|
|
/// If specified, launch will fail when attempting to bind to a unix socket that
|
|
/// already exists, rather than removing the old socket
|
|
#[structopt(long)]
|
|
pub fail_if_socket_exists: bool,
|
|
|
|
/// The time in seconds before shutting down the server if there are no active
|
|
/// connections. The countdown begins once all connections have closed and
|
|
/// stops when a new connection is made. In not specified, the server will not
|
|
/// shutdown at any point when there are no active connections.
|
|
///
|
|
/// In the case of launch, this is only applicable when it is set to socket session
|
|
/// as this controls when the unix socket listener would shutdown, not when the
|
|
/// remote server it is connected to will shutdown.
|
|
///
|
|
/// To configure the remote server's shutdown time, provide it as an argument
|
|
/// via `--extra-server-args`
|
|
#[structopt(long)]
|
|
pub shutdown_after: Option<f32>,
|
|
|
|
/// Runs in background via daemon-mode (does nothing on windows); only applies
|
|
/// when session is socket
|
|
#[structopt(short, long)]
|
|
pub daemon: bool,
|
|
|
|
/// Represents the format that results should be returned when session is "keep",
|
|
/// causing the launcher to enter an interactive loop to handle input and output
|
|
/// itself rather than enabling other clients to connect
|
|
#[structopt(
|
|
short,
|
|
long,
|
|
case_insensitive = true,
|
|
default_value = Format::Shell.into(),
|
|
possible_values = Format::VARIANTS
|
|
)]
|
|
pub format: Format,
|
|
|
|
/// Path to distant program on remote machine to execute via ssh;
|
|
/// by default, this program needs to be available within PATH as
|
|
/// specified when compiling ssh (not your login shell)
|
|
#[structopt(long, default_value = "distant")]
|
|
pub distant: String,
|
|
|
|
/// Path to ssh program on local machine to execute
|
|
#[structopt(long, default_value = "ssh")]
|
|
pub ssh: String,
|
|
|
|
/// Control the IP address that the server binds to.
|
|
///
|
|
/// The default is `ssh', in which case the server will reply from the IP address that the SSH
|
|
/// connection came from (as found in the SSH_CONNECTION environment variable). This is
|
|
/// useful for multihomed servers.
|
|
///
|
|
/// With --bind-server=any, the server will reply on the default interface and will not bind to
|
|
/// a particular IP address. This can be useful if the connection is made through sslh or
|
|
/// another tool that makes the SSH connection appear to come from localhost.
|
|
///
|
|
/// With --bind-server=IP, the server will attempt to bind to the specified IP address.
|
|
#[structopt(long, value_name = "ssh|any|IP", default_value = "ssh")]
|
|
pub bind_server: BindAddress,
|
|
|
|
/// Additional arguments to provide to the server
|
|
#[structopt(long, allow_hyphen_values(true))]
|
|
pub extra_server_args: Option<String>,
|
|
|
|
/// Username to use when sshing into remote machine
|
|
#[structopt(short, long, default_value = &USERNAME)]
|
|
pub username: String,
|
|
|
|
/// Explicit identity file to use with ssh
|
|
#[structopt(short, long)]
|
|
pub identity_file: Option<PathBuf>,
|
|
|
|
/// If specified, will not launch distant using a login shell but instead execute it directly
|
|
#[structopt(long)]
|
|
pub no_shell: bool,
|
|
|
|
/// Port to use for sshing into the remote machine
|
|
#[structopt(short, long, default_value = "22")]
|
|
pub port: u16,
|
|
|
|
/// Host to use for sshing into the remote machine
|
|
#[structopt(name = "HOST")]
|
|
pub host: String,
|
|
}
|
|
|
|
impl LaunchSubcommand {
|
|
/// Creates a new duration representing the shutdown period in seconds
|
|
pub fn to_shutdown_after_duration(&self) -> Option<Duration> {
|
|
self.shutdown_after
|
|
.as_ref()
|
|
.copied()
|
|
.map(Duration::from_secs_f32)
|
|
}
|
|
}
|
|
|
|
/// Represents subcommand to operate in listen mode for incoming requests
|
|
#[derive(Debug, StructOpt)]
|
|
pub struct ListenSubcommand {
|
|
/// Runs in background via daemon-mode (does nothing on windows)
|
|
#[structopt(short, long)]
|
|
pub daemon: bool,
|
|
|
|
/// Control the IP address that the distant binds to
|
|
///
|
|
/// There are three options here:
|
|
///
|
|
/// 1. `ssh`: the server will reply from the IP address that the SSH
|
|
/// connection came from (as found in the SSH_CONNECTION environment variable). This is
|
|
/// useful for multihomed servers.
|
|
///
|
|
/// 2. `any`: the server will reply on the default interface and will not bind to
|
|
/// a particular IP address. This can be useful if the connection is made through sslh or
|
|
/// another tool that makes the SSH connection appear to come from localhost.
|
|
///
|
|
/// 3. `IP`: the server will attempt to bind to the specified IP address.
|
|
#[structopt(short, long, value_name = "ssh|any|IP", default_value = "localhost")]
|
|
pub host: BindAddress,
|
|
|
|
/// If specified, will bind to the ipv6 interface if host is "any" instead of ipv4
|
|
#[structopt(short = "6", long)]
|
|
pub use_ipv6: bool,
|
|
|
|
/// Maximum capacity for concurrent message handled by the server
|
|
#[structopt(long, default_value = &SERVER_CONN_MSG_CAPACITY_STR)]
|
|
pub max_msg_capacity: u16,
|
|
|
|
/// The time in seconds before shutting down the server if there are no active
|
|
/// connections. The countdown begins once all connections have closed and
|
|
/// stops when a new connection is made. In not specified, the server will not
|
|
/// shutdown at any point when there are no active connections.
|
|
#[structopt(long)]
|
|
pub shutdown_after: Option<f32>,
|
|
|
|
/// Changes the current working directory (cwd) to the specified directory
|
|
#[structopt(long)]
|
|
pub current_dir: Option<PathBuf>,
|
|
|
|
/// Set the port(s) that the server will attempt to bind to
|
|
///
|
|
/// This can be in the form of PORT1 or PORT1:PORTN to provide a range of ports.
|
|
/// With -p 0, the server will let the operating system pick an available TCP port.
|
|
///
|
|
/// Please note that this option does not affect the server-side port used by SSH
|
|
#[structopt(short, long, value_name = "PORT[:PORT2]", default_value = "8080:8099")]
|
|
pub port: PortRange,
|
|
}
|
|
|
|
impl ListenSubcommand {
|
|
/// Creates a new duration representing the shutdown period in seconds
|
|
pub fn to_shutdown_after_duration(&self) -> Option<Duration> {
|
|
self.shutdown_after
|
|
.as_ref()
|
|
.copied()
|
|
.map(Duration::from_secs_f32)
|
|
}
|
|
}
|
|
|
|
/// Represents subcommand to execute some LSP server on a remote machine
|
|
#[derive(Debug, StructOpt)]
|
|
#[structopt(verbatim_doc_comment)]
|
|
pub struct LspSubcommand {
|
|
/// Represents the format that results should be returned
|
|
///
|
|
/// Currently, there are two possible formats:
|
|
///
|
|
/// 1. "json": printing out JSON for external program usage
|
|
///
|
|
/// 2. "shell": printing out human-readable results for interactive shell usage
|
|
#[structopt(
|
|
short,
|
|
long,
|
|
case_insensitive = true,
|
|
default_value = Format::Shell.into(),
|
|
possible_values = Format::VARIANTS
|
|
)]
|
|
pub format: Format,
|
|
|
|
/// Method to communicate with a remote machine
|
|
#[structopt(
|
|
short,
|
|
long,
|
|
case_insensitive = true,
|
|
default_value = Method::default().into(),
|
|
possible_values = Method::VARIANTS
|
|
)]
|
|
pub method: Method,
|
|
|
|
/// Represents the medium for retrieving a session to use when running a remote LSP server
|
|
#[structopt(
|
|
long,
|
|
case_insensitive = true,
|
|
default_value = SessionInput::default().into(),
|
|
possible_values = SessionInput::VARIANTS
|
|
)]
|
|
pub session: SessionInput,
|
|
|
|
/// Contains additional information related to sessions
|
|
#[structopt(flatten)]
|
|
pub session_data: SessionOpt,
|
|
|
|
/// SSH connection settings when method is ssh
|
|
#[structopt(flatten)]
|
|
pub ssh_connection: SshConnectionOpts,
|
|
|
|
/// Command to run on the remote machine that represents an LSP server
|
|
pub cmd: String,
|
|
|
|
/// Additional arguments to supply to the remote machine
|
|
pub args: Vec<String>,
|
|
}
|