Files
clash-verge-rev/src-tauri/src/ipc.rs
T
TungliesandCopilot d09f78f9b1 feat(linux): reinstall an existing Service during package upgrades
The deb and rpm packages never refreshed an installed Service, and the
previous package's prerm/%preun uninstalls it unconditionally. When the
Service unit existed before the upgrade, the new postinst/%post now
reinstalls it with its group and restores disabled, enabled-runtime,
masked-runtime and stopped states. First installs and explicit removals
never add a Service.

rpm runs the new %post before the old %preun. %post records the
transaction process (pid and start time) in /run, and
/usr/bin/clash-verge-service-uninstall becomes a wrapper that skips only
that %preun call and otherwise runs the real uninstaller, now under
/usr/lib/clash-verge-rev. deb has no such ordering: an upgrade from a
released version still loses the Service, and later deb upgrades reinstall
it in place only once the prerm upgrade guard from #8240 ships.

The scripts exit 0, because a failing postinst or %post cannot restore the
Service. On failure they write /run/clash-verge-service-upgrade instead;
package script output never reaches the GUI. At startup the app offers a
reinstall if the Service is not ready and warns if its previous state was
not restored. Both notices replay when a window subscribes, and the marker
is acknowledged only after both are taken. In-app updates stop a
Service-run core and hold the core lifecycle until the package manager
exits, including after the 30-second startup timeout.

Refs #8515

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-11-09 21:28:46 +08:00

236 lines
7.9 KiB
Rust

