diff --git a/app/src-tauri/build.rs b/app/src-tauri/build.rs index d860e1e..817559d 100644 --- a/app/src-tauri/build.rs +++ b/app/src-tauri/build.rs @@ -1,3 +1,28 @@ +use std::process::Command; + +// Stamp the GUI with the same git build id as the bundled daemon so the bridge +// can detect and restart a stale running daemon. Matches crates/spaceshd/build.rs. fn main() { + println!("cargo:rustc-env=SPACESH_BUILD={}", git_build()); + println!("cargo:rerun-if-changed=../../.git/HEAD"); + println!("cargo:rerun-if-changed=../../.git/index"); tauri_build::build() } + +fn git_build() -> String { + let sha = Command::new("git") + .args(["rev-parse", "--short=12", "HEAD"]) + .output() + .ok() + .filter(|o| o.status.success()) + .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) + .filter(|s| !s.is_empty()); + let Some(sha) = sha else { return "dev".into() }; + let dirty = Command::new("git") + .args(["status", "--porcelain"]) + .output() + .ok() + .map(|o| !o.stdout.is_empty()) + .unwrap_or(false); + if dirty { format!("{sha}-dirty") } else { sha } +} diff --git a/app/src-tauri/src/bridge.rs b/app/src-tauri/src/bridge.rs index 904941b..4e3c592 100644 --- a/app/src-tauri/src/bridge.rs +++ b/app/src-tauri/src/bridge.rs @@ -120,7 +120,7 @@ impl Bridge { let pending: Arc>>> = Arc::default(); let out_channels: Arc>>>> = Arc::default(); let (tx, reader) = spawn_connection(&sock, &app, pending.clone(), out_channels.clone()).await?; - Ok(Self { + let bridge = Self { next_id: AtomicU64::new(1), app, sock, @@ -130,7 +130,34 @@ impl Bridge { reader: Mutex::new(reader), pending, out_channels, - }) + }; + bridge.ensure_matching_daemon().await; + Ok(bridge) + } + + /// If a previously-running daemon is a different build than the one bundled + /// with this GUI (the daemon outlives the GUI, so an old one can still be + /// serving the socket), restart it so our matching daemon takes over. No-op + /// for unstamped dev builds or when the daemon is too old to report a build. + async fn ensure_matching_daemon(&self) { + let want = option_env!("SPACESH_BUILD").unwrap_or("dev"); + if want == "dev" { + return; + } + let got = match self.request(Cmd::Health).await { + Ok(env) => data_of(env) + .ok() + .and_then(|v| v.get("build").and_then(|b| b.as_str()).map(str::to_string)) + .unwrap_or_default(), + Err(_) => return, + }; + if got.is_empty() || got == want { + return; + } + // Stale daemon — stop it and respawn our bundled one (matching code). + let seen = self.gen.load(Ordering::Acquire); + let _ = self.request(Cmd::Shutdown).await; // daemon exits; reply may not arrive + let _ = self.reconnect(seen).await; // ensure_daemon respawns the bundled daemon } /// Re-establish the daemon connection. Single-flight: callers pass the `gen` diff --git a/app/src/Settings.tsx b/app/src/Settings.tsx index 37e6807..3f3e07c 100644 --- a/app/src/Settings.tsx +++ b/app/src/Settings.tsx @@ -83,7 +83,7 @@ function DaemonSection({ health, onReload }: { health: DaemonHealth | null; onRe
Daemon
{health ? (<> -
version {health.version} · pid {health.pid}
+
version {health.version}{health.build ? ` · ${health.build}` : ""} · pid {health.pid}
uptime {fmtUptime(health.started_at_ms)}
) :
offline
}
diff --git a/app/src/socketBridge.ts b/app/src/socketBridge.ts index 5cffde0..9bae7ff 100644 --- a/app/src/socketBridge.ts +++ b/app/src/socketBridge.ts @@ -178,7 +178,7 @@ export async function closeSurfaceCmd(surfaceId: string): Promise { await invoke("close_surface", { surfaceId }); } -export interface DaemonHealth { version: string; pid: number; started_at_ms: number } +export interface DaemonHealth { version: string; build?: string; pid: number; started_at_ms: number } export async function getHealth(): Promise { return await invoke("health"); diff --git a/crates/spaceshd/build.rs b/crates/spaceshd/build.rs new file mode 100644 index 0000000..bda237d --- /dev/null +++ b/crates/spaceshd/build.rs @@ -0,0 +1,27 @@ +use std::process::Command; + +// Stamp the binary with the current git build id so the GUI can detect a stale +// running daemon (different code) and restart it. Matches app/src-tauri/build.rs. +fn main() { + println!("cargo:rustc-env=SPACESH_BUILD={}", git_build()); + println!("cargo:rerun-if-changed=../../.git/HEAD"); + println!("cargo:rerun-if-changed=../../.git/index"); +} + +fn git_build() -> String { + let sha = Command::new("git") + .args(["rev-parse", "--short=12", "HEAD"]) + .output() + .ok() + .filter(|o| o.status.success()) + .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()) + .filter(|s| !s.is_empty()); + let Some(sha) = sha else { return "dev".into() }; + let dirty = Command::new("git") + .args(["status", "--porcelain"]) + .output() + .ok() + .map(|o| !o.stdout.is_empty()) + .unwrap_or(false); + if dirty { format!("{sha}-dirty") } else { sha } +} diff --git a/crates/spaceshd/src/server.rs b/crates/spaceshd/src/server.rs index 04a4bdd..7cb7319 100644 --- a/crates/spaceshd/src/server.rs +++ b/crates/spaceshd/src/server.rs @@ -622,6 +622,7 @@ async fn handle_request( Cmd::Health => { let _ = out.send(ok(id, serde_json::json!({ "version": env!("CARGO_PKG_VERSION"), + "build": option_env!("SPACESH_BUILD").unwrap_or("dev"), "pid": std::process::id(), "started_at_ms": started_at_ms, }))).await;