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);
}
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") {
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" {
return match self {
UpdateRole::Auto | UpdateRole::Client => Ok("client"),
UpdateRole::Server | UpdateRole::Both => {
Err(anyhow!("Windows installs support client updates only"))
}
UpdateRole::Auto | UpdateRole::Client | UpdateRole::Both => Some("client"),
UpdateRole::Server => None,
};
}
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())
}
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
.update_repo
.clone()
@@ -4432,14 +4438,24 @@ fn run_update(config: &dosh::config::ClientConfig, options: UpdateOptions) -> Re
let os = std::env::consts::OS;
let installer = update_installer_url(&raw_base, os);
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 {
let local_version = env!("CARGO_PKG_VERSION");
let latest_tag = latest_release_tag(&repo)?;
println!("dosh {local_version}");
println!("repo: {repo}");
if let Some(local_installer_role) = local_installer_role {
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() {
Some(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"),
}
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 display_url = latest_url.clone();
if let Some(tag_url) =
@@ -4481,15 +4499,27 @@ fn run_update(config: &dosh::config::ClientConfig, options: UpdateOptions) -> Re
env!("CARGO_PKG_VERSION"),
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(
config,
&repo,
&installer,
installer_role,
local_installer_role,
&update_cache.display().to_string(),
&use_prebuilt,
binary_version.as_deref(),
)?;
}
Ok(())
}
@@ -4546,6 +4576,31 @@ fn run_update_installer(
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(
config: &dosh::config::ClientConfig,
repo: &str,
@@ -4580,6 +4635,29 @@ fn unix_update_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(
config: &dosh::config::ClientConfig,
repo: &str,
@@ -10257,25 +10335,25 @@ mod tests {
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,
release_tag_download_url, release_tag_from_effective_url, release_version_from_tag,
render_frame_bytes, render_status_clear, render_status_overlay, requested_env,
resolve_forward_agent_endpoint, resolved_startup_command, retire_stream_state,
retransmit_stream_closes, retransmit_stream_eofs, retransmit_stream_opens,
retransmit_stream_window_adjusts, rewrite_forward_command, sanitize_trace_name,
selected_predict_mode, selected_udp_host, send_stream_eof, server_version_mismatch,
should_flush_terminal_input_after_contact, should_health_log_client_start,
should_hold_during_startup_gate, should_hold_post_submit_input,
should_reconnect_before_input_for_local_sleep, should_repaint_idle_terminal,
should_strip_unowned_terminal_reports, split_after_command_submit, split_trace_tokens,
ssh_command_target, ssh_config_uses_proxy, ssh_config_word_for_os, ssh_destination_host,
ssh_username, ssh_with_user, startup_command, status_ssh_target, strip_stale_mouse_reports,
strip_terminal_focus_reports, strip_unowned_terminal_reports, summarize_trace_file,
summarize_trace_file_with_mode, terminal_private_mode_transition, toml_bare_key_or_quoted,
top_trace_events, trace_report_warnings, unix_update_script,
update_binary_version_for_installer, update_installer_url, update_version_status,
upsert_managed_block, valid_forward_host, vscode_safe_alias, wake_repaint_retry_deadline,
windows_command_word, windows_deferred_update_script, windows_effective_url_script,
windows_mode_from_readonly, windows_readonly_from_mode, windows_update_script,
windows_url_reachable_script,
remote_unix_server_update_script, render_frame_bytes, render_status_clear,
render_status_overlay, requested_env, resolve_forward_agent_endpoint,
resolved_startup_command, retire_stream_state, retransmit_stream_closes,
retransmit_stream_eofs, retransmit_stream_opens, retransmit_stream_window_adjusts,
rewrite_forward_command, sanitize_trace_name, selected_predict_mode, selected_udp_host,
send_stream_eof, server_version_mismatch, should_flush_terminal_input_after_contact,
should_health_log_client_start, should_hold_during_startup_gate,
should_hold_post_submit_input, should_reconnect_before_input_for_local_sleep,
should_repaint_idle_terminal, should_strip_unowned_terminal_reports,
split_after_command_submit, split_trace_tokens, ssh_command_target, ssh_config_uses_proxy,
ssh_config_word_for_os, ssh_destination_host, ssh_username, ssh_with_user, startup_command,
status_ssh_target, strip_stale_mouse_reports, strip_terminal_focus_reports,
strip_unowned_terminal_reports, summarize_trace_file, summarize_trace_file_with_mode,
terminal_private_mode_transition, toml_bare_key_or_quoted, top_trace_events,
trace_report_warnings, unix_update_script, update_binary_version_for_installer,
update_installer_url, update_version_status, upsert_managed_block, valid_forward_host,
vscode_safe_alias, wake_repaint_retry_deadline, windows_command_word,
windows_deferred_update_script, windows_effective_url_script, windows_mode_from_readonly,
windows_readonly_from_mode, windows_update_script, windows_url_reachable_script,
};
use dosh::config::{ClientConfig, CommandExtension, HostConfig};
use dosh::native::EnvVar;
@@ -12461,21 +12539,31 @@ mod tests {
}
#[test]
fn windows_update_roles_are_client_only() {
fn windows_update_roles_split_local_client_and_remote_server() {
assert_eq!(
UpdateRole::Auto.installer_arg_for_os("windows").unwrap(),
"client"
UpdateRole::Auto.local_installer_arg_for_os("windows"),
Some("client")
);
assert_eq!(
UpdateRole::Client.installer_arg_for_os("windows").unwrap(),
"client"
UpdateRole::Client.local_installer_arg_for_os("windows"),
Some("client")
);
assert!(UpdateRole::Server.installer_arg_for_os("windows").is_err());
assert!(UpdateRole::Both.installer_arg_for_os("windows").is_err());
assert_eq!(
UpdateRole::Both.installer_arg_for_os("linux").unwrap(),
"both"
UpdateRole::Server.local_installer_arg_for_os("windows"),
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]
@@ -12523,6 +12611,31 @@ mod tests {
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]
fn windows_update_script_waits_for_parent_before_replacing_locked_exe() {
let installer = windows_update_script(