Support remote server updates from Windows
ci / test (push) Canceled after 0s
ci / fuzz-smoke (push) Canceled after 0s
ci / macos-client (macos-aarch64, macos-14) (push) Canceled after 0s
ci / macos-client (macos-x86_64, macos-13) (push) Canceled after 0s
ci / windows-client (push) Canceled after 0s
ci / package-release (linux-x86_64, ubuntu-latest, , , ) (push) Canceled after 0s
ci / package-release (macos-aarch64, macos-14, , , ) (push) Canceled after 0s
ci / package-release (macos-x86_64, macos-13, , , ) (push) Canceled after 0s
ci / package-release (windows-aarch64, windows-latest, aarch64, windows, aarch64-pc-windows-msvc) (push) Canceled after 0s
ci / package-release (windows-x86_64, windows-latest, , , ) (push) Canceled after 0s
ci / remote-bench (push) Canceled after 0s
ci / publish-gitea-release (push) Canceled after 0s

This commit is contained in:
DuProcess
2026-07-16 20:32:42 -04:00
parent 5bde144b12
commit b84ec4eb3f
+153 -40
View File
@@ -301,7 +301,7 @@ async fn main() -> Result<()> {
return run_file_cat_command(&args); return run_file_cat_command(&args);
} }
if args.server.as_deref() == Some("update") { if args.server.as_deref() == Some("update") {
return run_update(&config, parse_update_options(&args.command)?); return run_update(&config, &args, parse_update_options(&args.command)?);
} }
if args.server.as_deref() == Some("trace") { if args.server.as_deref() == Some("trace") {
return run_trace_command(&args); return run_trace_command(&args);
@@ -4097,16 +4097,18 @@ impl UpdateRole {
} }
} }
fn installer_arg_for_os(self, os: &str) -> Result<&'static str> { fn local_installer_arg_for_os(self, os: &str) -> Option<&'static str> {
if os == "windows" { if os == "windows" {
return match self { return match self {
UpdateRole::Auto | UpdateRole::Client => Ok("client"), UpdateRole::Auto | UpdateRole::Client | UpdateRole::Both => Some("client"),
UpdateRole::Server | UpdateRole::Both => { UpdateRole::Server => None,
Err(anyhow!("Windows installs support client updates only"))
}
}; };
} }
Ok(self.installer_arg()) Some(self.installer_arg())
}
fn updates_remote_server_from_os(self, os: &str) -> bool {
os == "windows" && matches!(self, UpdateRole::Server | UpdateRole::Both)
} }
} }
@@ -4423,7 +4425,11 @@ fn url_reachable(url: &str) -> Result<bool> {
Ok(status.success()) Ok(status.success())
} }
fn run_update(config: &dosh::config::ClientConfig, options: UpdateOptions) -> Result<()> { fn run_update(
config: &dosh::config::ClientConfig,
args: &Args,
options: UpdateOptions,
) -> Result<()> {
let repo = config let repo = config
.update_repo .update_repo
.clone() .clone()
@@ -4432,14 +4438,24 @@ fn run_update(config: &dosh::config::ClientConfig, options: UpdateOptions) -> Re
let os = std::env::consts::OS; let os = std::env::consts::OS;
let installer = update_installer_url(&raw_base, os); let installer = update_installer_url(&raw_base, os);
let artifact = release_artifact_name(); let artifact = release_artifact_name();
let installer_role = options.role.installer_arg_for_os(os)?; let local_installer_role = options.role.local_installer_arg_for_os(os);
let update_remote_server = options.role.updates_remote_server_from_os(os);
if options.check_only { if options.check_only {
let local_version = env!("CARGO_PKG_VERSION"); let local_version = env!("CARGO_PKG_VERSION");
let latest_tag = latest_release_tag(&repo)?; let latest_tag = latest_release_tag(&repo)?;
println!("dosh {local_version}"); println!("dosh {local_version}");
println!("repo: {repo}"); println!("repo: {repo}");
if let Some(local_installer_role) = local_installer_role {
println!("installer: {installer}"); println!("installer: {installer}");
println!("role: {installer_role}"); println!("role: {local_installer_role}");
}
if update_remote_server {
println!(
"remote_installer: {}",
update_installer_url(&raw_base, "linux")
);
println!("remote_server: {}", config.server);
}
match latest_tag.as_deref() { match latest_tag.as_deref() {
Some(tag) => { Some(tag) => {
let latest_version = release_version_from_tag(tag); let latest_version = release_version_from_tag(tag);
@@ -4450,7 +4466,9 @@ fn run_update(config: &dosh::config::ClientConfig, options: UpdateOptions) -> Re
} }
None => println!("latest: unknown"), None => println!("latest: unknown"),
} }
if let Some(latest_url) = latest_release_download_url(&repo, &artifact) { if let Some(latest_url) =
local_installer_role.and_then(|_| latest_release_download_url(&repo, &artifact))
{
let mut status = "missing"; let mut status = "missing";
let mut display_url = latest_url.clone(); let mut display_url = latest_url.clone();
if let Some(tag_url) = if let Some(tag_url) =
@@ -4481,15 +4499,27 @@ fn run_update(config: &dosh::config::ClientConfig, options: UpdateOptions) -> Re
env!("CARGO_PKG_VERSION"), env!("CARGO_PKG_VERSION"),
latest_release_tag(&repo)?.as_deref(), latest_release_tag(&repo)?.as_deref(),
); );
if update_remote_server {
run_remote_server_update(
config,
args,
&repo,
&raw_base,
&use_prebuilt,
binary_version.as_deref(),
)?;
}
if let Some(local_installer_role) = local_installer_role {
run_update_installer( run_update_installer(
config, config,
&repo, &repo,
&installer, &installer,
installer_role, local_installer_role,
&update_cache.display().to_string(), &update_cache.display().to_string(),
&use_prebuilt, &use_prebuilt,
binary_version.as_deref(), binary_version.as_deref(),
)?; )?;
}
Ok(()) Ok(())
} }
@@ -4546,6 +4576,31 @@ fn run_update_installer(
Ok(()) Ok(())
} }
fn run_remote_server_update(
config: &dosh::config::ClientConfig,
args: &Args,
repo: &str,
raw_base: &str,
use_prebuilt: &str,
binary_version: Option<&str>,
) -> Result<()> {
anyhow::ensure!(
config.server != "user@example.com",
"set server in ~/.config/dosh/client.toml before running dosh update --server"
);
let installer = update_installer_url(raw_base, "linux");
let script =
remote_unix_server_update_script(config, repo, &installer, use_prebuilt, binary_version);
run_remote_script(
&config.server,
args.ssh_port.or(config.ssh_port),
args.ssh_key.as_deref(),
args.ssh_known_hosts.as_deref(),
&script,
"run remote Dosh server update",
)
}
fn unix_update_script( fn unix_update_script(
config: &dosh::config::ClientConfig, config: &dosh::config::ClientConfig,
repo: &str, repo: &str,
@@ -4580,6 +4635,29 @@ fn unix_update_script(
script script
} }
fn remote_unix_server_update_script(
config: &dosh::config::ClientConfig,
repo: &str,
installer: &str,
use_prebuilt: &str,
binary_version: Option<&str>,
) -> String {
let mut env = format!(
"DOSH_REPO={} DOSH_PORT={} DOSH_UPDATE_QUIET=1 DOSH_USE_PREBUILT={}",
shell_word(repo),
config.update_port.unwrap_or(config.dosh_port),
shell_word(use_prebuilt)
);
if let Some(version) = binary_version {
env.push_str(&format!(" DOSH_BINARY_VERSION={}", shell_word(version)));
}
format!(
"curl -fsSL {} | {} sh -s -- server",
shell_word(installer),
env
)
}
fn windows_update_script( fn windows_update_script(
config: &dosh::config::ClientConfig, config: &dosh::config::ClientConfig,
repo: &str, repo: &str,
@@ -10257,25 +10335,25 @@ mod tests {
queue_or_send_stream_data, queue_pending_user_input, queue_stale_pending_user_input, queue_or_send_stream_data, queue_pending_user_input, queue_stale_pending_user_input,
raw_contains_host_table, recv_response_until, refresh_live_addr, release_artifact_name_for, raw_contains_host_table, recv_response_until, refresh_live_addr, release_artifact_name_for,
release_tag_download_url, release_tag_from_effective_url, release_version_from_tag, release_tag_download_url, release_tag_from_effective_url, release_version_from_tag,
render_frame_bytes, render_status_clear, render_status_overlay, requested_env, remote_unix_server_update_script, render_frame_bytes, render_status_clear,
resolve_forward_agent_endpoint, resolved_startup_command, retire_stream_state, render_status_overlay, requested_env, resolve_forward_agent_endpoint,
retransmit_stream_closes, retransmit_stream_eofs, retransmit_stream_opens, resolved_startup_command, retire_stream_state, retransmit_stream_closes,
retransmit_stream_window_adjusts, rewrite_forward_command, sanitize_trace_name, retransmit_stream_eofs, retransmit_stream_opens, retransmit_stream_window_adjusts,
selected_predict_mode, selected_udp_host, send_stream_eof, server_version_mismatch, rewrite_forward_command, sanitize_trace_name, selected_predict_mode, selected_udp_host,
should_flush_terminal_input_after_contact, should_health_log_client_start, send_stream_eof, server_version_mismatch, should_flush_terminal_input_after_contact,
should_hold_during_startup_gate, should_hold_post_submit_input, should_health_log_client_start, should_hold_during_startup_gate,
should_reconnect_before_input_for_local_sleep, should_repaint_idle_terminal, should_hold_post_submit_input, should_reconnect_before_input_for_local_sleep,
should_strip_unowned_terminal_reports, split_after_command_submit, split_trace_tokens, should_repaint_idle_terminal, should_strip_unowned_terminal_reports,
ssh_command_target, ssh_config_uses_proxy, ssh_config_word_for_os, ssh_destination_host, split_after_command_submit, split_trace_tokens, ssh_command_target, ssh_config_uses_proxy,
ssh_username, ssh_with_user, startup_command, status_ssh_target, strip_stale_mouse_reports, ssh_config_word_for_os, ssh_destination_host, ssh_username, ssh_with_user, startup_command,
strip_terminal_focus_reports, strip_unowned_terminal_reports, summarize_trace_file, status_ssh_target, strip_stale_mouse_reports, strip_terminal_focus_reports,
summarize_trace_file_with_mode, terminal_private_mode_transition, toml_bare_key_or_quoted, strip_unowned_terminal_reports, summarize_trace_file, summarize_trace_file_with_mode,
top_trace_events, trace_report_warnings, unix_update_script, terminal_private_mode_transition, toml_bare_key_or_quoted, top_trace_events,
update_binary_version_for_installer, update_installer_url, update_version_status, trace_report_warnings, unix_update_script, update_binary_version_for_installer,
upsert_managed_block, valid_forward_host, vscode_safe_alias, wake_repaint_retry_deadline, update_installer_url, update_version_status, upsert_managed_block, valid_forward_host,
windows_command_word, windows_deferred_update_script, windows_effective_url_script, vscode_safe_alias, wake_repaint_retry_deadline, windows_command_word,
windows_mode_from_readonly, windows_readonly_from_mode, windows_update_script, windows_deferred_update_script, windows_effective_url_script, windows_mode_from_readonly,
windows_url_reachable_script, windows_readonly_from_mode, windows_update_script, windows_url_reachable_script,
}; };
use dosh::config::{ClientConfig, CommandExtension, HostConfig}; use dosh::config::{ClientConfig, CommandExtension, HostConfig};
use dosh::native::EnvVar; use dosh::native::EnvVar;
@@ -12461,21 +12539,31 @@ mod tests {
} }
#[test] #[test]
fn windows_update_roles_are_client_only() { fn windows_update_roles_split_local_client_and_remote_server() {
assert_eq!( assert_eq!(
UpdateRole::Auto.installer_arg_for_os("windows").unwrap(), UpdateRole::Auto.local_installer_arg_for_os("windows"),
"client" Some("client")
); );
assert_eq!( assert_eq!(
UpdateRole::Client.installer_arg_for_os("windows").unwrap(), UpdateRole::Client.local_installer_arg_for_os("windows"),
"client" Some("client")
); );
assert!(UpdateRole::Server.installer_arg_for_os("windows").is_err());
assert!(UpdateRole::Both.installer_arg_for_os("windows").is_err());
assert_eq!( assert_eq!(
UpdateRole::Both.installer_arg_for_os("linux").unwrap(), UpdateRole::Server.local_installer_arg_for_os("windows"),
"both" None
); );
assert_eq!(
UpdateRole::Both.local_installer_arg_for_os("windows"),
Some("client")
);
assert!(UpdateRole::Server.updates_remote_server_from_os("windows"));
assert!(UpdateRole::Both.updates_remote_server_from_os("windows"));
assert!(!UpdateRole::Client.updates_remote_server_from_os("windows"));
assert_eq!(
UpdateRole::Both.local_installer_arg_for_os("linux"),
Some("both")
);
assert!(!UpdateRole::Both.updates_remote_server_from_os("linux"));
} }
#[test] #[test]
@@ -12523,6 +12611,31 @@ mod tests {
assert!(!windows.contains(" sh -s ")); assert!(!windows.contains(" sh -s "));
} }
#[test]
fn windows_server_update_uses_remote_unix_installer_script() {
let config = ClientConfig {
server: "palav".to_string(),
dosh_port: 50000,
update_port: Some(50001),
..ClientConfig::default()
};
let script = remote_unix_server_update_script(
&config,
"https://git.palav.dev/Palav/dosh.git",
"https://git.palav.dev/Palav/dosh/raw/branch/main/install.sh",
"1",
Some("v1.0.0-rc41"),
);
assert!(script.contains("curl -fsSL"));
assert!(script.contains("install.sh"));
assert!(script.contains("DOSH_REPO='https://git.palav.dev/Palav/dosh.git'"));
assert!(script.contains("DOSH_PORT=50001"));
assert!(script.contains("DOSH_BINARY_VERSION='v1.0.0-rc41'"));
assert!(script.contains(" sh -s -- server"));
assert!(!script.contains("install.ps1"));
assert!(!script.contains("DOSH_ROLE='client'"));
}
#[test] #[test]
fn windows_update_script_waits_for_parent_before_replacing_locked_exe() { fn windows_update_script_waits_for_parent_before_replacing_locked_exe() {
let installer = windows_update_script( let installer = windows_update_script(