mirror of
https://github.com/safing/portmaster.git
synced 2026-05-20 20:40:36 +00:00
Merge pull request #2155 from safing/feature/s40-restart_ui_on_upgrade
(feat) Restart the UI process after automatic update The Tauri (UI) process now automatically restarts after a successful update.
This commit is contained in:
@@ -19,6 +19,7 @@ mod portmaster;
|
||||
mod traymenu;
|
||||
mod window;
|
||||
mod commands;
|
||||
mod relaunch;
|
||||
|
||||
use log::{debug, error, info};
|
||||
use portmaster::PortmasterExt;
|
||||
@@ -61,6 +62,22 @@ impl portmaster::Handler for WsHandler {
|
||||
fn on_connect(&mut self, cli: portapi::client::PortAPI) {
|
||||
info!("connection established, creating main window");
|
||||
|
||||
// If an restart-ui-process was observed before disconnect (e.g. on upgrade),
|
||||
// relaunch the UI process now that the core is reachable again.
|
||||
if self.handle.portmaster().consume_restart_ui_proc_requested() {
|
||||
info!("restart-ui-process pending, relaunching UI process");
|
||||
match relaunch::request_ui_relaunch() {
|
||||
Ok(()) => {
|
||||
self.handle.exit(0);
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
error!("failed to relaunch UI process after upgrade: {}", err);
|
||||
error!("continuing with current UI process");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we successfully connected to Portmaster. Set is_first_connect to false
|
||||
// so we don't show the splash-screen when we loose connection.
|
||||
self.is_first_connect = false;
|
||||
@@ -141,6 +158,8 @@ fn show_webview_not_installed_dialog() -> i32 {
|
||||
}
|
||||
|
||||
fn main() {
|
||||
relaunch::run_relaunch_helper_if_requested();
|
||||
|
||||
if tauri::webview_version().is_err() {
|
||||
std::process::exit(show_webview_not_installed_dialog());
|
||||
}
|
||||
|
||||
@@ -75,6 +75,10 @@ pub struct PortmasterInterface<R: Runtime> {
|
||||
|
||||
// handle to the tray handler task so we can abort it when reconnecting
|
||||
pub tray_handler_task: Mutex<Option<tauri::async_runtime::JoinHandle<()>>>,
|
||||
|
||||
// Marks that a UI process restart event was observed (e.g. on upgrade)
|
||||
// and the UI process should relaunch after the next successful reconnect.
|
||||
pending_restart: AtomicBool,
|
||||
}
|
||||
|
||||
impl<R: Runtime> PortmasterInterface<R> {
|
||||
@@ -261,6 +265,16 @@ impl<R: Runtime> PortmasterInterface<R> {
|
||||
});
|
||||
}
|
||||
|
||||
/// Marks that a UI process restart has been requested.
|
||||
pub fn mark_restart_ui_proc_requested(&self) {
|
||||
self.pending_restart.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
/// Returns whether a UI process restart was requested and clears the flag.
|
||||
pub fn consume_restart_ui_proc_requested(&self) -> bool {
|
||||
self.pending_restart.swap(false, Ordering::AcqRel)
|
||||
}
|
||||
|
||||
//// Internal functions
|
||||
fn start_notification_handler(&self) {
|
||||
if let Some(api) = self.get_api() {
|
||||
@@ -346,6 +360,7 @@ pub fn setup(app: AppHandle) {
|
||||
handle_prompts: AtomicBool::new(false),
|
||||
should_show_after_bootstrap: AtomicBool::new(true),
|
||||
tray_handler_task: Mutex::new(None),
|
||||
pending_restart: AtomicBool::new(false),
|
||||
};
|
||||
|
||||
app.manage(interface);
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
use std::{
|
||||
ffi::OsString,
|
||||
path::Path,
|
||||
process::{Command, Stdio},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use log::{debug, error, warn};
|
||||
|
||||
const UI_RELAUNCH_HELPER_ENV: &str = "PORTMASTER_UI_RELAUNCH_HELPER";
|
||||
const RELAUNCH_RETRY_COUNT: usize = 40;
|
||||
const RELAUNCH_RETRY_DELAY: Duration = Duration::from_millis(500);
|
||||
|
||||
fn current_process_argv0() -> Option<OsString> {
|
||||
std::env::args_os().next()
|
||||
}
|
||||
|
||||
fn is_usable_launch_program(program: &OsString) -> bool {
|
||||
let path = Path::new(program);
|
||||
|
||||
// Fail closed for command-only values (for example, "portmaster"): we cannot
|
||||
// verify where they resolve to, so do not use them for relaunch.
|
||||
if !path.is_absolute() && path.components().count() <= 1 {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !path.exists() || !path.is_file() {
|
||||
return false;
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
if let Ok(meta) = std::fs::metadata(path) {
|
||||
return meta.permissions().mode() & 0o111 != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_launch_program() -> Result<OsString, String> {
|
||||
let current_exe = std::env::current_exe().ok().map(|p| p.into_os_string());
|
||||
let argv0 = current_process_argv0();
|
||||
|
||||
if let Some(program) = current_exe.as_ref().filter(|p| is_usable_launch_program(p)) {
|
||||
return Ok(program.clone());
|
||||
}
|
||||
|
||||
if let Some(program) = argv0.as_ref().filter(|p| is_usable_launch_program(p)) {
|
||||
return Ok(program.clone());
|
||||
}
|
||||
|
||||
Err("failed to determine relaunch executable: no verified launchable file path from current_exe or argv0".to_string())
|
||||
}
|
||||
|
||||
fn current_process_args() -> Vec<OsString> {
|
||||
std::env::args_os()
|
||||
.skip(1)
|
||||
.filter(|arg| {
|
||||
// On upgrade-triggered relaunch we always want to show the UI,
|
||||
// so do not propagate background startup flags.
|
||||
arg != "--background" && arg != "-b"
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn request_ui_relaunch() -> Result<(), String> {
|
||||
let exe = resolve_launch_program()?;
|
||||
let args = current_process_args();
|
||||
|
||||
let mut cmd = Command::new(&exe);
|
||||
cmd.args(&args)
|
||||
.env(UI_RELAUNCH_HELPER_ENV, "1")
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
|
||||
cmd.spawn()
|
||||
.map_err(|err| format!("failed to spawn relaunch helper process: {}", err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_relaunch_helper_if_requested() {
|
||||
if std::env::var(UI_RELAUNCH_HELPER_ENV).ok().as_deref() != Some("1") {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(err) = run_relaunch_helper() {
|
||||
error!("[tauri] relaunch helper failed: {}", err);
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
fn run_relaunch_helper() -> Result<(), String> {
|
||||
let exe = resolve_launch_program()?;
|
||||
let args = current_process_args();
|
||||
|
||||
debug!("[tauri] relaunch helper started");
|
||||
|
||||
for attempt in 1..=RELAUNCH_RETRY_COUNT {
|
||||
let mut cmd = Command::new(&exe);
|
||||
cmd.args(&args)
|
||||
.env_remove(UI_RELAUNCH_HELPER_ENV)
|
||||
.stdin(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::null());
|
||||
|
||||
let mut child = cmd
|
||||
.spawn()
|
||||
.map_err(|err| format!("failed to spawn replacement process: {}", err))?;
|
||||
|
||||
thread::sleep(RELAUNCH_RETRY_DELAY);
|
||||
|
||||
match child.try_wait() {
|
||||
Ok(Some(status)) => {
|
||||
// Most commonly means the single-instance guard still detected a running instance.
|
||||
warn!(
|
||||
"[tauri] replacement process exited quickly (attempt {}/{}; status={}), retrying",
|
||||
attempt,
|
||||
RELAUNCH_RETRY_COUNT,
|
||||
status
|
||||
);
|
||||
thread::sleep(RELAUNCH_RETRY_DELAY);
|
||||
}
|
||||
Ok(None) => {
|
||||
debug!(
|
||||
"[tauri] replacement process is running (attempt {}/{})",
|
||||
attempt,
|
||||
RELAUNCH_RETRY_COUNT
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
Err(err) => {
|
||||
return Err(format!("failed to observe replacement process status: {}", err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(format!(
|
||||
"failed to relaunch UI process after {} attempts",
|
||||
RELAUNCH_RETRY_COUNT
|
||||
))
|
||||
}
|
||||
@@ -497,6 +497,22 @@ pub async fn tray_handler(cli: PortAPI, app: tauri::AppHandle) {
|
||||
}
|
||||
};
|
||||
|
||||
let mut portmaster_restart_ui_proc_event_subscription = match cli
|
||||
.request(Request::Subscribe(
|
||||
"query runtime:modules/core/event/restart-ui-process".to_string(),
|
||||
))
|
||||
.await
|
||||
{
|
||||
Ok(rx) => rx,
|
||||
Err(err) => {
|
||||
error!(
|
||||
"cancel try_handler: failed to subscribe to 'runtime:modules/core/event/restart-ui-process': {}",
|
||||
err
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
update_icon_color(&icon, IconColor::Blue);
|
||||
|
||||
let mut system_status = SystemStatus::default();
|
||||
@@ -610,6 +626,20 @@ pub async fn tray_handler(cli: PortAPI, app: tauri::AppHandle) {
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
},
|
||||
msg = portmaster_restart_ui_proc_event_subscription.recv() => {
|
||||
let msg = match msg {
|
||||
Some(m) => m,
|
||||
None => { break }
|
||||
};
|
||||
|
||||
debug!("Upgrade restart event received: {:?}", msg);
|
||||
match msg {
|
||||
Response::Ok(_, _) | Response::New(_, _) | Response::Update(_, _) => {
|
||||
app.portmaster().mark_restart_ui_proc_requested();
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -180,9 +180,14 @@ func shutdown(_ *api.Request) (msg string, err error) {
|
||||
}
|
||||
|
||||
// restart restarts the Portmaster.
|
||||
func restart(_ *api.Request) (msg string, err error) {
|
||||
func restart(ar *api.Request) (msg string, err error) {
|
||||
log.Info("core: user requested restart via action")
|
||||
|
||||
// If the restart request came from an upgrade, also trigger a module event to restart the UI process, so that it can restart itself as well.
|
||||
if ar != nil && ar.Request != nil && ar.Request.URL.Query().Get("source") == "upgrade" {
|
||||
pushModuleEvent("core", "restart-ui-process", false, nil)
|
||||
}
|
||||
|
||||
// Trigger restart
|
||||
module.instance.Restart()
|
||||
|
||||
|
||||
@@ -377,7 +377,8 @@ func copyAndCheckSHA256Sum(src, dst, sha256sum string, filePermission utils.FSPe
|
||||
// Check expected hash.
|
||||
var expectedDigest []byte
|
||||
if sha256sum != "" {
|
||||
expectedDigest, err := hex.DecodeString(sha256sum)
|
||||
var err error
|
||||
expectedDigest, err = hex.DecodeString(sha256sum)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid hex encoding for expected hash %s: %w", sha256sum, err)
|
||||
}
|
||||
|
||||
@@ -423,7 +423,7 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV
|
||||
Type: notifications.ActionTypeWebhook,
|
||||
Payload: notifications.ActionTypeWebhookPayload{
|
||||
Method: "POST",
|
||||
URL: "core/restart",
|
||||
URL: "core/restart?source=upgrade",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user