Use native installer for Windows updates

This commit is contained in:
DuProcess
2026-07-11 11:29:41 -04:00
parent 732f53f2d0
commit b87f277e2d
2 changed files with 216 additions and 16 deletions
+1
View File
@@ -44,6 +44,7 @@ dosh setup HOST
dosh HOST dosh HOST
dosh HOST COMMAND dosh HOST COMMAND
dosh update dosh update
dosh update --client|--server|--both
``` ```
Useful commands: Useful commands:
+215 -16
View File
@@ -2864,6 +2864,18 @@ impl UpdateRole {
UpdateRole::Both => "both", UpdateRole::Both => "both",
} }
} }
fn installer_arg_for_os(self, os: &str) -> Result<&'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"))
}
};
}
Ok(self.installer_arg())
}
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -2932,6 +2944,18 @@ fn release_artifact_name() -> &'static str {
} }
} }
fn update_installer_name(os: &str) -> &'static str {
if os == "windows" {
"install.ps1"
} else {
"install.sh"
}
}
fn update_installer_url(raw_base: &str, os: &str) -> String {
format!("{raw_base}/raw/branch/main/{}", update_installer_name(os))
}
fn latest_release_download_url(repo: &str, artifact: &str) -> Option<String> { fn latest_release_download_url(repo: &str, artifact: &str) -> Option<String> {
let web = repo let web = repo
.strip_suffix(".git") .strip_suffix(".git")
@@ -3044,9 +3068,10 @@ fn run_update(config: &dosh::config::ClientConfig, options: UpdateOptions) -> Re
.clone() .clone()
.unwrap_or_else(|| "https://git.palav.dev/Palav/dosh.git".to_string()); .unwrap_or_else(|| "https://git.palav.dev/Palav/dosh.git".to_string());
let raw_base = raw_base_from_repo(&repo); let raw_base = raw_base_from_repo(&repo);
let installer = format!("{raw_base}/raw/branch/main/install.sh"); let os = std::env::consts::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(); let installer_role = options.role.installer_arg_for_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)?;
@@ -3089,13 +3114,78 @@ fn run_update(config: &dosh::config::ClientConfig, options: UpdateOptions) -> Re
.join("dosh") .join("dosh")
.join("source"); .join("source");
let use_prebuilt = std::env::var("DOSH_USE_PREBUILT").unwrap_or_else(|_| "1".to_string()); let use_prebuilt = std::env::var("DOSH_USE_PREBUILT").unwrap_or_else(|_| "1".to_string());
let status = run_update_installer(
config,
&repo,
&installer,
installer_role,
&update_cache.display().to_string(),
&use_prebuilt,
)?;
if !status.success() {
return Err(anyhow!("dosh update failed with status {status}"));
}
Ok(())
}
fn run_update_installer(
config: &dosh::config::ClientConfig,
repo: &str,
installer: &str,
installer_role: &str,
update_cache: &str,
use_prebuilt: &str,
) -> Result<std::process::ExitStatus> {
if cfg!(windows) {
let script = windows_update_script(
config,
repo,
installer,
installer_role,
update_cache,
use_prebuilt,
);
return Command::new("powershell.exe")
.arg("-NoProfile")
.arg("-ExecutionPolicy")
.arg("Bypass")
.arg("-Command")
.arg(script)
.stdin(Stdio::null())
.status()
.context("run dosh update installer");
}
let script = unix_update_script(
config,
repo,
installer,
installer_role,
update_cache,
use_prebuilt,
);
Command::new("sh")
.arg("-c")
.arg(script)
.stdin(Stdio::null())
.status()
.context("run dosh update installer")
}
fn unix_update_script(
config: &dosh::config::ClientConfig,
repo: &str,
installer: &str,
installer_role: &str,
update_cache: &str,
use_prebuilt: &str,
) -> String {
let mut script = format!( let mut script = format!(
"curl -fsSL {} | DOSH_REPO={} DOSH_PORT={} DOSH_UPDATE_CACHE={} DOSH_UPDATE_QUIET=1 DOSH_USE_PREBUILT={} sh -s -- {}", "curl -fsSL {} | DOSH_REPO={} DOSH_PORT={} DOSH_UPDATE_CACHE={} DOSH_UPDATE_QUIET=1 DOSH_USE_PREBUILT={} sh -s -- {}",
shell_word(&installer), shell_word(installer),
shell_word(&repo), shell_word(repo),
config.update_port.unwrap_or(config.dosh_port), config.update_port.unwrap_or(config.dosh_port),
shell_word(&update_cache.display().to_string()), shell_word(update_cache),
shell_word(&use_prebuilt), shell_word(use_prebuilt),
shell_word(installer_role) shell_word(installer_role)
); );
if config.server != "user@example.com" { if config.server != "user@example.com" {
@@ -3104,16 +3194,47 @@ fn run_update(config: &dosh::config::ClientConfig, options: UpdateOptions) -> Re
if let Some(host) = &config.dosh_host { if let Some(host) = &config.dosh_host {
script.push_str(&format!(" --dosh-host {}", shell_word(host))); script.push_str(&format!(" --dosh-host {}", shell_word(host)));
} }
let status = Command::new("sh") script
.arg("-c") }
.arg(script)
.stdin(Stdio::null()) fn windows_update_script(
.status() config: &dosh::config::ClientConfig,
.context("run dosh update installer")?; repo: &str,
if !status.success() { installer: &str,
return Err(anyhow!("dosh update failed with status {status}")); installer_role: &str,
update_cache: &str,
use_prebuilt: &str,
) -> String {
let mut script = String::from("$ErrorActionPreference='Stop';");
script.push_str("$ProgressPreference='SilentlyContinue';");
for (name, value) in [
("DOSH_REPO", repo),
("DOSH_ROLE", installer_role),
(
"DOSH_PORT",
&config.update_port.unwrap_or(config.dosh_port).to_string(),
),
("DOSH_UPDATE_CACHE", update_cache),
("DOSH_UPDATE_QUIET", "1"),
("DOSH_USE_PREBUILT", use_prebuilt),
] {
script.push_str(&format!("$env:{name}={};", powershell_string(value)));
} }
Ok(()) if config.server != "user@example.com" {
script.push_str(&format!(
"$env:DOSH_SERVER={};",
powershell_string(&config.server)
));
}
if let Some(host) = &config.dosh_host {
script.push_str(&format!("$env:DOSH_HOST={};", powershell_string(host)));
}
script.push_str(&format!("irm {} | iex", powershell_string(installer)));
script
}
fn powershell_string(value: &str) -> String {
format!("'{}'", value.replace('\'', "''"))
} }
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq)]
@@ -7654,7 +7775,8 @@ mod tests {
should_repaint_idle_alternate_screen, split_after_command_submit, ssh_destination_host, should_repaint_idle_alternate_screen, split_after_command_submit, ssh_destination_host,
ssh_username, ssh_with_user, startup_command, status_ssh_target, strip_stale_mouse_reports, ssh_username, ssh_with_user, startup_command, status_ssh_target, strip_stale_mouse_reports,
strip_terminal_focus_reports, terminal_private_mode_transition, toml_bare_key_or_quoted, strip_terminal_focus_reports, terminal_private_mode_transition, toml_bare_key_or_quoted,
update_version_status, upsert_managed_block, valid_forward_host, vscode_safe_alias, unix_update_script, update_installer_url, update_version_status, upsert_managed_block,
valid_forward_host, vscode_safe_alias, windows_update_script,
}; };
use dosh::config::{ClientConfig, CommandExtension, HostConfig}; use dosh::config::{ClientConfig, CommandExtension, HostConfig};
use dosh::native::EnvVar; use dosh::native::EnvVar;
@@ -8801,6 +8923,83 @@ mod tests {
assert!(parse_update_options(&["--client".to_string(), "--server".to_string()]).is_err()); assert!(parse_update_options(&["--client".to_string(), "--server".to_string()]).is_err());
} }
#[test]
fn update_installer_uses_platform_native_script() {
assert_eq!(
update_installer_url("https://git.palav.dev/Palav/dosh", "linux"),
"https://git.palav.dev/Palav/dosh/raw/branch/main/install.sh"
);
assert_eq!(
update_installer_url("https://git.palav.dev/Palav/dosh", "macos"),
"https://git.palav.dev/Palav/dosh/raw/branch/main/install.sh"
);
assert_eq!(
update_installer_url("https://git.palav.dev/Palav/dosh", "windows"),
"https://git.palav.dev/Palav/dosh/raw/branch/main/install.ps1"
);
}
#[test]
fn windows_update_roles_are_client_only() {
assert_eq!(
UpdateRole::Auto.installer_arg_for_os("windows").unwrap(),
"client"
);
assert_eq!(
UpdateRole::Client.installer_arg_for_os("windows").unwrap(),
"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"
);
}
#[test]
fn update_scripts_are_native_to_the_platform() {
let config = ClientConfig {
server: "palav".to_string(),
dosh_host: Some("dosh.example.com".to_string()),
dosh_port: 50000,
update_port: Some(50001),
..ClientConfig::default()
};
let unix = unix_update_script(
&config,
"https://git.palav.dev/Palav/dosh.git",
"https://git.palav.dev/Palav/dosh/raw/branch/main/install.sh",
"both",
"/tmp/dosh-cache",
"1",
);
assert!(unix.contains("curl -fsSL"));
assert!(unix.contains("install.sh"));
assert!(unix.contains("sh -s -- 'both'"));
assert!(unix.contains("--server 'palav'"));
assert!(unix.contains("--dosh-host 'dosh.example.com'"));
let windows = windows_update_script(
&config,
"https://git.palav.dev/Palav/dosh.git",
"https://git.palav.dev/Palav/dosh/raw/branch/main/install.ps1",
"client",
"C:\\Users\\palav\\AppData\\Local\\dosh\\source",
"1",
);
assert!(
windows.contains(
"irm 'https://git.palav.dev/Palav/dosh/raw/branch/main/install.ps1' | iex"
)
);
assert!(windows.contains("$env:DOSH_ROLE='client';"));
assert!(windows.contains("$env:DOSH_SERVER='palav';"));
assert!(windows.contains("$env:DOSH_HOST='dosh.example.com';"));
assert!(!windows.contains("install.sh"));
assert!(!windows.contains(" sh -s "));
}
#[test] #[test]
fn status_uses_host_alias_and_user() { fn status_uses_host_alias_and_user() {
let host = HostConfig { let host = HostConfig {