mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-07 02:09:06 +00:00
feat(gui): add service and remote mode support (#1578)
This PR fundamentally restructures the EasyTier GUI, introducing support for service mode and remote mode, transforming it from a simple desktop application into a powerful network management terminal. This change allows users to persistently run the EasyTier core as a background service or remotely manage multiple EasyTier instances, greatly improving deployment flexibility and manageability.
This commit is contained in:
@@ -50,7 +50,7 @@ tauri-plugin-clipboard-manager = "2.3.0"
|
||||
tauri-plugin-positioner = { version = "2.3.0", features = ["tray-icon"] }
|
||||
tauri-plugin-vpnservice = { path = "../../tauri-plugin-vpnservice" }
|
||||
tauri-plugin-os = "2.3.0"
|
||||
tauri-plugin-autostart = "2.5.0"
|
||||
|
||||
uuid = "1.17.0"
|
||||
async-trait = "0.1.89"
|
||||
|
||||
|
||||
@@ -45,10 +45,6 @@
|
||||
"os:allow-arch",
|
||||
"os:allow-hostname",
|
||||
"os:allow-platform",
|
||||
"os:allow-locale",
|
||||
"autostart:default",
|
||||
"autostart:allow-disable",
|
||||
"autostart:allow-enable",
|
||||
"autostart:allow-is-enabled"
|
||||
"os:allow-locale"
|
||||
]
|
||||
}
|
||||
+388
-158
@@ -3,6 +3,7 @@
|
||||
|
||||
mod elevate;
|
||||
|
||||
use anyhow::Context;
|
||||
use easytier::proto::api::manage::{
|
||||
CollectNetworkInfoResponse, ValidateConfigResponse, WebClientService,
|
||||
WebClientServiceClientFactory,
|
||||
@@ -17,10 +18,11 @@ use easytier::{
|
||||
launcher::NetworkConfig,
|
||||
rpc_service::ApiRpcServer,
|
||||
tunnel::ring::RingTunnelListener,
|
||||
utils::{self, NewFilterSender},
|
||||
utils::{self},
|
||||
};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
use uuid::Uuid;
|
||||
|
||||
use tauri::{AppHandle, Emitter, Manager as _};
|
||||
@@ -28,19 +30,27 @@ use tauri::{AppHandle, Emitter, Manager as _};
|
||||
#[cfg(not(target_os = "android"))]
|
||||
use tauri::tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent};
|
||||
|
||||
pub const AUTOSTART_ARG: &str = "--autostart";
|
||||
|
||||
static INSTANCE_MANAGER: once_cell::sync::Lazy<Arc<NetworkInstanceManager>> =
|
||||
once_cell::sync::Lazy::new(|| Arc::new(NetworkInstanceManager::new()));
|
||||
|
||||
static mut LOGGER_LEVEL_SENDER: once_cell::sync::Lazy<Option<NewFilterSender>> =
|
||||
once_cell::sync::Lazy::new(Default::default);
|
||||
static INSTANCE_MANAGER: once_cell::sync::Lazy<RwLock<Option<Arc<NetworkInstanceManager>>>> =
|
||||
once_cell::sync::Lazy::new(|| RwLock::new(None));
|
||||
|
||||
static RPC_RING_UUID: once_cell::sync::Lazy<uuid::Uuid> =
|
||||
once_cell::sync::Lazy::new(uuid::Uuid::new_v4);
|
||||
|
||||
static CLIENT_MANAGER: once_cell::sync::OnceCell<manager::GUIClientManager> =
|
||||
once_cell::sync::OnceCell::new();
|
||||
static CLIENT_MANAGER: once_cell::sync::Lazy<RwLock<Option<manager::GUIClientManager>>> =
|
||||
once_cell::sync::Lazy::new(|| RwLock::new(None));
|
||||
|
||||
static RING_RPC_SERVER: once_cell::sync::Lazy<RwLock<Option<ApiRpcServer<RingTunnelListener>>>> =
|
||||
once_cell::sync::Lazy::new(|| RwLock::new(None));
|
||||
|
||||
macro_rules! get_client_manager {
|
||||
() => {{
|
||||
let guard = CLIENT_MANAGER
|
||||
.try_read()
|
||||
.map_err(|_| "Failed to acquire read lock for client manager")?;
|
||||
RwLockReadGuard::try_map(guard, |cm| cm.as_ref())
|
||||
.map_err(|_| "RPC connection not initialized".to_string())
|
||||
}};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn easytier_version() -> Result<String, String> {
|
||||
@@ -88,19 +98,17 @@ async fn run_network_instance(
|
||||
app.emit("pre_run_network_instance", cfg.instance_id())
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let client_manager = get_client_manager!()?;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
if cfg.no_tun() == false {
|
||||
CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
client_manager
|
||||
.disable_instances_with_tun(&app)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
|
||||
CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
client_manager
|
||||
.handle_run_network_instance(app.clone(), cfg, save)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
@@ -118,31 +126,32 @@ async fn collect_network_info(
|
||||
let instance_id = instance_id
|
||||
.parse()
|
||||
.map_err(|e: uuid::Error| e.to_string())?;
|
||||
CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
get_client_manager!()?
|
||||
.handle_collect_network_info(app, Some(vec![instance_id]))
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn set_logging_level(level: String) -> Result<(), String> {
|
||||
#[allow(static_mut_refs)]
|
||||
let sender = unsafe { LOGGER_LEVEL_SENDER.as_ref().unwrap() };
|
||||
sender.send(level).map_err(|e| e.to_string())?;
|
||||
async fn set_logging_level(level: String) -> Result<(), String> {
|
||||
println!("Setting logging level to: {}", level);
|
||||
get_client_manager!()?
|
||||
.set_logging_level(level.clone())
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn set_tun_fd(fd: i32) -> Result<(), String> {
|
||||
if let Some(uuid) = CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
async fn set_tun_fd(fd: i32) -> Result<(), String> {
|
||||
let Some(instance_manager) = INSTANCE_MANAGER.read().await.clone() else {
|
||||
return Err("set_tun_fd is not supported in remote mode".to_string());
|
||||
};
|
||||
if let Some(uuid) = get_client_manager!()?
|
||||
.get_enabled_instances_with_tun_ids()
|
||||
.next()
|
||||
{
|
||||
INSTANCE_MANAGER
|
||||
instance_manager
|
||||
.set_tun_fd(&uuid, fd)
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
@@ -153,9 +162,7 @@ fn set_tun_fd(fd: i32) -> Result<(), String> {
|
||||
async fn list_network_instance_ids(
|
||||
app: AppHandle,
|
||||
) -> Result<ListNetworkInstanceIdsJsonResp, String> {
|
||||
CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
get_client_manager!()?
|
||||
.handle_list_network_instance_ids(app)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
@@ -166,16 +173,12 @@ async fn remove_network_instance(app: AppHandle, instance_id: String) -> Result<
|
||||
let instance_id = instance_id
|
||||
.parse()
|
||||
.map_err(|e: uuid::Error| e.to_string())?;
|
||||
CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
let client_manager = get_client_manager!()?;
|
||||
client_manager
|
||||
.handle_remove_network_instances(app.clone(), vec![instance_id])
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
.notify_vpn_stop_if_no_tun(&app)?;
|
||||
client_manager.notify_vpn_stop_if_no_tun(&app)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -188,17 +191,13 @@ async fn update_network_config_state(
|
||||
let instance_id = instance_id
|
||||
.parse()
|
||||
.map_err(|e: uuid::Error| e.to_string())?;
|
||||
CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
let client_manager = get_client_manager!()?;
|
||||
client_manager
|
||||
.handle_update_network_state(app.clone(), instance_id, disabled)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
if disabled {
|
||||
CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
.notify_vpn_stop_if_no_tun(&app)?;
|
||||
client_manager.notify_vpn_stop_if_no_tun(&app)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -209,9 +208,7 @@ async fn save_network_config(app: AppHandle, cfg: NetworkConfig) -> Result<(), S
|
||||
.instance_id()
|
||||
.parse()
|
||||
.map_err(|e: uuid::Error| e.to_string())?;
|
||||
CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
get_client_manager!()?
|
||||
.handle_save_network_config(app, instance_id, cfg)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
@@ -222,9 +219,7 @@ async fn validate_config(
|
||||
app: AppHandle,
|
||||
config: NetworkConfig,
|
||||
) -> Result<ValidateConfigResponse, String> {
|
||||
CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
get_client_manager!()?
|
||||
.handle_validate_config(app, config)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
@@ -232,9 +227,7 @@ async fn validate_config(
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_config(app: AppHandle, instance_id: String) -> Result<NetworkConfig, String> {
|
||||
let cfg = CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
let cfg = get_client_manager!()?
|
||||
.storage
|
||||
.get_network_config(app, &instance_id)
|
||||
.await
|
||||
@@ -249,10 +242,7 @@ async fn load_configs(
|
||||
configs: Vec<NetworkConfig>,
|
||||
enabled_networks: Vec<String>,
|
||||
) -> Result<(), String> {
|
||||
CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
.storage
|
||||
get_client_manager!()?
|
||||
.load_configs(app, configs, enabled_networks)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
@@ -264,14 +254,143 @@ async fn get_network_metas(
|
||||
app: AppHandle,
|
||||
instance_ids: Vec<uuid::Uuid>,
|
||||
) -> Result<GetNetworkMetasResponse, String> {
|
||||
CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
get_client_manager!()?
|
||||
.handle_get_network_metas(app, instance_ids)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[tauri::command]
|
||||
fn init_service() -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
#[tauri::command]
|
||||
fn init_service(opts: Option<service::ServiceOptions>) -> Result<(), String> {
|
||||
match opts {
|
||||
Some(args) => {
|
||||
let path = std::path::Path::new(&args.config_dir);
|
||||
if !path.exists() {
|
||||
std::fs::create_dir_all(&args.config_dir).map_err(|e| e.to_string())?;
|
||||
} else if !path.is_dir() {
|
||||
return Err("config_dir exists but is not a directory".to_string());
|
||||
}
|
||||
let path = std::path::Path::new(&args.file_log_dir);
|
||||
if !path.exists() {
|
||||
std::fs::create_dir_all(&args.file_log_dir).map_err(|e| e.to_string())?;
|
||||
} else if !path.is_dir() {
|
||||
return Err("file_log_dir exists but is not a directory".to_string());
|
||||
}
|
||||
|
||||
service::install(args).map_err(|e| format!("{:#}", e))?;
|
||||
}
|
||||
None => {
|
||||
service::uninstall().map_err(|e| format!("{:#}", e))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn set_service_status(_enable: bool) -> Result<(), String> {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
service::set_status(_enable).map_err(|e| format!("{:#}", e))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_service_status() -> Result<&'static str, String> {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
use easytier::service_manager::ServiceStatus;
|
||||
let status = service::status().map_err(|e| format!("{:#}", e))?;
|
||||
match status {
|
||||
ServiceStatus::NotInstalled => Ok("NotInstalled"),
|
||||
ServiceStatus::Stopped(_) => Ok("Stopped"),
|
||||
ServiceStatus::Running => Ok("Running"),
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
Ok("NotInstalled")
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn init_rpc_connection(_app: AppHandle, url: Option<String>) -> Result<(), String> {
|
||||
let mut client_manager_guard =
|
||||
tokio::time::timeout(std::time::Duration::from_secs(5), CLIENT_MANAGER.write())
|
||||
.await
|
||||
.map_err(|_| "Failed to acquire write lock for client manager")?;
|
||||
let mut instance_manager_guard = INSTANCE_MANAGER
|
||||
.try_write()
|
||||
.map_err(|_| "Failed to acquire write lock for instance manager")?;
|
||||
let mut ring_rpc_server_guard = RING_RPC_SERVER
|
||||
.try_write()
|
||||
.map_err(|_| "Failed to acquire write lock for ring rpc server")?;
|
||||
|
||||
let normal_mode = url.is_none();
|
||||
if normal_mode {
|
||||
let instance_manager = if let Some(im) = instance_manager_guard.take() {
|
||||
im
|
||||
} else {
|
||||
Arc::new(NetworkInstanceManager::new())
|
||||
};
|
||||
let rpc_server = if let Some(rpc_server) = ring_rpc_server_guard.take() {
|
||||
rpc_server
|
||||
} else {
|
||||
ApiRpcServer::from_tunnel(
|
||||
RingTunnelListener::new(
|
||||
format!("ring://{}", RPC_RING_UUID.deref()).parse().unwrap(),
|
||||
),
|
||||
instance_manager.clone(),
|
||||
)
|
||||
.with_rx_timeout(None)
|
||||
.serve()
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
};
|
||||
|
||||
*instance_manager_guard = Some(instance_manager);
|
||||
*ring_rpc_server_guard = Some(rpc_server);
|
||||
} else {
|
||||
*ring_rpc_server_guard = None;
|
||||
}
|
||||
|
||||
let mut client_manager = tokio::time::timeout(
|
||||
std::time::Duration::from_millis(1000),
|
||||
manager::GUIClientManager::new(url),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| "connect remote rpc timed out".to_string())?
|
||||
.with_context(|| "Failed to connect remote rpc")
|
||||
.map_err(|e| format!("{:#}", e))?;
|
||||
if let Some(old_manager) = client_manager_guard.take() {
|
||||
client_manager.storage = old_manager.storage;
|
||||
}
|
||||
*client_manager_guard = Some(client_manager);
|
||||
|
||||
if !normal_mode {
|
||||
if let Some(instance_manager) = instance_manager_guard.take() {
|
||||
instance_manager
|
||||
.retain_network_instance(vec![])
|
||||
.map_err(|e| e.to_string())?;
|
||||
drop(instance_manager);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn is_client_running() -> Result<bool, String> {
|
||||
Ok(get_client_manager!()?.rpc_manager.is_running())
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn toggle_window_visibility<R: tauri::Runtime>(app: &tauri::AppHandle<R>) {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
@@ -289,19 +408,23 @@ fn toggle_window_visibility<R: tauri::Runtime>(app: &tauri::AppHandle<R>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_exe_path() -> String {
|
||||
if let Ok(appimage_path) = std::env::var("APPIMAGE") {
|
||||
if !appimage_path.is_empty() {
|
||||
return appimage_path;
|
||||
}
|
||||
}
|
||||
std::env::current_exe()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
fn check_sudo() -> bool {
|
||||
let is_elevated = elevate::Command::is_elevated();
|
||||
if !is_elevated {
|
||||
let exe_path = std::env::var("APPIMAGE")
|
||||
.ok()
|
||||
.or_else(|| std::env::args().next())
|
||||
.unwrap_or_default();
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let mut stdcmd = std::process::Command::new(&exe_path);
|
||||
if args.contains(&AUTOSTART_ARG.to_owned()) {
|
||||
stdcmd.arg(AUTOSTART_ARG);
|
||||
}
|
||||
let exe_path = get_exe_path();
|
||||
let stdcmd = std::process::Command::new(&exe_path);
|
||||
elevate::Command::new(stdcmd)
|
||||
.output()
|
||||
.expect("Failed to run elevated command");
|
||||
@@ -313,10 +436,15 @@ mod manager {
|
||||
use super::*;
|
||||
use async_trait::async_trait;
|
||||
use dashmap::{DashMap, DashSet};
|
||||
use easytier::common::global_ctx::GlobalCtx;
|
||||
use easytier::common::stun::MockStunInfoCollector;
|
||||
use easytier::launcher::NetworkConfig;
|
||||
use easytier::proto::api::manage::RunNetworkInstanceRequest;
|
||||
use easytier::proto::api::logger::{LoggerRpc, LoggerRpcClientFactory, SetLoggerConfigRequest};
|
||||
use easytier::proto::api::manage::{ListNetworkInstanceRequest, RunNetworkInstanceRequest};
|
||||
use easytier::proto::common::NatType;
|
||||
use easytier::proto::rpc_impl::bidirect::BidirectRpcManager;
|
||||
use easytier::proto::rpc_types::controller::BaseController;
|
||||
use easytier::rpc_service::logger::LoggerRpcService;
|
||||
use easytier::rpc_service::remote_client::PersistentConfig;
|
||||
use easytier::tunnel::ring::RingTunnelConnector;
|
||||
use easytier::tunnel::TunnelConnector;
|
||||
@@ -344,54 +472,6 @@ mod manager {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn load_configs(
|
||||
&self,
|
||||
app: AppHandle,
|
||||
configs: Vec<NetworkConfig>,
|
||||
enabled_networks: Vec<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.network_configs.clear();
|
||||
for cfg in configs {
|
||||
let instance_id = cfg.instance_id();
|
||||
self.network_configs.insert(
|
||||
instance_id.parse()?,
|
||||
GUIConfig(instance_id.to_string(), cfg),
|
||||
);
|
||||
}
|
||||
|
||||
self.enabled_networks.clear();
|
||||
INSTANCE_MANAGER.iter().for_each(|v| {
|
||||
self.enabled_networks.insert(*v.key());
|
||||
});
|
||||
for id in enabled_networks {
|
||||
if let Ok(uuid) = id.parse() {
|
||||
if !self.enabled_networks.contains(&uuid) {
|
||||
let config = self
|
||||
.network_configs
|
||||
.get(&uuid)
|
||||
.map(|i| i.value().1.clone())
|
||||
.ok_or_else(|| anyhow::anyhow!("Config not found"))?;
|
||||
CLIENT_MANAGER
|
||||
.get()
|
||||
.unwrap()
|
||||
.get_rpc_client(app.clone())
|
||||
.ok_or_else(|| anyhow::anyhow!("RPC client not found"))?
|
||||
.run_network_instance(
|
||||
BaseController::default(),
|
||||
RunNetworkInstanceRequest {
|
||||
inst_id: None,
|
||||
config: Some(config),
|
||||
overwrite: false,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
self.enabled_networks.insert(uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn save_configs(&self, app: &AppHandle) -> anyhow::Result<()> {
|
||||
let configs: Result<Vec<String>, _> = self
|
||||
.network_configs
|
||||
@@ -507,14 +587,32 @@ mod manager {
|
||||
|
||||
pub(super) struct GUIClientManager {
|
||||
pub(super) storage: GUIStorage,
|
||||
rpc_manager: BidirectRpcManager,
|
||||
pub(super) rpc_manager: BidirectRpcManager,
|
||||
}
|
||||
impl GUIClientManager {
|
||||
pub async fn new() -> Result<Self, anyhow::Error> {
|
||||
let mut connector = RingTunnelConnector::new(
|
||||
format!("ring://{}", RPC_RING_UUID.deref()).parse().unwrap(),
|
||||
);
|
||||
let tunnel = connector.connect().await?;
|
||||
pub async fn new(rpc_url: Option<String>) -> Result<Self, anyhow::Error> {
|
||||
let global_ctx = Arc::new(GlobalCtx::new(TomlConfigLoader::default()));
|
||||
global_ctx.replace_stun_info_collector(Box::new(MockStunInfoCollector {
|
||||
udp_nat_type: NatType::Unknown,
|
||||
}));
|
||||
let mut flags = global_ctx.get_flags();
|
||||
flags.bind_device = false;
|
||||
global_ctx.set_flags(flags);
|
||||
let tunnel = if let Some(url) = rpc_url {
|
||||
let mut connector = easytier::connector::create_connector_by_url(
|
||||
&url,
|
||||
&global_ctx,
|
||||
easytier::tunnel::IpVersion::Both,
|
||||
)
|
||||
.await?;
|
||||
connector.connect().await?
|
||||
} else {
|
||||
let mut connector = RingTunnelConnector::new(
|
||||
format!("ring://{}", RPC_RING_UUID.deref()).parse().unwrap(),
|
||||
);
|
||||
connector.connect().await?
|
||||
};
|
||||
|
||||
let rpc_manager = BidirectRpcManager::new();
|
||||
rpc_manager.run_with_tunnel(tunnel);
|
||||
|
||||
@@ -554,6 +652,84 @@ mod manager {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_logger_rpc_client(
|
||||
&self,
|
||||
) -> Option<Box<dyn LoggerRpc<Controller = BaseController> + Send>> {
|
||||
Some(
|
||||
self.rpc_manager
|
||||
.rpc_client()
|
||||
.scoped_client::<LoggerRpcClientFactory<BaseController>>(1, 1, "".to_string()),
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) async fn set_logging_level(&self, level: String) -> Result<(), anyhow::Error> {
|
||||
let logger_rpc = self
|
||||
.get_logger_rpc_client()
|
||||
.ok_or_else(|| anyhow::anyhow!("Logger RPC client not available"))?;
|
||||
logger_rpc
|
||||
.set_logger_config(
|
||||
BaseController::default(),
|
||||
SetLoggerConfigRequest {
|
||||
level: LoggerRpcService::string_to_log_level(&level).into(),
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) async fn load_configs(
|
||||
&self,
|
||||
app: AppHandle,
|
||||
configs: Vec<NetworkConfig>,
|
||||
enabled_networks: Vec<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.storage.network_configs.clear();
|
||||
for cfg in configs {
|
||||
let instance_id = cfg.instance_id();
|
||||
self.storage.network_configs.insert(
|
||||
instance_id.parse()?,
|
||||
GUIConfig(instance_id.to_string(), cfg),
|
||||
);
|
||||
}
|
||||
|
||||
self.storage.enabled_networks.clear();
|
||||
let client = self
|
||||
.get_rpc_client(app.clone())
|
||||
.ok_or_else(|| anyhow::anyhow!("RPC client not found"))?;
|
||||
let running_instances = client
|
||||
.list_network_instance(BaseController::default(), ListNetworkInstanceRequest {})
|
||||
.await?;
|
||||
for id in running_instances.inst_ids {
|
||||
self.storage.enabled_networks.insert(id.into());
|
||||
}
|
||||
for id in enabled_networks {
|
||||
if let Ok(uuid) = id.parse() {
|
||||
if !self.storage.enabled_networks.contains(&uuid) {
|
||||
let config = self
|
||||
.storage
|
||||
.network_configs
|
||||
.get(&uuid)
|
||||
.map(|i| i.value().1.clone());
|
||||
if config.is_none() {
|
||||
continue;
|
||||
}
|
||||
client
|
||||
.run_network_instance(
|
||||
BaseController::default(),
|
||||
RunNetworkInstanceRequest {
|
||||
inst_id: None,
|
||||
config,
|
||||
overwrite: false,
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
self.storage.enabled_networks.insert(uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
impl RemoteClientManager<AppHandle, GUIConfig, anyhow::Error> for GUIClientManager {
|
||||
fn get_rpc_client(
|
||||
@@ -577,8 +753,78 @@ mod manager {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
mod service {
|
||||
use anyhow::Context;
|
||||
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ServiceOptions {
|
||||
pub(super) config_dir: String,
|
||||
pub(super) rpc_portal: String,
|
||||
pub(super) file_log_level: String,
|
||||
pub(super) file_log_dir: String,
|
||||
}
|
||||
impl ServiceOptions {
|
||||
fn to_args_vec(&self) -> Vec<std::ffi::OsString> {
|
||||
vec![
|
||||
"--config-dir".into(),
|
||||
self.config_dir.clone().into(),
|
||||
"--rpc-portal".into(),
|
||||
self.rpc_portal.clone().into(),
|
||||
"--file-log-level".into(),
|
||||
self.file_log_level.clone().into(),
|
||||
"--file-log-dir".into(),
|
||||
self.file_log_dir.clone().into(),
|
||||
"--daemon".into(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn install(opts: ServiceOptions) -> anyhow::Result<()> {
|
||||
let service = easytier::service_manager::Service::new(env!("CARGO_PKG_NAME").to_string())?;
|
||||
let options = easytier::service_manager::ServiceInstallOptions {
|
||||
program: super::get_exe_path().into(),
|
||||
args: opts.to_args_vec(),
|
||||
work_directory: std::env::current_dir()?,
|
||||
disable_autostart: false,
|
||||
description: Some("EasyTier Gui Service".to_string()),
|
||||
display_name: Some("EasyTier Gui Service".to_string()),
|
||||
disable_restart_on_failure: false,
|
||||
};
|
||||
service
|
||||
.install(&options)
|
||||
.with_context(|| "Failed to install service")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn uninstall() -> anyhow::Result<()> {
|
||||
let service = easytier::service_manager::Service::new(env!("CARGO_PKG_NAME").to_string())?;
|
||||
service.uninstall()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_status(enable: bool) -> anyhow::Result<()> {
|
||||
use easytier::service_manager::*;
|
||||
let service = Service::new(env!("CARGO_PKG_NAME").to_string())?;
|
||||
let status = service.status()?;
|
||||
if enable && status != ServiceStatus::Running {
|
||||
service.start().with_context(|| "Failed to start service")?;
|
||||
} else if !enable && status == ServiceStatus::Running {
|
||||
service.stop().with_context(|| "Failed to stop service")?;
|
||||
} else if status == ServiceStatus::NotInstalled {
|
||||
return Err(anyhow::anyhow!("Service not installed"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn status() -> anyhow::Result<easytier::service_manager::ServiceStatus> {
|
||||
let service = easytier::service_manager::Service::new(env!("CARGO_PKG_NAME").to_string())?;
|
||||
service.status()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
pub fn run_gui() -> std::process::ExitCode {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
if !check_sudo() {
|
||||
use std::process;
|
||||
@@ -587,35 +833,8 @@ pub fn run() {
|
||||
|
||||
utils::setup_panic_handler();
|
||||
|
||||
let _rpc_server_handle = tauri::async_runtime::spawn(async move {
|
||||
let rpc_server = ApiRpcServer::from_tunnel(
|
||||
RingTunnelListener::new(format!("ring://{}", RPC_RING_UUID.deref()).parse().unwrap()),
|
||||
INSTANCE_MANAGER.clone(),
|
||||
)
|
||||
.serve()
|
||||
.await
|
||||
.expect("Failed to start RPC server");
|
||||
|
||||
let _ = CLIENT_MANAGER.set(
|
||||
manager::GUIClientManager::new()
|
||||
.await
|
||||
.expect("Failed to create GUI client manager"),
|
||||
);
|
||||
|
||||
rpc_server
|
||||
});
|
||||
|
||||
let mut builder = tauri::Builder::default();
|
||||
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
use tauri_plugin_autostart::MacosLauncher;
|
||||
builder = builder.plugin(tauri_plugin_autostart::init(
|
||||
MacosLauncher::LaunchAgent,
|
||||
Some(vec![AUTOSTART_ARG]),
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||
{
|
||||
builder = builder.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
|
||||
@@ -651,13 +870,9 @@ pub fn run() {
|
||||
})
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
let Ok(Some(logger_reinit)) = utils::init_logger(&config, true) else {
|
||||
let Ok(_) = utils::init_logger(&config, true) else {
|
||||
return Ok(());
|
||||
};
|
||||
#[allow(static_mut_refs)]
|
||||
unsafe {
|
||||
LOGGER_LEVEL_SENDER.replace(logger_reinit)
|
||||
};
|
||||
|
||||
// for tray icon, menu need to be built in js
|
||||
#[cfg(not(target_os = "android"))]
|
||||
@@ -699,6 +914,11 @@ pub fn run() {
|
||||
get_config,
|
||||
load_configs,
|
||||
get_network_metas,
|
||||
init_service,
|
||||
set_service_status,
|
||||
get_service_status,
|
||||
init_rpc_connection,
|
||||
is_client_running,
|
||||
])
|
||||
.on_window_event(|_win, event| match event {
|
||||
#[cfg(not(target_os = "android"))]
|
||||
@@ -724,4 +944,14 @@ pub fn run() {
|
||||
_ => {}
|
||||
});
|
||||
}
|
||||
|
||||
std::process::ExitCode::SUCCESS
|
||||
}
|
||||
|
||||
pub fn run_cli() -> std::process::ExitCode {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async { easytier::core::main().await })
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
app_lib::run();
|
||||
fn main() -> std::process::ExitCode {
|
||||
if std::env::args().count() > 1 {
|
||||
app_lib::run_cli()
|
||||
} else {
|
||||
app_lib::run_gui()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user