Support host-config login users
ci / test (push) Has been cancelled
ci / remote-bench (push) Has been cancelled

This commit is contained in:
DuProcess
2026-06-13 22:32:45 -04:00
parent 1b1278f39e
commit f66de64b3f
3 changed files with 40 additions and 6 deletions
+1 -1
View File
@@ -69,7 +69,7 @@ Must work in v1:
- Encrypted OpenSSH private-key authentication with prompt. - Encrypted OpenSSH private-key authentication with prompt.
- `~/.ssh/config` host aliases for `Host`, `HostName`, `User`, `Port`, - `~/.ssh/config` host aliases for `Host`, `HostName`, `User`, `Port`,
`IdentityFile`, `ProxyJump`, `UserKnownHostsFile`, and `IdentitiesOnly`. `IdentityFile`, `ProxyJump`, `UserKnownHostsFile`, and `IdentitiesOnly`.
- Dosh host config overrides for Dosh-specific UDP host/port/auth policy. - Dosh host config overrides for user, Dosh-specific UDP host/port/auth policy.
- Clear host-key trust and mismatch errors. - Clear host-key trust and mismatch errors.
- Clear auth failure errors that name the attempted key source. - Clear auth failure errors that name the attempted key source.
- Stable reconnect after sleep, network switch, NAT rebinding, and server packet loss. - Stable reconnect after sleep, network switch, NAT rebinding, and server packet loss.
+38 -5
View File
@@ -173,7 +173,8 @@ async fn main() -> Result<()> {
.get(&requested_server) .get(&requested_server)
.cloned() .cloned()
.unwrap_or_default(); .unwrap_or_default();
let server = host.ssh.clone().unwrap_or_else(|| requested_server.clone()); let raw_server = host.ssh.clone().unwrap_or_else(|| requested_server.clone());
let server = ssh_with_user(&raw_server, host.user.as_deref());
let startup_command = startup_command(&args.command).or_else(|| { let startup_command = startup_command(&args.command).or_else(|| {
host.default_command host.default_command
.as_deref() .as_deref()
@@ -490,7 +491,8 @@ fn run_trust_command(config: &dosh::config::ClientConfig, args: &Args) -> Result
let hosts = load_hosts_config(None).unwrap_or_default(); let hosts = load_hosts_config(None).unwrap_or_default();
let host_config = hosts.hosts.get(host).cloned().unwrap_or_default(); let host_config = hosts.hosts.get(host).cloned().unwrap_or_default();
let server = host_config.ssh.clone().unwrap_or_else(|| host.to_string()); let raw_server = host_config.ssh.clone().unwrap_or_else(|| host.to_string());
let server = ssh_with_user(&raw_server, host_config.user.as_deref());
let ssh_port = args.ssh_port.or(host_config.ssh_port).or(config.ssh_port); let ssh_port = args.ssh_port.or(host_config.ssh_port).or(config.ssh_port);
let ssh_auth_command = args let ssh_auth_command = args
.ssh_auth_command .ssh_auth_command
@@ -531,7 +533,8 @@ async fn run_doctor_command(config: &dosh::config::ClientConfig, args: &Args) ->
.ok_or_else(|| anyhow!("usage: dosh doctor <host>"))?; .ok_or_else(|| anyhow!("usage: dosh doctor <host>"))?;
let hosts = load_hosts_config(None).unwrap_or_default(); let hosts = load_hosts_config(None).unwrap_or_default();
let host_config = hosts.hosts.get(&requested).cloned().unwrap_or_default(); let host_config = hosts.hosts.get(&requested).cloned().unwrap_or_default();
let server = host_config.ssh.clone().unwrap_or_else(|| requested.clone()); let raw_server = host_config.ssh.clone().unwrap_or_else(|| requested.clone());
let server = ssh_with_user(&raw_server, host_config.user.as_deref());
let ssh_port = args.ssh_port.or(host_config.ssh_port).or(config.ssh_port); let ssh_port = args.ssh_port.or(host_config.ssh_port).or(config.ssh_port);
let dosh_port = args let dosh_port = args
.dosh_port .dosh_port
@@ -994,6 +997,9 @@ fn run_import_ssh(config: &dosh::config::ClientConfig, aliases: &[String]) -> Re
raw.push_str(&format!("ssh = {}\n", toml_string(alias))); raw.push_str(&format!("ssh = {}\n", toml_string(alias)));
raw.push_str(&format!("dosh_host = {}\n", toml_string(&udp_host))); raw.push_str(&format!("dosh_host = {}\n", toml_string(&udp_host)));
raw.push_str(&format!("port = {}\n", config.dosh_port)); raw.push_str(&format!("port = {}\n", config.dosh_port));
if let Some(user) = &ssh_config.user {
raw.push_str(&format!("user = {}\n", toml_string(user)));
}
if let Some(port) = ssh_config.port if let Some(port) = ssh_config.port
&& port != 22 && port != 22
{ {
@@ -1183,6 +1189,17 @@ fn ssh_username(server: &str) -> Option<String> {
} }
} }
fn ssh_with_user(server: &str, user: Option<&str>) -> String {
let Some(user) = user else {
return server.to_string();
};
if user.is_empty() || ssh_username(server).is_some() || server.starts_with("ssh://") {
server.to_string()
} else {
format!("{user}@{server}")
}
}
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
struct SshConfig { struct SshConfig {
hostname: Option<String>, hostname: Option<String>,
@@ -2964,8 +2981,8 @@ mod tests {
DynamicForward, FrameBuffer, LocalForward, Predictor, RemoteForward, SshConfig, DynamicForward, FrameBuffer, LocalForward, Predictor, RemoteForward, SshConfig,
auth_allows, load_first_native_identity_with_prompt, parse_dynamic_forward, auth_allows, load_first_native_identity_with_prompt, parse_dynamic_forward,
parse_local_forward, parse_remote_forward, parse_ssh_config, raw_contains_host_table, parse_local_forward, parse_remote_forward, parse_ssh_config, raw_contains_host_table,
recv_response_until, requested_env, ssh_destination_host, ssh_username, startup_command, recv_response_until, requested_env, ssh_destination_host, ssh_username, ssh_with_user,
toml_bare_key_or_quoted, startup_command, toml_bare_key_or_quoted,
}; };
use dosh::config::{ClientConfig, HostConfig}; use dosh::config::{ClientConfig, HostConfig};
use dosh::native::EnvVar; use dosh::native::EnvVar;
@@ -3108,6 +3125,22 @@ mod tests {
assert_eq!(ssh_username("example.com"), None); assert_eq!(ssh_username("example.com"), None);
} }
#[test]
fn applies_host_config_user_to_plain_ssh_destination() {
assert_eq!(
ssh_with_user("example.com", Some("palav")),
"palav@example.com"
);
assert_eq!(
ssh_with_user("root@example.com", Some("palav")),
"root@example.com"
);
assert_eq!(
ssh_with_user("ssh://example.com", Some("palav")),
"ssh://example.com"
);
}
#[test] #[test]
fn auth_preference_accepts_auto_or_named_method() { fn auth_preference_accepts_auto_or_named_method() {
assert!(auth_allows("native,ssh", "native")); assert!(auth_allows("native,ssh", "native"));
+1
View File
@@ -106,6 +106,7 @@ pub struct ClientConfig {
#[derive(Debug, Clone, Default, Serialize, Deserialize)] #[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct HostConfig { pub struct HostConfig {
pub ssh: Option<String>, pub ssh: Option<String>,
pub user: Option<String>,
pub dosh_host: Option<String>, pub dosh_host: Option<String>,
pub port: Option<u16>, pub port: Option<u16>,
pub ssh_port: Option<u16>, pub ssh_port: Option<u16>,