aboutsummaryrefslogtreecommitdiff
path: root/src/server/autodetect.rs
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-06 02:27:06 +0300
committerGitHub <noreply@github.com>2026-09-06 02:27:06 +0300
commit9e9bc8a144667a6b7debfd4f0c7a31ff0e11493a (patch)
tree03f1259f91fd9edeacfd4b904f2e84d4acf25232 /src/server/autodetect.rs
parent35b0dff96579f099e0436290085024424ffb289c (diff)
feat: manage multiple ssh machines from one client (#3670)
Diffstat (limited to 'src/server/autodetect.rs')
-rw-r--r--src/server/autodetect.rs46
1 files changed, 31 insertions, 15 deletions
diff --git a/src/server/autodetect.rs b/src/server/autodetect.rs
index bd364a70..e6fdaaa4 100644
--- a/src/server/autodetect.rs
+++ b/src/server/autodetect.rs
@@ -142,7 +142,7 @@ fn client_protocol_accepts_hello(socket_path: &Path) -> io::Result<bool> {
}
}
-fn validate_running_server_compatibility() -> io::Result<()> {
+fn validate_running_server_compatibility(saved_federation: bool) -> io::Result<()> {
let Some(status) = read_server_status()? else {
return Err(io::Error::other(format!(
"a herdr server is listening, but its status API is unavailable.\n\n{}\nIf that fails, stop the old server process manually.",
@@ -150,16 +150,23 @@ fn validate_running_server_compatibility() -> io::Result<()> {
)));
};
- let endpoint_generation = status
- .capabilities
- .as_ref()
- .and_then(|capabilities| capabilities.endpoint_protocol_generation);
- if endpoint_generation == Some(crate::protocol::endpoint::ENDPOINT_PROTOCOL_GENERATION) {
+ let capabilities = status.capabilities.as_ref();
+ let endpoint_generation =
+ capabilities.and_then(|capabilities| capabilities.endpoint_protocol_generation);
+ let surface_interest = capabilities.is_some_and(|capabilities| capabilities.surface_interest);
+ if endpoint_generation == Some(crate::protocol::endpoint::ENDPOINT_PROTOCOL_GENERATION)
+ && (!saved_federation || surface_interest)
+ {
return Ok(());
}
+ let requirement = if saved_federation && !surface_interest {
+ "saved SSH machines require surface lifecycle support"
+ } else {
+ "the stable endpoint generation is incompatible"
+ };
Err(io::Error::other(format!(
- "This session predates Herdr's stable endpoint protocol and needs one final server update.\n\nserver: v{} endpoint generation {}\nclient: v{} endpoint generation {}\n\n{}",
+ "This session needs one final server update before Herdr can attach ({requirement}).\n\nserver: v{} endpoint generation {}\nclient: v{} endpoint generation {}\n\n{}",
status.version.as_deref().unwrap_or("unknown"),
endpoint_generation
.map(|value| value.to_string())
@@ -285,18 +292,27 @@ pub fn wait_for_server_socket(socket_path: &Path, timeout: Duration) -> io::Resu
/// 1. Check if a server is listening on the client socket
/// 2. If no server → spawn server daemon → wait for socket readiness
/// 3. Run the thin client (which connects to the server)
-pub fn auto_detect_launch() -> io::Result<()> {
+pub fn auto_detect_launch(saved_federation: bool) -> io::Result<()> {
let socket_path = client_socket_path();
info!(path = %socket_path.display(), "auto-detect launch starting");
- if is_server_listening_at(&socket_path) {
- validate_running_server_compatibility()?;
+ let startup = if is_server_listening_at(&socket_path) {
info!("server already running, attaching as client");
+ if saved_federation {
+ Ok(())
+ } else {
+ validate_running_server_compatibility(false)
+ }
} else {
info!("no server running, spawning server daemon");
- spawn_server_daemon()?;
- wait_for_server_socket(&socket_path, SERVER_READY_TIMEOUT)?;
- info!("server ready, attaching as client");
+ spawn_server_daemon()
+ .and_then(|_| wait_for_server_socket(&socket_path, SERVER_READY_TIMEOUT))
+ };
+ if let Err(error) = startup {
+ if !saved_federation {
+ return Err(error);
+ }
+ tracing::warn!(%error, "Local startup failed; keeping saved machines available");
}
// Now attach as a thin client.
@@ -522,7 +538,7 @@ test "$sid" = "$$"
let path = dir.join("api.sock");
std::env::set_var(crate::api::SOCKET_PATH_ENV_VAR, &path);
- let err = validate_running_server_compatibility().unwrap_err();
+ let err = validate_running_server_compatibility(false).unwrap_err();
assert!(
err.to_string().contains("status API is unavailable"),
@@ -558,7 +574,7 @@ test "$sid" = "$$"
stream.flush().unwrap();
});
- let err = validate_running_server_compatibility().unwrap_err();
+ let err = validate_running_server_compatibility(false).unwrap_err();
let message = err.to_string();
let _ = handle.join();