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]]
|
[[package]]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.22.35"
|
version = "0.22.36"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -1686,7 +1686,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.22.35"
|
version = "0.22.36"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@ -1698,7 +1698,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.22.35"
|
version = "0.22.36"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.22.35"
|
version = "0.22.36"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -15,6 +15,9 @@ use serial_test::serial;
|
|||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::BTreeMap,
|
collections::BTreeMap,
|
||||||
|
fs,
|
||||||
|
path::PathBuf,
|
||||||
|
process,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
@ -35,6 +38,93 @@ fn rail_button_text(button: >k::Button) -> Option<String> {
|
|||||||
.map(|label| label.text().to_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]
|
#[test]
|
||||||
fn local_test_detail_mentions_idle_and_running_modes() {
|
fn local_test_detail_mentions_idle_and_running_modes() {
|
||||||
assert!(local_test_detail(false, false, false, false).contains("idle"));
|
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(
|
pub fn spawn_client_process(
|
||||||
server_addr: &str,
|
server_addr: &str,
|
||||||
state: &LauncherState,
|
state: &LauncherState,
|
||||||
@ -6,8 +9,8 @@ pub fn spawn_client_process(
|
|||||||
input_state_path: &Path,
|
input_state_path: &Path,
|
||||||
input_toggle_control_path: &Path,
|
input_toggle_control_path: &Path,
|
||||||
) -> Result<RelayChild> {
|
) -> Result<RelayChild> {
|
||||||
let exe = std::env::current_exe()?;
|
let exe = resolve_relay_child_exe()?;
|
||||||
let mut command = Command::new(exe);
|
let mut command = Command::new(&exe);
|
||||||
command.arg("--no-launcher");
|
command.arg("--no-launcher");
|
||||||
command.stdout(Stdio::piped());
|
command.stdout(Stdio::piped());
|
||||||
command.stderr(Stdio::piped());
|
command.stderr(Stdio::piped());
|
||||||
@ -54,7 +57,62 @@ pub fn spawn_client_process(
|
|||||||
for (key, value) in runtime_env_vars(state) {
|
for (key, value) in runtime_env_vars(state) {
|
||||||
command.env(key, value);
|
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>) {
|
pub fn attach_child_log_streams(child: &mut RelayChild, tx: Sender<String>) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.22.35"
|
version = "0.22.36"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ bench = false
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.22.35"
|
version = "0.22.36"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
autobins = false
|
autobins = false
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user