mirror of https://github.com/ospab/ostp.git
feat: linux auto-sudo and tauri system tray background mode
This commit is contained in:
parent
0951afa499
commit
c2bc764613
|
|
@ -111,6 +111,41 @@ fn relaunch_as_admin() -> Result<()> {
|
|||
std::process::exit(0);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn is_root() -> bool {
|
||||
unsafe { libc::geteuid() == 0 }
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn relaunch_as_root() -> Result<()> {
|
||||
use std::io::IsTerminal;
|
||||
let exe = std::env::current_exe()?;
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
|
||||
let is_gui = std::env::var("DISPLAY").is_ok() || std::env::var("WAYLAND_DISPLAY").is_ok();
|
||||
let is_term = std::io::stdout().is_terminal();
|
||||
|
||||
let mut cmd = if is_gui && !is_term {
|
||||
let mut c = std::process::Command::new("pkexec");
|
||||
c.arg(exe);
|
||||
c
|
||||
} else {
|
||||
let mut c = std::process::Command::new("sudo");
|
||||
c.arg(exe);
|
||||
c
|
||||
};
|
||||
|
||||
cmd.args(&args);
|
||||
|
||||
let status = cmd.status().map_err(|e| anyhow::anyhow!("Failed to execute privilege escalation command: {}", e))?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(anyhow::anyhow!("Privilege escalation failed or was denied."));
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
pub async fn run_client(config: crate::config::ClientConfig) -> Result<()> {
|
||||
#[cfg(target_os = "windows")]
|
||||
if config.mode == "tun" && !is_admin() {
|
||||
|
|
@ -118,6 +153,12 @@ pub async fn run_client(config: crate::config::ClientConfig) -> Result<()> {
|
|||
relaunch_as_admin()?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
if config.mode == "tun" && !is_root() {
|
||||
println!("[ostp] TUN mode requires root privileges. Requesting sudo/pkexec elevation...");
|
||||
relaunch_as_root()?;
|
||||
}
|
||||
|
||||
let bg = std::env::args().any(|a| a == "--bg");
|
||||
|
||||
if bg {
|
||||
|
|
@ -152,6 +193,11 @@ pub async fn run_client_core(
|
|||
return Err(anyhow::anyhow!("Administrator privileges are required to initialize TUN mode. Please run the application as Administrator."));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
if config.mode == "tun" && !is_root() {
|
||||
return Err(anyhow::anyhow!("Root privileges are required to initialize TUN mode on Linux. Please run with sudo."));
|
||||
}
|
||||
|
||||
log_to_core_file(&format!("[core] Starting run_client_core in mode: {}", config.mode));
|
||||
|
||||
// Resolve the server IP before we override system routing and DNS.
|
||||
|
|
|
|||
|
|
@ -522,6 +522,91 @@ pub fn run() {
|
|||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.manage(state)
|
||||
.setup(|app| {
|
||||
use tauri::menu::{Menu, MenuItem};
|
||||
use tauri::tray::{TrayIconBuilder, TrayIconEvent, MouseButton, MouseButtonState};
|
||||
use tauri::Manager;
|
||||
|
||||
let config_path = get_config_path();
|
||||
let mut masked_ip = String::from("0.0.0.0");
|
||||
if config_path.exists() {
|
||||
if let Ok(content) = std::fs::read_to_string(&config_path) {
|
||||
let mut stripped = json_comments::StripComments::new(content.as_bytes());
|
||||
if let Ok(val) = serde_json::from_reader::<_, serde_json::Value>(&mut stripped) {
|
||||
if let Some(server) = val.get("server").and_then(|s| s.as_str()) {
|
||||
let parts: Vec<&str> = server.split(':').collect();
|
||||
let ip = parts[0];
|
||||
let port = if parts.len() > 1 { parts[1] } else { "" };
|
||||
let octets: Vec<&str> = ip.split('.').collect();
|
||||
if octets.len() == 4 {
|
||||
masked_ip = format!("{}.{}.**.**:{}", octets[0], octets[1], port);
|
||||
} else if octets.len() > 2 {
|
||||
masked_ip = format!("{}...:{}", octets[0], port);
|
||||
} else {
|
||||
masked_ip = server.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let connect_i = MenuItem::with_id(app, "connect", "Подключиться", true, None::<&str>)?;
|
||||
let disconnect_i = MenuItem::with_id(app, "disconnect", "Отключиться", true, None::<&str>)?;
|
||||
let server_i = MenuItem::with_id(app, "server", format!("Сервер: {}", masked_ip), false, None::<&str>)?;
|
||||
let show_i = MenuItem::with_id(app, "show", "Показать окно", true, None::<&str>)?;
|
||||
let exit_i = MenuItem::with_id(app, "exit", "Выход", true, None::<&str>)?;
|
||||
|
||||
let menu = Menu::with_items(app, &[
|
||||
&server_i,
|
||||
&connect_i,
|
||||
&disconnect_i,
|
||||
&show_i,
|
||||
&exit_i,
|
||||
])?;
|
||||
|
||||
let _tray = TrayIconBuilder::new()
|
||||
.icon(app.default_window_icon().unwrap().clone())
|
||||
.menu(&menu)
|
||||
.on_menu_event(|app, event| {
|
||||
match event.id.as_ref() {
|
||||
"connect" => {
|
||||
let _ = app.emit("tray_connect", ());
|
||||
}
|
||||
"disconnect" => {
|
||||
let _ = app.emit("tray_disconnect", ());
|
||||
}
|
||||
"show" => {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
"exit" => {
|
||||
app.exit(0);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
.on_tray_icon_event(|tray, event| {
|
||||
if let TrayIconEvent::Click { button: MouseButton::Left, button_state: MouseButtonState::Up, .. } = event {
|
||||
let app = tray.app_handle();
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
})
|
||||
.build(app)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.on_window_event(|window, event| match event {
|
||||
tauri::WindowEvent::CloseRequested { api, .. } => {
|
||||
let _ = window.hide();
|
||||
api.prevent_close();
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![start_tunnel, stop_tunnel, get_tunnel_status, get_metrics, get_config, save_config])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
|
|
|||
|
|
@ -513,4 +513,14 @@ window.addEventListener('DOMContentLoaded', async () => {
|
|||
const code = await invoke('get_tunnel_status');
|
||||
if (code > 0) startPolling();
|
||||
} catch { /* not in Tauri context */ }
|
||||
|
||||
if (window.__TAURI__?.event) {
|
||||
const { listen } = window.__TAURI__.event;
|
||||
listen('tray_connect', () => {
|
||||
if (appState === 'disconnected') handleToggle();
|
||||
});
|
||||
listen('tray_disconnect', () => {
|
||||
if (appState !== 'disconnected') handleToggle();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue