client: recover relay launch after binary replacement
This commit is contained in:
parent
80062c1b4b
commit
b2e7a4cb38
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_client"
|
||||
version = "0.22.35"
|
||||
version = "0.22.36"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@ -1686,7 +1686,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_common"
|
||||
version = "0.22.35"
|
||||
version = "0.22.36"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@ -1698,7 +1698,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_server"
|
||||
version = "0.22.35"
|
||||
version = "0.22.36"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
|
||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
||||
|
||||
[package]
|
||||
name = "lesavka_client"
|
||||
version = "0.22.35"
|
||||
version = "0.22.36"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -15,6 +15,9 @@ use serial_test::serial;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::BTreeMap,
|
||||
fs,
|
||||
path::PathBuf,
|
||||
process,
|
||||
rc::Rc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
@ -35,6 +38,93 @@ fn rail_button_text(button: >k::Button) -> Option<String> {
|
||||
.map(|label| label.text().to_string())
|
||||
}
|
||||
|
||||
fn unique_test_path(label: &str) -> PathBuf {
|
||||
let nanos = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("system clock before unix epoch")
|
||||
.as_nanos();
|
||||
std::env::temp_dir().join(format!(
|
||||
"lesavka-relay-child-{label}-{}-{nanos}",
|
||||
process::id()
|
||||
))
|
||||
}
|
||||
|
||||
fn unique_test_file(label: &str) -> PathBuf {
|
||||
let path = unique_test_path(label);
|
||||
fs::write(&path, b"test executable placeholder").expect("write relay child test file");
|
||||
path
|
||||
}
|
||||
|
||||
fn unique_deleted_exe_path(label: &str) -> PathBuf {
|
||||
let path = unique_test_path(label);
|
||||
let deleted_path = path.with_file_name(format!(
|
||||
"{} (deleted)",
|
||||
path.file_name()
|
||||
.expect("test path has file name")
|
||||
.to_string_lossy()
|
||||
));
|
||||
fs::write(&deleted_path, b"test executable placeholder")
|
||||
.expect("write deleted relay child test file");
|
||||
deleted_path
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relay_child_exe_uses_current_exe_when_still_runnable() {
|
||||
let current = unique_test_file("current");
|
||||
let installed = unique_test_file("installed");
|
||||
|
||||
let resolved = resolve_relay_child_exe_from(None, ¤t, &installed)
|
||||
.expect("resolve relay child executable");
|
||||
|
||||
assert_eq!(resolved, current);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relay_child_exe_falls_back_when_launcher_exe_was_replaced() {
|
||||
let current = unique_deleted_exe_path("current");
|
||||
let installed = unique_test_file("installed");
|
||||
|
||||
let resolved = resolve_relay_child_exe_from(None, ¤t, &installed)
|
||||
.expect("resolve relay child executable");
|
||||
|
||||
assert_eq!(resolved, installed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relay_child_exe_falls_back_when_current_exe_path_is_missing() {
|
||||
let current = unique_test_path("missing-current");
|
||||
let installed = unique_test_file("installed");
|
||||
|
||||
let resolved = resolve_relay_child_exe_from(None, ¤t, &installed)
|
||||
.expect("resolve relay child executable");
|
||||
|
||||
assert_eq!(resolved, installed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relay_child_exe_honors_valid_env_override() {
|
||||
let override_path = unique_test_file("override");
|
||||
let current = unique_test_file("current");
|
||||
let installed = unique_test_file("installed");
|
||||
|
||||
let resolved = resolve_relay_child_exe_from(Some(&override_path), ¤t, &installed)
|
||||
.expect("resolve relay child executable");
|
||||
|
||||
assert_eq!(resolved, override_path);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relay_child_exe_rejects_invalid_env_override() {
|
||||
let override_path = unique_test_path("missing-override");
|
||||
let current = unique_test_file("current");
|
||||
let installed = unique_test_file("installed");
|
||||
|
||||
let err = resolve_relay_child_exe_from(Some(&override_path), ¤t, &installed)
|
||||
.expect_err("invalid override should fail loudly");
|
||||
|
||||
assert!(err.to_string().contains(CLIENT_BIN_ENV));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_test_detail_mentions_idle_and_running_modes() {
|
||||
assert!(local_test_detail(false, false, false, false).contains("idle"));
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
const CLIENT_BIN_ENV: &str = "LESAVKA_CLIENT_BIN";
|
||||
const INSTALLED_CLIENT_BIN: &str = "/usr/local/bin/lesavka-client";
|
||||
|
||||
pub fn spawn_client_process(
|
||||
server_addr: &str,
|
||||
state: &LauncherState,
|
||||
@ -6,8 +9,8 @@ pub fn spawn_client_process(
|
||||
input_state_path: &Path,
|
||||
input_toggle_control_path: &Path,
|
||||
) -> Result<RelayChild> {
|
||||
let exe = std::env::current_exe()?;
|
||||
let mut command = Command::new(exe);
|
||||
let exe = resolve_relay_child_exe()?;
|
||||
let mut command = Command::new(&exe);
|
||||
command.arg("--no-launcher");
|
||||
command.stdout(Stdio::piped());
|
||||
command.stderr(Stdio::piped());
|
||||
@ -54,7 +57,62 @@ pub fn spawn_client_process(
|
||||
for (key, value) in runtime_env_vars(state) {
|
||||
command.env(key, value);
|
||||
}
|
||||
Ok(command.spawn()?)
|
||||
let child = command.spawn().map_err(|err| {
|
||||
anyhow::anyhow!("starting relay child {} failed: {err}", exe.display())
|
||||
})?;
|
||||
Ok(child)
|
||||
}
|
||||
|
||||
fn resolve_relay_child_exe() -> Result<PathBuf> {
|
||||
let env_override = std::env::var_os(CLIENT_BIN_ENV).map(PathBuf::from);
|
||||
let current_exe = std::env::current_exe().map_err(|err| {
|
||||
anyhow::anyhow!("resolving launcher executable for relay child failed: {err}")
|
||||
})?;
|
||||
resolve_relay_child_exe_from(
|
||||
env_override.as_deref(),
|
||||
¤t_exe,
|
||||
Path::new(INSTALLED_CLIENT_BIN),
|
||||
)
|
||||
}
|
||||
|
||||
fn resolve_relay_child_exe_from(
|
||||
env_override: Option<&Path>,
|
||||
current_exe: &Path,
|
||||
installed_client: &Path,
|
||||
) -> Result<PathBuf> {
|
||||
if let Some(override_path) = env_override
|
||||
&& !override_path.as_os_str().is_empty()
|
||||
{
|
||||
if override_path.is_file() {
|
||||
return Ok(override_path.to_path_buf());
|
||||
}
|
||||
return Err(anyhow::anyhow!(
|
||||
"{CLIENT_BIN_ENV} points at {}, but that file is not runnable",
|
||||
override_path.display()
|
||||
));
|
||||
}
|
||||
|
||||
if relay_exe_path_is_runnable(current_exe) {
|
||||
return Ok(current_exe.to_path_buf());
|
||||
}
|
||||
|
||||
if installed_client.is_file() {
|
||||
return Ok(installed_client.to_path_buf());
|
||||
}
|
||||
|
||||
Err(anyhow::anyhow!(
|
||||
"launcher executable {} is no longer runnable and fallback {} is missing; restart Lesavka or reinstall the client",
|
||||
current_exe.display(),
|
||||
installed_client.display()
|
||||
))
|
||||
}
|
||||
|
||||
fn relay_exe_path_is_runnable(path: &Path) -> bool {
|
||||
path.is_file()
|
||||
&& !path
|
||||
.as_os_str()
|
||||
.to_string_lossy()
|
||||
.ends_with(" (deleted)")
|
||||
}
|
||||
|
||||
pub fn attach_child_log_streams(child: &mut RelayChild, tx: Sender<String>) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lesavka_common"
|
||||
version = "0.22.35"
|
||||
version = "0.22.36"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ bench = false
|
||||
|
||||
[package]
|
||||
name = "lesavka_server"
|
||||
version = "0.22.35"
|
||||
version = "0.22.36"
|
||||
edition = "2024"
|
||||
autobins = false
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user