mirror of
https://github.com/wez/wezterm.git
synced 2024-12-23 21:32:13 +03:00
gui: allow wezterm connect sshdomnomux
If an ssh domain is set to use_multiplexer=false, it is now possible to `wezterm connect` to it. Previously, it was only possible to connect to domains that used the mux client. refs: https://github.com/wez/wezterm/issues/1456
This commit is contained in:
parent
0676eed42f
commit
f84c0632d8
@ -7,6 +7,7 @@ use crate::window::WindowId;
|
|||||||
use crate::Mux;
|
use crate::Mux;
|
||||||
use anyhow::{anyhow, bail, Context, Error};
|
use anyhow::{anyhow, bail, Context, Error};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use config::{SshBackend, SshDomain};
|
||||||
use filedescriptor::{poll, pollfd, socketpair, AsRawSocketDescriptor, FileDescriptor, POLLIN};
|
use filedescriptor::{poll, pollfd, socketpair, AsRawSocketDescriptor, FileDescriptor, POLLIN};
|
||||||
use portable_pty::cmdbuilder::CommandBuilder;
|
use portable_pty::cmdbuilder::CommandBuilder;
|
||||||
use portable_pty::{ChildKiller, ExitStatus, MasterPty, PtySize};
|
use portable_pty::{ChildKiller, ExitStatus, MasterPty, PtySize};
|
||||||
@ -134,11 +135,58 @@ pub fn ssh_connect_with_ui(
|
|||||||
/// interactive setup. The bulk of that is driven by `connect_ssh_session`.
|
/// interactive setup. The bulk of that is driven by `connect_ssh_session`.
|
||||||
pub struct RemoteSshDomain {
|
pub struct RemoteSshDomain {
|
||||||
session: RefCell<Option<Session>>,
|
session: RefCell<Option<Session>>,
|
||||||
ssh_config: ConfigMap,
|
config: RemoteSshConfig,
|
||||||
id: DomainId,
|
id: DomainId,
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum RemoteSshConfig {
|
||||||
|
AdHoc(ConfigMap),
|
||||||
|
Domain(SshDomain),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ssh_domain_to_ssh_config(ssh_dom: &SshDomain) -> anyhow::Result<ConfigMap> {
|
||||||
|
let mut ssh_config = wezterm_ssh::Config::new();
|
||||||
|
ssh_config.add_default_config_files();
|
||||||
|
|
||||||
|
let (remote_host_name, port) = {
|
||||||
|
let parts: Vec<&str> = ssh_dom.remote_address.split(':').collect();
|
||||||
|
|
||||||
|
if parts.len() == 2 {
|
||||||
|
(parts[0], Some(parts[1].parse::<u16>()?))
|
||||||
|
} else {
|
||||||
|
(ssh_dom.remote_address.as_str(), None)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ssh_config = ssh_config.for_host(&remote_host_name);
|
||||||
|
ssh_config.insert(
|
||||||
|
"wezterm_ssh_backend".to_string(),
|
||||||
|
match ssh_dom
|
||||||
|
.ssh_backend
|
||||||
|
.unwrap_or_else(|| config::configuration().ssh_backend)
|
||||||
|
{
|
||||||
|
SshBackend::Ssh2 => "ssh2",
|
||||||
|
SshBackend::LibSsh => "libssh",
|
||||||
|
}
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
for (k, v) in &ssh_dom.ssh_option {
|
||||||
|
ssh_config.insert(k.to_string(), v.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(username) = &ssh_dom.username {
|
||||||
|
ssh_config.insert("user".to_string(), username.to_string());
|
||||||
|
}
|
||||||
|
if let Some(port) = port {
|
||||||
|
ssh_config.insert("port".to_string(), port.to_string());
|
||||||
|
}
|
||||||
|
if ssh_dom.no_agent_auth {
|
||||||
|
ssh_config.insert("identitiesonly".to_string(), "yes".to_string());
|
||||||
|
}
|
||||||
|
Ok(ssh_config)
|
||||||
|
}
|
||||||
|
|
||||||
impl RemoteSshDomain {
|
impl RemoteSshDomain {
|
||||||
pub fn with_ssh_config(name: &str, ssh_config: ConfigMap) -> anyhow::Result<Self> {
|
pub fn with_ssh_config(name: &str, ssh_config: ConfigMap) -> anyhow::Result<Self> {
|
||||||
let id = alloc_domain_id();
|
let id = alloc_domain_id();
|
||||||
@ -146,9 +194,26 @@ impl RemoteSshDomain {
|
|||||||
id,
|
id,
|
||||||
name: format!("SSH to {}", name),
|
name: format!("SSH to {}", name),
|
||||||
session: RefCell::new(None),
|
session: RefCell::new(None),
|
||||||
ssh_config,
|
config: RemoteSshConfig::AdHoc(ssh_config),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_ssh_domain(dom: &SshDomain) -> anyhow::Result<Self> {
|
||||||
|
let id = alloc_domain_id();
|
||||||
|
Ok(Self {
|
||||||
|
id,
|
||||||
|
name: dom.name.clone(),
|
||||||
|
session: RefCell::new(None),
|
||||||
|
config: RemoteSshConfig::Domain(dom.clone()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ssh_config(&self) -> anyhow::Result<ConfigMap> {
|
||||||
|
match &self.config {
|
||||||
|
RemoteSshConfig::AdHoc(config) => Ok(config.clone()),
|
||||||
|
RemoteSshConfig::Domain(dom) => ssh_domain_to_ssh_config(dom),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Carry out the authentication process and create the initial pty.
|
/// Carry out the authentication process and create the initial pty.
|
||||||
@ -475,7 +540,7 @@ impl Domain for RemoteSshDomain {
|
|||||||
writer = Box::new(pty.try_clone_writer()?);
|
writer = Box::new(pty.try_clone_writer()?);
|
||||||
} else {
|
} else {
|
||||||
// We're starting the session
|
// We're starting the session
|
||||||
let (session, events) = Session::connect(self.ssh_config.clone())?;
|
let (session, events) = Session::connect(self.ssh_config()?)?;
|
||||||
self.session.borrow_mut().replace(session.clone());
|
self.session.borrow_mut().replace(session.clone());
|
||||||
|
|
||||||
// We get to establish the session!
|
// We get to establish the session!
|
||||||
@ -682,11 +747,10 @@ impl Domain for RemoteSshDomain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn state(&self) -> DomainState {
|
fn state(&self) -> DomainState {
|
||||||
if self.session.borrow().is_some() {
|
// Just pretend that we are always attached, as we don't
|
||||||
|
// have a defined attach operation that is distinct from
|
||||||
|
// a spawn.
|
||||||
DomainState::Attached
|
DomainState::Attached
|
||||||
} else {
|
|
||||||
DomainState::Detached
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use anyhow::{anyhow, bail, Context};
|
|||||||
use async_ossl::AsyncSslStream;
|
use async_ossl::AsyncSslStream;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use codec::*;
|
use codec::*;
|
||||||
use config::{configuration, SshBackend, SshDomain, TlsDomainClient, UnixDomain, UnixTarget};
|
use config::{configuration, SshDomain, TlsDomainClient, UnixDomain, UnixTarget};
|
||||||
use filedescriptor::FileDescriptor;
|
use filedescriptor::FileDescriptor;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use mux::connui::ConnectionUI;
|
use mux::connui::ConnectionUI;
|
||||||
@ -502,44 +502,7 @@ impl Reconnectable {
|
|||||||
initial: bool,
|
initial: bool,
|
||||||
ui: &mut ConnectionUI,
|
ui: &mut ConnectionUI,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let mut ssh_config = wezterm_ssh::Config::new();
|
let ssh_config = mux::ssh::ssh_domain_to_ssh_config(&ssh_dom)?;
|
||||||
ssh_config.add_default_config_files();
|
|
||||||
|
|
||||||
let (remote_host_name, port) = {
|
|
||||||
let parts: Vec<&str> = ssh_dom.remote_address.split(':').collect();
|
|
||||||
|
|
||||||
if parts.len() == 2 {
|
|
||||||
(parts[0], Some(parts[1].parse::<u16>()?))
|
|
||||||
} else {
|
|
||||||
(ssh_dom.remote_address.as_str(), None)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut ssh_config = ssh_config.for_host(&remote_host_name);
|
|
||||||
ssh_config.insert(
|
|
||||||
"wezterm_ssh_backend".to_string(),
|
|
||||||
match ssh_dom
|
|
||||||
.ssh_backend
|
|
||||||
.unwrap_or_else(|| configuration().ssh_backend)
|
|
||||||
{
|
|
||||||
SshBackend::Ssh2 => "ssh2",
|
|
||||||
SshBackend::LibSsh => "libssh",
|
|
||||||
}
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
for (k, v) in &ssh_dom.ssh_option {
|
|
||||||
ssh_config.insert(k.to_string(), v.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(username) = &ssh_dom.username {
|
|
||||||
ssh_config.insert("user".to_string(), username.to_string());
|
|
||||||
}
|
|
||||||
if let Some(port) = port {
|
|
||||||
ssh_config.insert("port".to_string(), port.to_string());
|
|
||||||
}
|
|
||||||
if ssh_dom.no_agent_auth {
|
|
||||||
ssh_config.insert("identitiesonly".to_string(), "yes".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
let sess = ssh_connect_with_ui(ssh_config, ui)?;
|
let sess = ssh_connect_with_ui(ssh_config, ui)?;
|
||||||
let proxy_bin = Self::wezterm_bin_path(&ssh_dom.remote_wezterm_path);
|
let proxy_bin = Self::wezterm_bin_path(&ssh_dom.remote_wezterm_path);
|
||||||
|
@ -6,6 +6,7 @@ use anyhow::{anyhow, Context};
|
|||||||
use config::{ConfigHandle, SshBackend};
|
use config::{ConfigHandle, SshBackend};
|
||||||
use mux::activity::Activity;
|
use mux::activity::Activity;
|
||||||
use mux::domain::{Domain, LocalDomain};
|
use mux::domain::{Domain, LocalDomain};
|
||||||
|
use mux::ssh::RemoteSshDomain;
|
||||||
use mux::Mux;
|
use mux::Mux;
|
||||||
use portable_pty::cmdbuilder::CommandBuilder;
|
use portable_pty::cmdbuilder::CommandBuilder;
|
||||||
use promise::spawn::block_on;
|
use promise::spawn::block_on;
|
||||||
@ -240,17 +241,14 @@ async fn async_run_with_domain_as_default(
|
|||||||
spawn_tab_in_default_domain_if_mux_is_empty(cmd).await
|
spawn_tab_in_default_domain_if_mux_is_empty(cmd).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn async_run_mux_client(
|
async fn async_run_mux_client(opts: ConnectCommand) -> anyhow::Result<()> {
|
||||||
config: config::ConfigHandle,
|
|
||||||
opts: ConnectCommand,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
if let Some(cls) = opts.class.as_ref() {
|
if let Some(cls) = opts.class.as_ref() {
|
||||||
crate::set_window_class(cls);
|
crate::set_window_class(cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
let client_config = client_domains(&config)
|
let domain = Mux::get()
|
||||||
.into_iter()
|
.unwrap()
|
||||||
.find(|c| c.name() == opts.domain_name)
|
.get_domain_by_name(&opts.domain_name)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
anyhow!(
|
anyhow!(
|
||||||
"no multiplexer domain with name `{}` was found in the configuration",
|
"no multiplexer domain with name `{}` was found in the configuration",
|
||||||
@ -258,7 +256,6 @@ async fn async_run_mux_client(
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let domain: Arc<dyn Domain> = Arc::new(ClientDomain::new(client_config));
|
|
||||||
let opts = opts.clone();
|
let opts = opts.clone();
|
||||||
let cmd = if !opts.prog.is_empty() {
|
let cmd = if !opts.prog.is_empty() {
|
||||||
let builder = CommandBuilder::from_argv(opts.prog);
|
let builder = CommandBuilder::from_argv(opts.prog);
|
||||||
@ -270,12 +267,12 @@ async fn async_run_mux_client(
|
|||||||
async_run_with_domain_as_default(domain, cmd).await
|
async_run_with_domain_as_default(domain, cmd).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_mux_client(config: config::ConfigHandle, opts: ConnectCommand) -> anyhow::Result<()> {
|
fn run_mux_client(opts: ConnectCommand) -> anyhow::Result<()> {
|
||||||
let activity = Activity::new();
|
let activity = Activity::new();
|
||||||
build_initial_mux(&config::configuration(), None)?;
|
build_initial_mux(&config::configuration(), None)?;
|
||||||
let gui = crate::frontend::try_new()?;
|
let gui = crate::frontend::try_new()?;
|
||||||
promise::spawn::spawn(async {
|
promise::spawn::spawn(async {
|
||||||
if let Err(err) = async_run_mux_client(config, opts).await {
|
if let Err(err) = async_run_mux_client(opts).await {
|
||||||
terminate_with_error(err);
|
terminate_with_error(err);
|
||||||
}
|
}
|
||||||
drop(activity);
|
drop(activity);
|
||||||
@ -327,18 +324,26 @@ async fn spawn_tab_in_default_domain_if_mux_is_empty(
|
|||||||
fn update_mux_domains(config: &ConfigHandle) -> anyhow::Result<()> {
|
fn update_mux_domains(config: &ConfigHandle) -> anyhow::Result<()> {
|
||||||
let mux = Mux::get().unwrap();
|
let mux = Mux::get().unwrap();
|
||||||
|
|
||||||
fn record_domain(mux: &Rc<Mux>, client: ClientDomain) -> anyhow::Result<Arc<dyn Domain>> {
|
for client_config in client_domains(&config) {
|
||||||
if let Some(domain) = mux.get_domain_by_name(client.domain_name()) {
|
if mux.get_domain_by_name(client_config.name()).is_some() {
|
||||||
Ok(domain)
|
continue;
|
||||||
} else {
|
|
||||||
let domain: Arc<dyn Domain> = Arc::new(client);
|
|
||||||
mux.add_domain(&domain);
|
|
||||||
Ok(domain)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for client_config in client_domains(&config) {
|
let domain: Arc<dyn Domain> = Arc::new(ClientDomain::new(client_config));
|
||||||
record_domain(&mux, ClientDomain::new(client_config))?;
|
mux.add_domain(&domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
for ssh_dom in &config.ssh_domains {
|
||||||
|
if ssh_dom.use_multiplexer {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if mux.get_domain_by_name(&ssh_dom.name).is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let domain: Arc<dyn Domain> = Arc::new(RemoteSshDomain::with_ssh_domain(&ssh_dom)?);
|
||||||
|
mux.add_domain(&domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
for wsl_dom in &config.wsl_domains {
|
for wsl_dom in &config.wsl_domains {
|
||||||
@ -860,7 +865,7 @@ fn run() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
SubCommand::Ssh(ssh) => run_ssh(ssh),
|
SubCommand::Ssh(ssh) => run_ssh(ssh),
|
||||||
SubCommand::Serial(serial) => run_serial(config, &serial),
|
SubCommand::Serial(serial) => run_serial(config, &serial),
|
||||||
SubCommand::Connect(connect) => run_mux_client(config, connect),
|
SubCommand::Connect(connect) => run_mux_client(connect),
|
||||||
SubCommand::LsFonts(cmd) => run_ls_fonts(config, &cmd),
|
SubCommand::LsFonts(cmd) => run_ls_fonts(config, &cmd),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user