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);
|
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<()> {
|
pub async fn run_client(config: crate::config::ClientConfig) -> Result<()> {
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
if config.mode == "tun" && !is_admin() {
|
if config.mode == "tun" && !is_admin() {
|
||||||
|
|
@ -118,6 +153,12 @@ pub async fn run_client(config: crate::config::ClientConfig) -> Result<()> {
|
||||||
relaunch_as_admin()?;
|
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");
|
let bg = std::env::args().any(|a| a == "--bg");
|
||||||
|
|
||||||
if 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."));
|
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));
|
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.
|
// Resolve the server IP before we override system routing and DNS.
|
||||||
|
|
|
||||||
|
|
@ -522,6 +522,91 @@ pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.manage(state)
|
.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])
|
.invoke_handler(tauri::generate_handler![start_tunnel, stop_tunnel, get_tunnel_status, get_metrics, get_config, save_config])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|
|
||||||
|
|
@ -513,4 +513,14 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||||
const code = await invoke('get_tunnel_status');
|
const code = await invoke('get_tunnel_status');
|
||||||
if (code > 0) startPolling();
|
if (code > 0) startPolling();
|
||||||
} catch { /* not in Tauri context */ }
|
} 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