mirror of
https://github.com/herdrdev/herdr.git
synced 2026-10-08 00:00:47 +00:00
fix: summarize old servers and saved machines after herdr update (#4722)
* fix: summarize old servers and saved machines after herdr update * fix: include reconnect and per-session restart commands in update summary
This commit is contained in:
@@ -141,6 +141,8 @@ Stable is the recommended channel for normal Windows use. Preview receives fixes
|
||||
|
||||
By default, `herdr update` installs the new binary and leaves endpoint-generation-1 servers and their pane processes running. Start Herdr again to reconnect with the updated client. Client-only changes work immediately. A new server-side action stays unavailable until that server is updated and restarted; the client shows a notice instead of disconnecting. Servers from before endpoint generation 1 need one final stop during update.
|
||||
|
||||
When it finishes, `herdr update` lists any running server still on the old version with the command that restarts it, and your saved SSH machines, which run their own Herdr. Run `herdr update` on each machine to update it too.
|
||||
|
||||
To opt into experimental live server handoff for supported running sessions, run:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -141,6 +141,8 @@ Windows の通常利用には安定版を推奨します。プレビューでは
|
||||
|
||||
デフォルトでは、`herdr update` は新しいバイナリをインストールし、エンドポイント世代 1 のサーバーとペインプロセスを動かしたままにします。再度 Herdr を起動すると、更新済みクライアントで再接続します。クライアントだけの変更はすぐに使えます。新しいサーバー側アクションは、そのサーバーを更新して再起動するまで利用できず、クライアントは切断せずに通知を表示します。エンドポイント世代 1 より前のサーバーだけは、更新時に最後の 1 回の停止が必要です。
|
||||
|
||||
完了時に `herdr update` は、古いバージョンのまま動いているサーバーとそれを再起動するコマンド、そして独自の Herdr を動かしている保存済み SSH マシンを一覧表示します。各マシンでも `herdr update` を実行して更新してください。
|
||||
|
||||
対応する実行中セッションで実験的なライブサーバーハンドオフにオプトインするには:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -141,6 +141,8 @@ Windows 的常规使用建议选择稳定版。预览版能更早获得修复,
|
||||
|
||||
默认情况下,`herdr update` 会安装新二进制文件,并让端点第 1 代服务器及其窗格进程继续运行。再次启动 Herdr 即可用更新后的客户端重新连接。纯客户端改动会立即生效。新的服务器端操作会保持不可用,直到对应服务器更新并重启;客户端会显示通知,而不会断开连接。只有早于端点第 1 代的服务器需要在更新时再停止最后一次。
|
||||
|
||||
完成后,`herdr update` 会列出仍在运行旧版本的服务器及重启它们的命令,以及运行各自 Herdr 的已保存 SSH 机器。请在每台机器上也运行 `herdr update` 来更新。
|
||||
|
||||
要对受支持的运行中会话启用实验性的实时服务器交接,运行:
|
||||
|
||||
```bash
|
||||
|
||||
+219
-18
@@ -1781,26 +1781,23 @@ fn print_running_session_update_outcomes(
|
||||
return;
|
||||
}
|
||||
|
||||
let kept: Vec<KeptServer<'_>> = outcomes
|
||||
.iter()
|
||||
.filter(|outcome| outcome.outcome == RunningServerUpdateOutcome::CompatibleServerKept)
|
||||
.map(|outcome| KeptServer {
|
||||
label: &outcome.session_label,
|
||||
version: version_label(outcome.server_version.as_deref()),
|
||||
stop_command: &outcome.stop_command,
|
||||
attach_command: outcome.attach_command.as_deref(),
|
||||
})
|
||||
.collect();
|
||||
for line in kept_server_notice_lines(&kept, release.label()) {
|
||||
eprintln!("{line}");
|
||||
}
|
||||
|
||||
for outcome in outcomes {
|
||||
match outcome.outcome {
|
||||
RunningServerUpdateOutcome::CompatibleServerKept => {
|
||||
eprintln!(
|
||||
"{} {} kept running server v{}.",
|
||||
outcome.target_noun,
|
||||
outcome.session_label,
|
||||
version_label(outcome.server_version.as_deref())
|
||||
);
|
||||
match &outcome.attach_command {
|
||||
Some(command) => eprintln!(
|
||||
"Run `{command}` to reconnect with the updated client. Restart the server later only if you need server-side changes from {}.",
|
||||
release.label()
|
||||
),
|
||||
None => eprintln!(
|
||||
"Reconnect with the same socket override to use the updated client. Restart the server later only if you need server-side changes from {}.",
|
||||
release.label()
|
||||
),
|
||||
}
|
||||
}
|
||||
RunningServerUpdateOutcome::CompatibleServerKept => {}
|
||||
RunningServerUpdateOutcome::LiveHandoffComplete => {
|
||||
if let Some(command) = &outcome.attach_command {
|
||||
eprintln!(
|
||||
@@ -2190,6 +2187,7 @@ pub fn self_update(options: SelfUpdateOptions) -> Result<Version, String> {
|
||||
"Open a new terminal, or reconnect SSH, then start Herdr again to use the updated client. Running servers remain active; restart them later only if you need server-side changes from {}.",
|
||||
release.label()
|
||||
);
|
||||
print_saved_machine_update_notice();
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
@@ -2224,11 +2222,117 @@ pub fn self_update(options: SelfUpdateOptions) -> Result<Version, String> {
|
||||
print_outdated_integration_notice_with_updated_binary(&updated_exe);
|
||||
|
||||
print_running_session_update_outcomes(&server_update_outcomes, &release);
|
||||
print_saved_machine_update_notice();
|
||||
}
|
||||
|
||||
Ok(release.version)
|
||||
}
|
||||
|
||||
fn print_saved_machine_update_notice() {
|
||||
let profiles = match crate::client::endpoint::EndpointCatalog::load_profiles() {
|
||||
Ok(profiles) => profiles,
|
||||
Err(error) => {
|
||||
tracing::debug!(%error, "skipping saved machine update notice");
|
||||
return;
|
||||
}
|
||||
};
|
||||
for line in saved_machine_update_notice_lines(&profiles) {
|
||||
eprintln!("{line}");
|
||||
}
|
||||
}
|
||||
|
||||
fn saved_machine_update_notice_lines(
|
||||
profiles: &[crate::client::endpoint::SavedSshEndpoint],
|
||||
) -> Vec<String> {
|
||||
let mut targets: Vec<&str> = Vec::new();
|
||||
let mut labels: Vec<&str> = Vec::new();
|
||||
for profile in profiles.iter().filter(|profile| profile.enabled) {
|
||||
if !targets.contains(&profile.target.as_str()) {
|
||||
targets.push(&profile.target);
|
||||
labels.push(&profile.label);
|
||||
}
|
||||
}
|
||||
if labels.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
let mut lines = vec![
|
||||
String::new(),
|
||||
"your SSH machines run their own herdr and may be older:".to_string(),
|
||||
];
|
||||
lines.extend(labels.into_iter().map(|label| format!(" {label}")));
|
||||
lines.push(
|
||||
"run `herdr update` on each one. it will tell you what to restart there.".to_string(),
|
||||
);
|
||||
lines
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
struct KeptServer<'a> {
|
||||
label: &'a str,
|
||||
version: &'a str,
|
||||
stop_command: &'a str,
|
||||
attach_command: Option<&'a str>,
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
impl KeptServer<'_> {
|
||||
fn reconnect(&self) -> String {
|
||||
match self.attach_command {
|
||||
Some(attach) => format!("`{attach}`"),
|
||||
None => "herdr with the same socket override".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn kept_server_notice_lines(kept: &[KeptServer<'_>], release_label: &str) -> Vec<String> {
|
||||
match kept {
|
||||
[] => Vec::new(),
|
||||
[server] => vec![
|
||||
String::new(),
|
||||
format!(
|
||||
"your running server is still v{}. run {} to reconnect; everything keeps working.",
|
||||
server.version,
|
||||
server.reconnect()
|
||||
),
|
||||
format!("server fixes in v{release_label} apply only after it restarts."),
|
||||
format!(
|
||||
"when your agents are idle, run `{}`, then {}.",
|
||||
server.stop_command,
|
||||
server.reconnect()
|
||||
),
|
||||
"this closes running panes and their agents.".to_string(),
|
||||
],
|
||||
servers => {
|
||||
let width = servers
|
||||
.iter()
|
||||
.map(|server| server.label.chars().count())
|
||||
.max()
|
||||
.unwrap_or(0);
|
||||
let mut lines = vec![
|
||||
String::new(),
|
||||
"your running servers are still older. reconnect as usual; everything keeps working."
|
||||
.to_string(),
|
||||
format!("server fixes in v{release_label} apply only after each one restarts:"),
|
||||
];
|
||||
lines.extend(servers.iter().map(|server| {
|
||||
format!(
|
||||
" {:<width$} v{} `{}`, then {}",
|
||||
server.label,
|
||||
server.version,
|
||||
server.stop_command,
|
||||
server.reconnect()
|
||||
)
|
||||
}));
|
||||
lines.push(
|
||||
"restart one when its agents are idle. this closes its panes and their agents."
|
||||
.to_string(),
|
||||
);
|
||||
lines
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_outdated_integration_notice_with_updated_binary(updated_exe: &Path) {
|
||||
let status = Command::new(updated_exe)
|
||||
.args(["integration", "status", "--outdated-only"])
|
||||
@@ -2407,6 +2511,103 @@ mod tests {
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::thread;
|
||||
|
||||
fn saved_machine(
|
||||
label: &str,
|
||||
target: &str,
|
||||
enabled: bool,
|
||||
) -> crate::client::endpoint::SavedSshEndpoint {
|
||||
let mut profile =
|
||||
crate::client::endpoint::SavedSshEndpoint::new(label, target, "default").unwrap();
|
||||
profile.enabled = enabled;
|
||||
profile
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saved_machine_notice_lists_enabled_machines_once_per_target() {
|
||||
let profiles = [
|
||||
saved_machine("rohan", "rohan", true),
|
||||
saved_machine("rohan/agents", "rohan", true),
|
||||
saved_machine("off", "offbox", false),
|
||||
saved_machine("workbox", "dev@workbox", true),
|
||||
];
|
||||
|
||||
assert_eq!(
|
||||
saved_machine_update_notice_lines(&profiles),
|
||||
[
|
||||
"",
|
||||
"your SSH machines run their own herdr and may be older:",
|
||||
" rohan",
|
||||
" workbox",
|
||||
"run `herdr update` on each one. it will tell you what to restart there.",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saved_machine_notice_is_empty_without_enabled_machines() {
|
||||
assert!(saved_machine_update_notice_lines(&[]).is_empty());
|
||||
assert!(
|
||||
saved_machine_update_notice_lines(&[saved_machine("off", "offbox", false)]).is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kept_server_notice_names_the_session_restart_commands() {
|
||||
let default = KeptServer {
|
||||
label: "default",
|
||||
version: "0.9.1",
|
||||
stop_command: "herdr server stop",
|
||||
attach_command: Some("herdr"),
|
||||
};
|
||||
assert_eq!(
|
||||
kept_server_notice_lines(&[default], "0.10.0"),
|
||||
[
|
||||
"",
|
||||
"your running server is still v0.9.1. run `herdr` to reconnect; everything keeps working.",
|
||||
"server fixes in v0.10.0 apply only after it restarts.",
|
||||
"when your agents are idle, run `herdr server stop`, then `herdr`.",
|
||||
"this closes running panes and their agents.",
|
||||
]
|
||||
);
|
||||
|
||||
let work = KeptServer {
|
||||
label: "work",
|
||||
version: "0.9.0",
|
||||
stop_command: "herdr session stop work",
|
||||
attach_command: Some("herdr session attach work"),
|
||||
};
|
||||
let default = KeptServer {
|
||||
label: "default",
|
||||
version: "0.9.1",
|
||||
stop_command: "herdr server stop",
|
||||
attach_command: Some("herdr"),
|
||||
};
|
||||
assert_eq!(
|
||||
kept_server_notice_lines(&[default, work], "0.10.0"),
|
||||
[
|
||||
"",
|
||||
"your running servers are still older. reconnect as usual; everything keeps working.",
|
||||
"server fixes in v0.10.0 apply only after each one restarts:",
|
||||
" default v0.9.1 `herdr server stop`, then `herdr`",
|
||||
" work v0.9.0 `herdr session stop work`, then `herdr session attach work`",
|
||||
"restart one when its agents are idle. this closes its panes and their agents.",
|
||||
]
|
||||
);
|
||||
assert!(kept_server_notice_lines(&[], "0.10.0").is_empty());
|
||||
|
||||
let socket_override = KeptServer {
|
||||
label: "/tmp/herdr.sock",
|
||||
version: "0.9.1",
|
||||
stop_command: "herdr server stop",
|
||||
attach_command: None,
|
||||
};
|
||||
let lines = kept_server_notice_lines(&[socket_override], "0.10.0");
|
||||
assert_eq!(
|
||||
lines[3],
|
||||
"when your agents are idle, run `herdr server stop`, then herdr with the same socket override."
|
||||
);
|
||||
}
|
||||
|
||||
fn env_lock() -> &'static Mutex<()> {
|
||||
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
|
||||
LOCK.get_or_init(|| Mutex::new(()))
|
||||
|
||||
Reference in New Issue
Block a user