Clarify update checks for releases
This commit is contained in:
@@ -54,11 +54,11 @@ dosh recover HOST # clear cached attach state and re-check
|
|||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
dosh palav
|
dosh server
|
||||||
dosh palav tm
|
dosh server tm
|
||||||
dosh forward palav -L 8080:127.0.0.1:80
|
dosh forward server -L 8080:127.0.0.1:80
|
||||||
dosh forward palav -D 1080
|
dosh forward server -D 1080
|
||||||
dosh forward palav -R 2222:127.0.0.1:22
|
dosh forward server -R 2222:127.0.0.1:22
|
||||||
```
|
```
|
||||||
|
|
||||||
Agent forwarding is opt-in with `-A` and must be enabled on the server.
|
Agent forwarding is opt-in with `-A` and must be enabled on the server.
|
||||||
|
|||||||
+2
-2
@@ -547,8 +547,8 @@ EOF
|
|||||||
fi
|
fi
|
||||||
cat >"$hosts_config" <<EOF
|
cat >"$hosts_config" <<EOF
|
||||||
# Example:
|
# Example:
|
||||||
# [homelab]
|
# [server]
|
||||||
# ssh = "homelab"
|
# ssh = "server"
|
||||||
# dosh_host = "server.example.com"
|
# dosh_host = "server.example.com"
|
||||||
# port = 50000
|
# port = 50000
|
||||||
# default_command = "tm"
|
# default_command = "tm"
|
||||||
|
|||||||
+99
-14
@@ -1503,7 +1503,54 @@ fn latest_release_download_url(repo: &str, artifact: &str) -> Option<String> {
|
|||||||
Some(format!("{web}/releases/latest/download/{artifact}"))
|
Some(format!("{web}/releases/latest/download/{artifact}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn latest_release_tag_download_url(repo: &str, artifact: &str) -> Result<Option<String>> {
|
fn release_tag_from_effective_url(web: &str, effective: &str) -> Option<String> {
|
||||||
|
let prefix = format!("{web}/releases/tag/");
|
||||||
|
let tag = effective.strip_prefix(&prefix)?;
|
||||||
|
if tag.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(tag.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release_version_from_tag(tag: &str) -> &str {
|
||||||
|
tag.strip_prefix('v').unwrap_or(tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_version_status(local: &str, latest: &str) -> &'static str {
|
||||||
|
match compare_dotted_versions(latest, local) {
|
||||||
|
std::cmp::Ordering::Equal => "current",
|
||||||
|
std::cmp::Ordering::Greater => "update available",
|
||||||
|
std::cmp::Ordering::Less => "different",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_dotted_versions(left: &str, right: &str) -> std::cmp::Ordering {
|
||||||
|
let left = parse_dotted_version(left);
|
||||||
|
let right = parse_dotted_version(right);
|
||||||
|
let width = left.len().max(right.len());
|
||||||
|
for index in 0..width {
|
||||||
|
let ordering = left
|
||||||
|
.get(index)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(0)
|
||||||
|
.cmp(&right.get(index).copied().unwrap_or(0));
|
||||||
|
if ordering != std::cmp::Ordering::Equal {
|
||||||
|
return ordering;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::cmp::Ordering::Equal
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_dotted_version(version: &str) -> Vec<u64> {
|
||||||
|
version
|
||||||
|
.split(|byte: char| !byte.is_ascii_digit())
|
||||||
|
.filter(|part| !part.is_empty())
|
||||||
|
.map(|part| part.parse::<u64>().unwrap_or(0))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn latest_release_tag(repo: &str) -> Result<Option<String>> {
|
||||||
let web = repo
|
let web = repo
|
||||||
.strip_suffix(".git")
|
.strip_suffix(".git")
|
||||||
.unwrap_or(repo)
|
.unwrap_or(repo)
|
||||||
@@ -1525,14 +1572,18 @@ fn latest_release_tag_download_url(repo: &str, artifact: &str) -> Result<Option<
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
let effective = String::from_utf8_lossy(&output.stdout);
|
let effective = String::from_utf8_lossy(&output.stdout);
|
||||||
let prefix = format!("{web}/releases/tag/");
|
Ok(release_tag_from_effective_url(web, &effective))
|
||||||
let Some(tag) = effective.strip_prefix(&prefix) else {
|
}
|
||||||
return Ok(None);
|
|
||||||
};
|
fn latest_release_tag_download_url(repo: &str, artifact: &str) -> Result<Option<String>> {
|
||||||
if tag.is_empty() {
|
let web = repo
|
||||||
|
.strip_suffix(".git")
|
||||||
|
.unwrap_or(repo)
|
||||||
|
.trim_end_matches('/');
|
||||||
|
if !web.starts_with("http://") && !web.starts_with("https://") {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
Ok(Some(format!("{web}/releases/download/{tag}/{artifact}")))
|
Ok(latest_release_tag(repo)?.map(|tag| format!("{web}/releases/download/{tag}/{artifact}")))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn url_reachable(url: &str) -> Result<bool> {
|
fn url_reachable(url: &str) -> Result<bool> {
|
||||||
@@ -1556,9 +1607,20 @@ fn run_update(config: &dosh::config::ClientConfig, check_only: bool) -> Result<(
|
|||||||
let installer = format!("{raw_base}/raw/branch/main/install.sh");
|
let installer = format!("{raw_base}/raw/branch/main/install.sh");
|
||||||
let artifact = release_artifact_name();
|
let artifact = release_artifact_name();
|
||||||
if check_only {
|
if check_only {
|
||||||
println!("dosh {}", env!("CARGO_PKG_VERSION"));
|
let local_version = env!("CARGO_PKG_VERSION");
|
||||||
|
println!("dosh {local_version}");
|
||||||
println!("repo: {repo}");
|
println!("repo: {repo}");
|
||||||
println!("installer: {installer}");
|
println!("installer: {installer}");
|
||||||
|
match latest_release_tag(&repo)? {
|
||||||
|
Some(tag) => {
|
||||||
|
let latest_version = release_version_from_tag(&tag);
|
||||||
|
println!(
|
||||||
|
"latest: {latest_version} ({})",
|
||||||
|
update_version_status(local_version, latest_version)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => println!("latest: unknown"),
|
||||||
|
}
|
||||||
if let Some(latest_url) = latest_release_download_url(&repo, artifact) {
|
if let Some(latest_url) = 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();
|
||||||
@@ -5557,12 +5619,13 @@ mod tests {
|
|||||||
load_first_native_identity_with_prompt, parse_dynamic_forward, parse_escape_key,
|
load_first_native_identity_with_prompt, parse_dynamic_forward, parse_escape_key,
|
||||||
parse_local_forward, parse_remote_forward, parse_ssh_config, post_submit_hold_duration,
|
parse_local_forward, parse_remote_forward, parse_ssh_config, post_submit_hold_duration,
|
||||||
queue_pending_user_input, raw_contains_host_table, recv_response_until, refresh_live_addr,
|
queue_pending_user_input, raw_contains_host_table, recv_response_until, refresh_live_addr,
|
||||||
render_status_clear, render_status_overlay, requested_env, resolved_startup_command,
|
release_tag_from_effective_url, release_version_from_tag, render_status_clear,
|
||||||
retransmit_stream_opens, rewrite_forward_command, selected_predict_mode, selected_udp_host,
|
render_status_overlay, requested_env, resolved_startup_command, retransmit_stream_opens,
|
||||||
server_version_mismatch, should_hold_during_startup_gate, should_hold_post_submit_input,
|
rewrite_forward_command, selected_predict_mode, selected_udp_host, server_version_mismatch,
|
||||||
split_after_command_submit, ssh_destination_host, ssh_username, ssh_with_user,
|
should_hold_during_startup_gate, should_hold_post_submit_input, split_after_command_submit,
|
||||||
startup_command, status_ssh_target, strip_stale_mouse_reports, toml_bare_key_or_quoted,
|
ssh_destination_host, ssh_username, ssh_with_user, startup_command, status_ssh_target,
|
||||||
update_check_requested, valid_forward_host,
|
strip_stale_mouse_reports, toml_bare_key_or_quoted, update_check_requested,
|
||||||
|
update_version_status, valid_forward_host,
|
||||||
};
|
};
|
||||||
use dosh::config::{ClientConfig, CommandExtension, HostConfig};
|
use dosh::config::{ClientConfig, CommandExtension, HostConfig};
|
||||||
use dosh::native::EnvVar;
|
use dosh::native::EnvVar;
|
||||||
@@ -6435,6 +6498,28 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn release_tag_parses_effective_latest_url() {
|
||||||
|
assert_eq!(
|
||||||
|
release_tag_from_effective_url(
|
||||||
|
"https://example.com/owner/dosh",
|
||||||
|
"https://example.com/owner/dosh/releases/tag/v1.0.0"
|
||||||
|
)
|
||||||
|
.as_deref(),
|
||||||
|
Some("v1.0.0")
|
||||||
|
);
|
||||||
|
assert_eq!(release_version_from_tag("v1.0.0"), "1.0.0");
|
||||||
|
assert_eq!(release_version_from_tag("2026.06.28"), "2026.06.28");
|
||||||
|
assert!(release_tag_from_effective_url("https://example.com/owner/dosh", "").is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_version_status_compares_numeric_parts() {
|
||||||
|
assert_eq!(update_version_status("0.1.5", "0.1.5"), "current");
|
||||||
|
assert_eq!(update_version_status("0.1.9", "0.1.10"), "update available");
|
||||||
|
assert_eq!(update_version_status("1.0.0", "0.9.9"), "different");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn resolved_startup_command_expands_global_extension() {
|
fn resolved_startup_command_expands_global_extension() {
|
||||||
let mut config = ClientConfig::default();
|
let mut config = ClientConfig::default();
|
||||||
|
|||||||
Reference in New Issue
Block a user