use crate::cmd;
pub fn builder() -> tauri_specta::Builder<tauri::Wry> {
tauri_specta::Builder::new()
.types(&platform_types())
.commands(tauri_specta::collect_commands![
tauri_plugin_clash_verge_sysinfo::commands::get_system_info,
tauri_plugin_clash_verge_sysinfo::commands::get_app_uptime,
tauri_plugin_clash_verge_sysinfo::commands::export_diagnostic_info::<tauri::Wry>,
cmd::probe_listener,
cmd::save_proxy_ports,
cmd::get_sys_proxy,
cmd::get_auto_proxy,
cmd::get_embedded_server_port,
cmd::open_app_dir,
cmd::open_logs_dir,
cmd::open_core_dir,
cmd::get_network_interfaces,
cmd::get_system_hostname,
cmd::restart_app,
cmd::install_update,
cmd::cancel_update_download,
cmd::restart_core,
cmd::upgrade_clash_core,
cmd::get_runtime_state,
cmd::get_pending_failures,
cmd::get_sidecar_failure,
cmd::entry_lightweight_mode,
cmd::install_service,
cmd::uninstall_service,
cmd::reinstall_service,
cmd::repair_service,
cmd::continue_with_sidecar,
cmd::sync_runtime_providers,
cmd::get_clash_info,
cmd::patch_clash_config,
cmd::patch_clash_mode,
cmd::get_clash_mode,
cmd::change_clash_core,
cmd::get_runtime_config,
cmd::get_proxy_view,
cmd::get_runtime_yaml,
cmd::get_runtime_logs,
cmd::get_runtime_proxy_chain_config,
cmd::update_proxy_chain_config_in_runtime,
cmd::invoke_uwp_tool,
cmd::copy_clash_env,
cmd::sync_tray_proxy_selection,
cmd::record_selected_node,
cmd::forget_selected_node,
cmd::save_dns_config,
cmd::apply_dns_config,
cmd::set_dns_override,
cmd::take_dns_override_notice,
cmd::take_service_fallback_notice,
cmd::get_core_startup_error,
cmd::take_service_repair_notice,
cmd::take_service_owner_notice,
cmd::take_service_upgrade_state_notice,
cmd::take_discarded_keys_notice,
cmd::get_dns_config_content,
cmd::validate_dns_config,
cmd::get_clash_logs,
cmd::get_verge_config,
cmd::patch_verge_config,
cmd::test_delay,
cmd::get_app_dir,
cmd::copy_icon_file,
cmd::download_icon_cache,
cmd::open_devtools,
cmd::exit_app,
cmd::get_network_interfaces_info,
cmd::get_profiles,
cmd::enhance_profiles,
cmd::patch_profiles_config,
cmd::view_profile,
cmd::patch_profile,
cmd::create_profile,
cmd::import_profile,
cmd::reorder_profile,
cmd::update_profile,
cmd::delete_profile,
cmd::read_profile_file,
cmd::save_profile_file,
cmd::get_next_update_time,
cmd::create_local_backup,
cmd::list_local_backup,
cmd::delete_local_backup,
cmd::restore_local_backup,
cmd::import_local_backup,
cmd::export_local_backup,
cmd::create_webdav_backup,
cmd::save_webdav_config,
cmd::list_webdav_backup,
cmd::delete_webdav_backup,
cmd::restore_webdav_backup,
cmd::get_unlock_items,
cmd::check_media_unlock,
cmd::check_media_unlock_item,
])
.error_handling(tauri_specta::ErrorHandlingMode::Throw)
.dangerously_cast_bigints_to_number()
.typ::<crate::core::notify::notification::VergeEventPayloads>()
}
// Tauri transports YAML values as JSON; keep YAML storage out of the IPC type graph.
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
#[serde(transparent)]
pub struct YamlMapping(#[specta(type = std::collections::HashMap<String, JsonValue>)] pub serde_yaml_ng::Mapping);
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
#[serde(transparent)]
pub struct YamlValue(#[specta(type = JsonValue)] pub serde_yaml_ng::Value);
#[derive(serde::Serialize, specta::Type)]
pub struct WebDavFile {
pub href: String,
#[specta(type = String)]
pub last_modified: chrono::DateTime<chrono::Utc>,
}
impl From<reqwest_dav::list_cmd::ListFile> for WebDavFile {
fn from(file: reqwest_dav::list_cmd::ListFile) -> Self {
Self {
href: file.href,
last_modified: file.last_modified,
}
}
}
#[derive(serde::Serialize, specta::Type)]
pub struct NetworkInterfaceView {
pub name: String,
pub addr: Vec<InterfaceAddress>,
pub mac_addr: Option<String>,
}
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
pub enum InterfaceAddress {
V4(IpAddress),
V6(IpAddress),
}
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
pub struct IpAddress {
pub ip: String,
}
impl From<network_interface::NetworkInterface> for NetworkInterfaceView {
fn from(interface: network_interface::NetworkInterface) -> Self {
Self {
name: interface.name,
addr: interface
.addr
.into_iter()
.map(|address| match address {
network_interface::Addr::V4(address) => InterfaceAddress::V4(IpAddress {
ip: address.ip.to_string(),
}),
network_interface::Addr::V6(address) => InterfaceAddress::V6(IpAddress {
ip: address.ip.to_string(),
}),
})
.collect(),
mac_addr: interface.mac_addr,
}
}
}
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
pub struct DelayHistory {
pub time: String,
pub delay: u16,
}
fn platform_types() -> specta::Types {
use specta::datatype::{DataType, Fields};
let mut types = specta::Types::default().register::<crate::config::IVerge>();
<PlatformVergeFields as specta::Type>::definition(&mut types);
let platform = types
.into_unsorted_iter()
.find(|ty| ty.name == "PlatformVergeFields")
.and_then(|ty| ty.ty.clone());
let Some(DataType::Struct(platform)) = platform else {
unreachable!()
};
let Fields::Named(platform) = platform.fields else {
unreachable!()
};
types.iter_mut(|ty| {
if ty.name != "IVerge" {
return;
}
let Some(DataType::Struct(config)) = &mut ty.ty else {
return;
};
let Fields::Named(fields) = &mut config.fields else {
return;
};
fields
.fields
.retain(|(name, _)| !platform.fields.iter().any(|(platform_name, _)| name == platform_name));
fields.fields.extend(platform.fields.iter().cloned());
});
types
}
// These fields exist only on some hosts; IPC generation covers every desktop target.
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
struct PlatformVergeFields {
#[specta(optional)]
tray_icon: Option<String>,
#[specta(optional)]
verge_redir_port: Option<u16>,
#[specta(optional)]
verge_redir_enabled: Option<bool>,
#[specta(optional)]
verge_tproxy_port: Option<u16>,
#[specta(optional)]
verge_tproxy_enabled: Option<bool>,
#[specta(optional)]
enable_tray_speed: Option<bool>,
}
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
#[serde(untagged)]
pub enum JsonValue {
Null(()),
Bool(bool),
Number(f64),
String(String),
// Specta resolves fields in nested functions, where Self is unavailable.
Array(#[specta(type = Vec<JsonValue>)] Vec<Self>),
Object(#[specta(type = std::collections::HashMap<String, JsonValue>)] std::collections::HashMap<String, Self>),
}