aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-06-01 19:40:50 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-06-01 19:40:50 +0300
commit6f9453a480db8f087e16b8eda008a355bf57cfb6 (patch)
treeca5cf2264e9d448406fbc64a3c301001af3484b8 /src
parent53e4b27165caeeaaf206907d082d3614876b2e4c (diff)
fix: clarify update restart prompts
Diffstat (limited to 'src')
-rw-r--r--src/remote.rs173
-rw-r--r--src/update.rs307
2 files changed, 212 insertions, 268 deletions
diff --git a/src/remote.rs b/src/remote.rs
index fbc062e1..d3810d5b 100644
--- a/src/remote.rs
+++ b/src/remote.rs
@@ -166,6 +166,7 @@ pub(crate) fn run_remote(remote: RemoteLaunch) -> io::Result<()> {
&remote.target,
&prepared_remote.remote_herdr,
prepared_remote.installed_or_replaced,
+ prepared_remote.stop_after_install_approved,
remote.live_handoff,
)?;
@@ -222,10 +223,9 @@ fn ensure_remote_server_running() -> io::Result<()> {
if status.protocol == Some(CURRENT_PROTOCOL) {
return Ok(());
}
- return Err(io::Error::other(format!(
- "remote herdr server is running with protocol {}, but this bridge needs protocol {CURRENT_PROTOCOL}; rerun `herdr --remote` from an interactive terminal to approve stopping it",
- protocol_label(status.protocol)
- )));
+ return Err(io::Error::other(
+ "remote herdr server must restart before this bridge can attach; rerun `herdr --remote` from an interactive terminal to approve stopping it",
+ ));
}
crate::server::autodetect::spawn_server_daemon()?;
@@ -370,6 +370,7 @@ struct InstallSource {
struct PreparedRemoteHerdr {
remote_herdr: RemoteHerdr,
installed_or_replaced: bool,
+ stop_after_install_approved: bool,
}
impl InstallSource {
@@ -411,22 +412,25 @@ fn prepare_remote_herdr(
return Ok(PreparedRemoteHerdr {
remote_herdr: path_remote_herdr.clone(),
installed_or_replaced: false,
+ stop_after_install_approved: false,
});
}
if remote_binary_matches(target, &remote_herdr)? {
return Ok(PreparedRemoteHerdr {
remote_herdr,
installed_or_replaced: false,
+ stop_after_install_approved: false,
});
}
}
+ let mut stop_after_install_approved = false;
if let Some(status_probe_herdr) = path_remote_herdr.as_ref().or_else(|| {
remote_binary_exists(target, &remote_herdr)
.ok()
.and_then(|exists| exists.then_some(&remote_herdr))
}) {
- confirm_remote_install_with_running_server(
+ stop_after_install_approved = confirm_remote_install_with_running_server(
target,
status_probe_herdr,
live_handoff_enabled,
@@ -453,6 +457,7 @@ fn prepare_remote_herdr(
Ok(PreparedRemoteHerdr {
remote_herdr,
installed_or_replaced: true,
+ stop_after_install_approved,
})
}
@@ -659,6 +664,7 @@ fn ensure_remote_server_ready(
target: &str,
remote_herdr: &RemoteHerdr,
remote_binary_changed: bool,
+ stop_after_install_approved: bool,
live_handoff_enabled: bool,
) -> io::Result<()> {
let status = remote_server_status(target, remote_herdr)?;
@@ -677,10 +683,7 @@ fn ensure_remote_server_ready(
return Ok(());
};
- if live_handoff_enabled
- && live_handoff
- && confirm_remote_server_handoff(target, version.as_deref(), protocol, reason)?
- {
+ if live_handoff_enabled && live_handoff {
match live_handoff_remote_server(target, remote_herdr) {
Ok(()) => return Ok(()),
Err(err) => {
@@ -690,6 +693,11 @@ fn ensure_remote_server_ready(
}
}
+ if stop_after_install_approved {
+ stop_remote_server(target, remote_herdr)?;
+ return Ok(());
+ }
+
if confirm_remote_server_stop(target, version.as_deref(), protocol, reason)? {
stop_remote_server(target, remote_herdr)?;
}
@@ -717,7 +725,7 @@ fn confirm_remote_install_with_running_server(
target: &str,
remote_herdr: &RemoteHerdr,
live_handoff_enabled: bool,
-) -> io::Result<()> {
+) -> io::Result<bool> {
let status = match remote_server_status(target, remote_herdr) {
Ok(status) => status,
Err(err) => {
@@ -729,65 +737,69 @@ fn confirm_remote_install_with_running_server(
eprintln!(
"could not inspect the running remote herdr server on {target} before installing: {err}"
);
- eprint!("continue installing the remote herdr binary? [Y/n] ");
+ eprint!("continue installing the remote herdr binary? [y/N] ");
io::stderr().flush()?;
let mut answer = String::new();
io::stdin().read_line(&mut answer)?;
let answer = answer.trim().to_ascii_lowercase();
- if answer == "n" || answer == "no" {
+ if answer != "y" && answer != "yes" {
return Err(io::Error::new(
io::ErrorKind::Interrupted,
"remote herdr install cancelled",
));
}
- return Ok(());
+ return Ok(false);
}
};
let RemoteServerStatus::Running {
version,
- protocol,
+ protocol: _,
live_handoff,
} = status
else {
- return Ok(());
+ return Ok(false);
};
- if live_handoff_enabled && live_handoff {
- return Ok(());
- }
-
if !io::stdin().is_terminal() {
+ if live_handoff_enabled && live_handoff {
+ return Ok(false);
+ }
return Err(io::Error::other(format!(
- "remote herdr server on {target} is running v{} protocol {}; run from an interactive terminal to approve updating the remote binary",
- version_label(version.as_deref()),
- protocol_label(protocol)
+ "remote herdr server on {target} is running v{}; run from an interactive terminal to approve stopping it for the update",
+ version_label(version.as_deref())
)));
}
+ if live_handoff_enabled && live_handoff {
+ eprintln!("remote herdr server on {target} is currently running:");
+ eprintln!(" server: v{}", version_label(version.as_deref()));
+ eprintln!(
+ "Herdr will install v{CURRENT_VERSION} and hand off live pane processes to the prepared server."
+ );
+ return Ok(false);
+ }
+
eprintln!("remote herdr server on {target} is currently running:");
+ eprintln!(" server: v{}", version_label(version.as_deref()));
eprintln!(
- " server: v{} protocol {}",
- version_label(version.as_deref()),
- protocol_label(protocol)
- );
- eprintln!(
- "this attach will not preserve running panes unless you pass --handoff and the remote server supports live handoff."
+ "To complete the remote update, Herdr must stop the running remote server after installing."
);
+ eprintln!("This stops active remote pane processes, including shells, dev servers, and tests.");
eprintln!();
- eprint!("continue installing the remote herdr binary? [Y/n] ");
+ eprint!("Install v{CURRENT_VERSION} and stop the remote server now? [y/N] ");
io::stderr().flush()?;
let mut answer = String::new();
io::stdin().read_line(&mut answer)?;
let answer = answer.trim().to_ascii_lowercase();
- if answer == "n" || answer == "no" {
+ if answer != "y" && answer != "yes" {
return Err(io::Error::new(
io::ErrorKind::Interrupted,
"remote herdr install cancelled",
));
}
- Ok(())
+ Ok(true)
}
fn remote_server_status(
@@ -848,14 +860,13 @@ fn parse_remote_server_status_json(status: &str) -> io::Result<RemoteServerStatu
fn confirm_remote_server_stop(
target: &str,
version: Option<&str>,
- protocol: Option<u32>,
+ _protocol: Option<u32>,
reason: RemoteServerRestartReason,
) -> io::Result<bool> {
if !io::stdin().is_terminal() {
if reason == RemoteServerRestartReason::ProtocolMismatch {
return Err(io::Error::other(format!(
- "remote herdr server on {target} is running with protocol {}, but this client needs protocol {CURRENT_PROTOCOL}; run from an interactive terminal to approve stopping it",
- protocol_label(protocol)
+ "remote herdr server on {target} must stop before this client can attach; run from an interactive terminal to approve stopping it"
)));
}
@@ -867,19 +878,13 @@ fn confirm_remote_server_stop(
}
eprintln!("remote herdr server on {target} is currently running:");
- eprintln!(
- " server: v{} protocol {}",
- version_label(version),
- protocol_label(protocol)
- );
- eprintln!(" prepared binary: v{CURRENT_VERSION} protocol {CURRENT_PROTOCOL}");
+ eprintln!(" server: v{}", version_label(version));
+ eprintln!(" prepared binary: v{CURRENT_VERSION}");
eprintln!();
match reason {
RemoteServerRestartReason::ProtocolMismatch => {
- eprintln!(
- "the remote server protocol does not match this client. the remote server must be stopped before attaching."
- );
+ eprintln!("the remote server must stop before this client can attach.");
}
RemoteServerRestartReason::BinaryUpdated => {
eprintln!(
@@ -896,7 +901,7 @@ fn confirm_remote_server_stop(
let prompt = if reason == RemoteServerRestartReason::ProtocolMismatch {
"stop the remote server and continue attaching? [Y/n] "
} else {
- "restart the remote server now? [Y/n] "
+ "restart the remote server now? [y/N] "
};
eprint!("{prompt}");
io::stderr().flush()?;
@@ -904,74 +909,20 @@ fn confirm_remote_server_stop(
let mut answer = String::new();
io::stdin().read_line(&mut answer)?;
let answer = answer.trim().to_ascii_lowercase();
- if answer == "n" || answer == "no" {
- if reason == RemoteServerRestartReason::ProtocolMismatch {
- return Err(io::Error::new(
- io::ErrorKind::Interrupted,
- "remote herdr server stop cancelled",
- ));
- }
- return Ok(false);
+ if answer == "y" || answer == "yes" {
+ return Ok(true);
}
-
- Ok(true)
-}
-
-fn confirm_remote_server_handoff(
- target: &str,
- version: Option<&str>,
- protocol: Option<u32>,
- reason: RemoteServerRestartReason,
-) -> io::Result<bool> {
- if !io::stdin().is_terminal() {
- if reason == RemoteServerRestartReason::ProtocolMismatch {
- return Err(io::Error::other(format!(
- "remote herdr server on {target} is running with protocol {}, but this client needs protocol {CURRENT_PROTOCOL}; run from an interactive terminal to approve live handoff or stopping it",
- protocol_label(protocol)
- )));
- }
-
- eprintln!(
- "remote herdr server on {target} is still running v{}; it will use v{CURRENT_VERSION} after it restarts.",
- version_label(version)
- );
- return Ok(false);
+ if answer.is_empty() && reason == RemoteServerRestartReason::ProtocolMismatch {
+ return Ok(true);
}
-
- eprintln!("remote herdr server on {target} is currently running:");
- eprintln!(
- " server: v{} protocol {}",
- version_label(version),
- protocol_label(protocol)
- );
- eprintln!(" prepared binary: v{CURRENT_VERSION} protocol {CURRENT_PROTOCOL}");
- eprintln!();
-
- match reason {
- RemoteServerRestartReason::ProtocolMismatch => {
- eprintln!(
- "the remote server protocol does not match this client. herdr will try to hand off live pane processes to the prepared remote server before the old server exits."
- );
- }
- RemoteServerRestartReason::BinaryUpdated => {
- eprintln!(
- "the remote herdr binary was installed or replaced. herdr will try to hand off live pane processes to the prepared remote server."
- );
- }
- RemoteServerRestartReason::VersionMismatch => {
- eprintln!(
- "the remote server is still running a different herdr version. herdr will try to hand off live pane processes to the prepared remote server."
- );
- }
+ if reason == RemoteServerRestartReason::ProtocolMismatch {
+ return Err(io::Error::new(
+ io::ErrorKind::Interrupted,
+ "remote herdr server stop cancelled",
+ ));
}
- eprint!("live-handoff remote panes to the prepared server? [Y/n] ");
- io::stderr().flush()?;
-
- let mut answer = String::new();
- io::stdin().read_line(&mut answer)?;
- let answer = answer.trim().to_ascii_lowercase();
- Ok(answer != "n" && answer != "no")
+ Ok(false)
}
fn live_handoff_remote_server(target: &str, remote_herdr: &RemoteHerdr) -> io::Result<()> {
@@ -1026,12 +977,6 @@ fn version_label(version: Option<&str>) -> &str {
version.unwrap_or("unknown")
}
-fn protocol_label(protocol: Option<u32>) -> String {
- protocol
- .map(|value| value.to_string())
- .unwrap_or_else(|| "unknown".to_string())
-}
-
fn warn_if_remote_bin_not_on_path(target: &str) -> io::Result<()> {
let output = ssh_output(
target,
diff --git a/src/update.rs b/src/update.rs
index c115efdb..d05365d1 100644
--- a/src/update.rs
+++ b/src/update.rs
@@ -414,12 +414,6 @@ fn client_protocol_server_is_running() -> bool {
client_protocol_server_is_running_at(&crate::server::socket_paths::client_socket_path())
}
-fn protocol_label(protocol: Option<u32>) -> String {
- protocol
- .map(|value| value.to_string())
- .unwrap_or_else(|| "unknown".to_string())
-}
-
fn version_label(version: Option<&str>) -> &str {
version.unwrap_or("unknown")
}
@@ -441,10 +435,11 @@ fn server_supports_live_handoff(server: &crate::api::RuntimeStatus) -> bool {
.is_some_and(|capabilities| capabilities.live_handoff)
}
-fn parse_live_handoff_before_update_response(input: &str) -> Option<bool> {
+fn parse_stop_old_servers_after_update_response(input: &str, default_yes: bool) -> Option<bool> {
let trimmed = input.trim().to_ascii_lowercase();
match trimmed.as_str() {
- "" | "y" | "yes" => Some(true),
+ "" => Some(default_yes),
+ "y" | "yes" => Some(true),
"n" | "no" => Some(false),
_ => None,
}
@@ -514,7 +509,6 @@ struct RunningSessionUpdateOutcome {
stop_command: String,
attach_command: Option<String>,
server_version: Option<String>,
- server_protocol: Option<u32>,
outcome: RunningServerUpdateOutcome,
}
@@ -703,28 +697,22 @@ fn prompt_to_stop_old_servers_before_update(
) -> Result<bool, String> {
if !io::stdin().is_terminal() {
return Err(
- "one or more Herdr sessions must restart for this update. Stop the old server to use the new version, then run `herdr update` again from an interactive terminal."
+ "one or more Herdr sessions must stop for this update. Stop running Herdr sessions when ready, then run `herdr update` again from an interactive terminal."
.to_string(),
);
}
eprintln!(
- "This update changes Herdr's client/server protocol.\n\nRunning sessions that must restart to use v{}:",
+ "Running sessions that must stop to use v{}:",
release.version
);
for plan in plans {
eprintln!(
- " {}: server v{} protocol {}",
+ " {}: server v{}",
plan.label(),
- version_label(plan.server.version.as_deref()),
- protocol_label(plan.server.protocol)
+ version_label(plan.server.version.as_deref())
);
}
- eprintln!(
- " update: v{} protocol {}",
- release.version,
- protocol_label(release.target_protocol)
- );
eprintln!();
eprintln!("If you choose no, these sessions keep using the old server until you stop them.");
eprintln!("Stop the old server after installing? Stopping exits pane processes.");
@@ -763,48 +751,20 @@ fn confirm_running_server_update_action(
print_running_session_update_summary(&plans, release, options);
if !options.live_handoff {
- let restart_required: Vec<RunningServerUpdatePlan> = plans
- .iter()
- .filter(|plan| plan.requires_server_restart)
- .cloned()
- .collect();
- let stop_restart_required = if restart_required.is_empty() {
- false
- } else {
- prompt_to_stop_old_servers_before_update(&restart_required, release)?
- };
return Ok(plans
.into_iter()
- .map(|plan| {
- let action = if plan.requires_server_restart && stop_restart_required {
- RunningServerUpdateAction::StopOldServer
- } else {
- RunningServerUpdateAction::None
- };
- RunningServerUpdateDecision { plan, action }
+ .map(|plan| RunningServerUpdateDecision {
+ plan,
+ action: RunningServerUpdateAction::None,
})
.collect());
}
- let handoff_supported: Vec<&RunningServerUpdatePlan> = plans
- .iter()
- .filter(|plan| server_supports_live_handoff(&plan.server))
- .collect();
let handoff_unsupported_requiring_update: Vec<&RunningServerUpdatePlan> = plans
.iter()
.filter(|plan| !server_supports_live_handoff(&plan.server) && plan.requires_server_restart)
.collect();
- let live_handoff = if handoff_supported.is_empty() {
- false
- } else {
- prompt_to_live_handoff_sessions_before_update(
- &handoff_supported,
- release,
- plans.iter().any(|plan| plan.requires_server_restart),
- )?
- };
-
let stop_unsupported = if handoff_unsupported_requiring_update.is_empty() {
false
} else {
@@ -817,7 +777,7 @@ fn confirm_running_server_update_action(
let mut decisions = Vec::new();
for plan in plans {
- let action = if server_supports_live_handoff(&plan.server) && live_handoff {
+ let action = if server_supports_live_handoff(&plan.server) {
RunningServerUpdateAction::LiveHandoff
} else if !server_supports_live_handoff(&plan.server)
&& plan.requires_server_restart
@@ -833,75 +793,54 @@ fn confirm_running_server_update_action(
Ok(decisions)
}
-fn print_running_session_update_summary(
- plans: &[RunningServerUpdatePlan],
- release: &ReleaseInfo,
- options: SelfUpdateOptions,
-) {
- eprintln!("running herdr targets:");
- for plan in plans {
- if options.live_handoff {
- let capability = if server_supports_live_handoff(&plan.server) {
- "handoff supported"
- } else {
- "too old for handoff"
- };
- eprintln!(
- " {}: v{} protocol {} ({})",
- plan.label(),
- version_label(plan.server.version.as_deref()),
- protocol_label(plan.server.protocol),
- capability
- );
- } else {
- eprintln!(
- " {}: v{} protocol {}",
- plan.label(),
- version_label(plan.server.version.as_deref()),
- protocol_label(plan.server.protocol)
- );
- }
+fn target_group_nouns(plans: &[&RunningServerUpdatePlan]) -> (&'static str, &'static str) {
+ let all_sessions = plans.iter().all(|plan| plan.target_noun() == "session");
+ let all_servers = plans.iter().all(|plan| plan.target_noun() == "server");
+ if all_sessions {
+ ("session", "sessions")
+ } else if all_servers {
+ ("server", "servers")
+ } else {
+ ("target", "targets")
}
- eprintln!(
- " update: v{} protocol {}",
- release.version,
- protocol_label(release.target_protocol)
- );
- eprintln!();
}
-fn prompt_to_live_handoff_sessions_before_update(
- plans: &[&RunningServerUpdatePlan],
+fn prompt_to_complete_plain_update(
+ decisions: &[RunningServerUpdateDecision],
release: &ReleaseInfo,
- requires_live_handoff: bool,
) -> Result<bool, String> {
+ if decisions.is_empty() {
+ return Ok(true);
+ }
+
if !io::stdin().is_terminal() {
- if requires_live_handoff {
- return Err(format!(
- "one or more herdr targets are running and updating to v{} requires live server handoff; run `herdr update` from an interactive terminal, or stop those targets and run `herdr update` again",
- release.version
- ));
- }
- eprintln!(
- "herdr targets are running. updating the binary will not affect them until they restart."
- );
return Ok(false);
}
+ let plans: Vec<&RunningServerUpdatePlan> =
+ decisions.iter().map(|decision| &decision.plan).collect();
+ let (singular, plural) = target_group_nouns(&plans);
+ let noun = if plans.len() == 1 { singular } else { plural };
eprintln!(
- "herdr can hand off {} running target{} to the new server so pane processes keep running.",
+ "To complete the update, Herdr must stop {} running {}.",
plans.len(),
- if plans.len() == 1 { "" } else { "s" }
+ noun
);
- eprintln!("connected clients will disconnect during handoff and can attach again afterward.");
+ eprintln!("This stops active pane processes, including shells, dev servers, and tests.");
+ for plan in plans {
+ eprintln!(
+ " {} {}: server v{}",
+ plan.target_noun(),
+ plan.label(),
+ version_label(plan.server.version.as_deref())
+ );
+ }
loop {
- let prompt = if requires_live_handoff {
- "update and live-handoff supported targets to the new server? [Y/n] "
- } else {
- "live-handoff supported running targets after updating? [Y/n] "
- };
- eprint!("{prompt}");
+ eprint!(
+ "Stop running {} and install v{} now? [y/N] ",
+ noun, release.version
+ );
io::stderr()
.flush()
.map_err(|e| format!("failed to flush prompt: {e}"))?;
@@ -914,14 +853,56 @@ fn prompt_to_live_handoff_sessions_before_update(
return Ok(false);
}
- if let Some(answer) = parse_live_handoff_before_update_response(&input) {
+ if let Some(answer) = parse_stop_old_servers_after_update_response(&input, false) {
return Ok(answer);
}
-
eprintln!("please answer y or n");
}
}
+fn mark_plain_update_stop_decisions(
+ decisions: Vec<RunningServerUpdateDecision>,
+) -> Vec<RunningServerUpdateDecision> {
+ decisions
+ .into_iter()
+ .map(|mut decision| {
+ decision.action = RunningServerUpdateAction::StopOldServer;
+ decision
+ })
+ .collect()
+}
+
+fn print_running_session_update_summary(
+ plans: &[RunningServerUpdatePlan],
+ release: &ReleaseInfo,
+ options: SelfUpdateOptions,
+) {
+ eprintln!("running herdr targets:");
+ for plan in plans {
+ if options.live_handoff {
+ let capability = if server_supports_live_handoff(&plan.server) {
+ "handoff supported"
+ } else {
+ "too old for handoff"
+ };
+ eprintln!(
+ " {}: server v{} ({})",
+ plan.label(),
+ version_label(plan.server.version.as_deref()),
+ capability
+ );
+ } else {
+ eprintln!(
+ " {}: server v{}",
+ plan.label(),
+ version_label(plan.server.version.as_deref())
+ );
+ }
+ }
+ eprintln!(" update: v{}", release.version);
+ eprintln!();
+}
+
fn live_handoff_running_server_for_update(
plan: &RunningServerUpdatePlan,
release: &ReleaseInfo,
@@ -974,16 +955,8 @@ fn prompt_to_stop_old_server_after_failed_handoff(
plan.target_noun(),
plan.label()
);
- eprintln!(
- " server: v{} protocol {}",
- version_label(status.version.as_deref()),
- protocol_label(status.protocol)
- );
- eprintln!(
- " installed: v{} protocol {}",
- release.version,
- protocol_label(release.target_protocol)
- );
+ eprintln!(" server: v{}", version_label(status.version.as_deref()));
+ eprintln!(" installed: v{}", release.version);
eprintln!(
"you can keep using the old server, or stop it now so the next `herdr` start uses v{}.",
release.version
@@ -1329,7 +1302,6 @@ fn apply_running_session_update_decisions(
stop_command,
attach_command: decision.plan.attach_command(),
server_version: decision.plan.server.version.clone(),
- server_protocol: decision.plan.server.protocol,
outcome,
});
}
@@ -1339,7 +1311,7 @@ fn apply_running_session_update_decisions(
fn print_running_session_update_outcomes(
outcomes: &[RunningSessionUpdateOutcome],
- _release: &ReleaseInfo,
+ release: &ReleaseInfo,
) {
if outcomes.is_empty() {
eprintln!("run herdr again.");
@@ -1363,19 +1335,20 @@ fn print_running_session_update_outcomes(
}
RunningServerUpdateOutcome::RestartDeferred => {
eprintln!(
- "{} {} is still running v{} protocol {}.",
- outcome.target_noun,
- outcome.session_label,
- version_label(outcome.server_version.as_deref()),
- protocol_label(outcome.server_protocol)
- );
- eprintln!(
- "{}",
- crate::session::restart_after_update_guidance(
- &outcome.stop_command,
- outcome.attach_command.as_deref()
- )
+ "{} {} kept running.",
+ outcome.target_noun, outcome.session_label
);
+ eprintln!("Stopping exits active pane processes.");
+ match &outcome.attach_command {
+ Some(command) => eprintln!(
+ "Run `{}`, then run `{command}` when ready to use v{}.",
+ outcome.stop_command, release.version
+ ),
+ None => eprintln!(
+ "Run `{}`, then restart Herdr with the same socket override when ready to use v{}.",
+ outcome.stop_command, release.version
+ ),
+ }
}
RunningServerUpdateOutcome::Stopped
| RunningServerUpdateOutcome::FailedHandoffOldServerStopped
@@ -1394,11 +1367,10 @@ fn print_running_session_update_outcomes(
}
RunningServerUpdateOutcome::FailedHandoffOldServerKept => {
eprintln!(
- "{} {} is still running v{} protocol {}.",
+ "{} {} is still running server v{}.",
outcome.target_noun,
outcome.session_label,
- version_label(outcome.server_version.as_deref()),
- protocol_label(outcome.server_protocol)
+ version_label(outcome.server_version.as_deref())
);
eprintln!(
"{}",
@@ -1567,10 +1539,23 @@ pub fn self_update(options: SelfUpdateOptions) -> Result<Version, String> {
}
let downloaded_update = download_update(&release)?;
let updated_exe = downloaded_update.current_exe.clone();
+ eprintln!("downloaded v{}", release.version);
+ if !options.live_handoff
+ && !prompt_to_complete_plain_update(&server_update_decisions, &release)?
+ {
+ eprintln!("Herdr was not updated.");
+ eprintln!("Stop running Herdr sessions when ready, then run `herdr update` again.");
+ return Ok(current);
+ }
install_downloaded_update(downloaded_update)?;
+ eprintln!("installed v{}", release.version);
+ let server_update_decisions = if options.live_handoff {
+ server_update_decisions
+ } else {
+ mark_plain_update_stop_decisions(server_update_decisions)
+ };
let server_update_outcomes =
apply_running_session_update_decisions(&release, &updated_exe, server_update_decisions)?;
- eprintln!("updated to v{}", release.version);
print_outdated_integration_notice_with_updated_binary(&updated_exe);
print_running_session_update_outcomes(&server_update_outcomes, &release);
@@ -2054,14 +2039,27 @@ mod tests {
}
#[test]
- fn parse_live_handoff_before_update_response_defaults_yes_for_blank() {
- assert_eq!(parse_live_handoff_before_update_response(""), Some(true));
- assert_eq!(parse_live_handoff_before_update_response("\n"), Some(true));
- assert_eq!(parse_live_handoff_before_update_response("y"), Some(true));
- assert_eq!(parse_live_handoff_before_update_response("yes"), Some(true));
- assert_eq!(parse_live_handoff_before_update_response("n"), Some(false));
- assert_eq!(parse_live_handoff_before_update_response("no"), Some(false));
- assert_eq!(parse_live_handoff_before_update_response("later"), None);
+ fn parse_stop_old_servers_after_update_response_uses_prompt_default_for_blank() {
+ assert_eq!(
+ parse_stop_old_servers_after_update_response("", true),
+ Some(true)
+ );
+ assert_eq!(
+ parse_stop_old_servers_after_update_response("\n", false),
+ Some(false)
+ );
+ assert_eq!(
+ parse_stop_old_servers_after_update_response("y", false),
+ Some(true)
+ );
+ assert_eq!(
+ parse_stop_old_servers_after_update_response("no", true),
+ Some(false)
+ );
+ assert_eq!(
+ parse_stop_old_servers_after_update_response("later", true),
+ None
+ );
}
#[test]
@@ -2098,7 +2096,7 @@ mod tests {
}
#[test]
- fn plain_update_requires_restart_for_supported_servers_without_handoff() {
+ fn plain_update_defers_stop_prompt_until_after_install() {
assert!(
!io::stdin().is_terminal(),
"this test relies on noninteractive test stdin"
@@ -2122,17 +2120,18 @@ mod tests {
},
};
- let err = confirm_running_server_update_action(
+ let decisions = confirm_running_server_update_action(
vec![plan],
&release,
SelfUpdateOptions {
live_handoff: false,
},
)
- .unwrap_err();
+ .unwrap();
- assert!(err.contains("must restart"), "unexpected error: {err}");
- assert!(!err.contains("live handoff"), "unexpected error: {err}");
+ assert_eq!(decisions.len(), 1);
+ assert_eq!(decisions[0].action, RunningServerUpdateAction::None);
+ assert!(decisions[0].plan.requires_server_restart);
}
#[test]
@@ -2291,7 +2290,7 @@ mod tests {
}
#[test]
- fn noninteractive_plain_update_requiring_restart_fails_without_handoff() {
+ fn noninteractive_plain_update_does_not_complete_with_running_server() {
let _guard = env_lock().lock().unwrap();
assert!(
!io::stdin().is_terminal(),
@@ -2324,17 +2323,17 @@ mod tests {
server,
};
- let err = confirm_running_server_update_action(
+ let decisions = confirm_running_server_update_action(
vec![plan],
&release,
SelfUpdateOptions {
live_handoff: false,
},
)
- .unwrap_err();
+ .unwrap();
+ let complete = prompt_to_complete_plain_update(&decisions, &release).unwrap();
- assert!(err.contains("must restart"), "unexpected error: {err}");
- assert!(!err.contains("live handoff"), "unexpected error: {err}");
+ assert!(!complete);
std::env::remove_var(crate::session::SESSION_ENV_VAR);
crate::session::clear_explicit_session_for_test();
}