chore: add ohrs as workspace member; clean Cargo.toml (#2601)

This commit is contained in:
Luna Yao authored and GitHub committed 2026-09-24 12:03:13 +08:00
1 parent 0a783c8e04
commit eafd46b91b
20 files changed
+484 -6616

No files matched your search

Generated
+381 -108
View File
File diff suppressed because it is too large. Load diff
+10 -5
View File
@@ -4,18 +4,18 @@ members = [
"easytier-core", "easytier-core",
"easytier-proto", "easytier-proto",
"easytier", "easytier",
"easytier-gui/src-tauri",
"easytier-web", "easytier-web",
"easytier-gui/src-tauri",
"easytier-contrib/easytier-mini", "easytier-contrib/easytier-mini",
"easytier-contrib/easytier-ffi", "easytier-contrib/easytier-ffi",
"easytier-contrib/easytier-uptime", "easytier-contrib/easytier-uptime",
"easytier-contrib/easytier-android-jni", "easytier-contrib/easytier-android-jni",
"easytier-contrib/easytier-ios", "easytier-contrib/easytier-ios",
"easytier-contrib/easytier-ohrs", # it needs ohrs sdk
"easytier-contrib/easytier-ohrs/crates/*",
] ]
default-members = ["easytier", "easytier-web"] default-members = ["easytier", "easytier-web"]
exclude = [ exclude = []
"easytier-contrib/easytier-ohrs", # it needs ohrs sdk
]
[workspace.package] [workspace.package]
edition = "2024" edition = "2024"
@@ -28,6 +28,7 @@ arc-swap = "1.9"
async-trait = "0.1.92" async-trait = "0.1.92"
atomic-shim = "0.2.0" atomic-shim = "0.2.0"
auto_impl = "1.3.0" auto_impl = "1.3.0"
axum = "0.8"
base64 = "0.23" base64 = "0.23"
bon = "3.10.1" bon = "3.10.1"
bytecodec = "0.5.0" bytecodec = "0.5.0"
@@ -39,9 +40,12 @@ crossbeam = "0.8.5"
dashmap = "6.2.1" dashmap = "6.2.1"
easytier = { version = "2.7.0", path = "easytier", default-features = false } easytier = { version = "2.7.0", path = "easytier", default-features = false }
easytier-core = { version = "2.7.0", path = "easytier-core", default-features = false } easytier-core = { version = "2.7.0", path = "easytier-core", default-features = false }
easytier-ffi = { path = "easytier-contrib/easytier-ffi", default-features = false } easytier-ffi = { version = "0.1.0", path = "easytier-contrib/easytier-ffi", default-features = false }
easytier-ohos-core = { version = "0.1.0", path = "easytier-contrib/easytier-ohrs/crates/easytier-ohos-core", default-features = false }
easytier-ohos-features = { version = "0.1.0", path = "easytier-contrib/easytier-ohrs/crates/easytier-ohos-features", default-features = false }
easytier-proto = { version = "2.7.0", path = "easytier-proto", default-features = false } easytier-proto = { version = "2.7.0", path = "easytier-proto", default-features = false }
futures = "0.3" futures = "0.3"
gethostname = "1.1"
guarden = "0.3" guarden = "0.3"
hmac = "0.13.0" hmac = "0.13.0"
http-body-util = "0.1" http-body-util = "0.1"
@@ -87,6 +91,7 @@ tracing-subscriber = { version = "0.3", default-features = false }
url = "2.5" url = "2.5"
uuid = "1.26.1" uuid = "1.26.1"
winapi = "0.3.9" winapi = "0.3.9"
windows = "0.62"
x25519-dalek = "2.0.1" x25519-dalek = "2.0.1"
# zerocopy 0.8 requires migrating the packet layout and casting APIs. # zerocopy 0.8 requires migrating the packet layout and casting APIs.
zerocopy = "0.7.35" zerocopy = "0.7.35"
@@ -2,6 +2,7 @@
name = "easytier-android-jni" name = "easytier-android-jni"
version = "0.1.0" version = "0.1.0"
edition.workspace = true edition.workspace = true
rust-version.workspace = true
[lib] [lib]
crate-type = ["cdylib"] crate-type = ["cdylib"]
+1
View File
@@ -2,6 +2,7 @@
name = "easytier-ffi" name = "easytier-ffi"
version = "0.1.0" version = "0.1.0"
edition.workspace = true edition.workspace = true
rust-version.workspace = true
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
+1
View File
@@ -2,6 +2,7 @@
name = "easytier-ios" name = "easytier-ios"
version = "0.1.0" version = "0.1.0"
edition.workspace = true edition.workspace = true
rust-version.workspace = true
[lib] [lib]
crate-type = ["staticlib", "rlib"] crate-type = ["staticlib", "rlib"]
File diff suppressed because it is too large. Load diff
+20 -37
View File
@@ -1,32 +1,26 @@
[workspace]
members = [
"crates/easytier-ohos-features",
"crates/easytier-ohos-core",
]
resolver = "2"
[package] [package]
name = "easytier-ohrs" name = "easytier-ohrs"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition.workspace = true
rust-version.workspace = true
[lib] [lib]
crate-type=["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
anyhow = "1.0" anyhow.workspace = true
async-trait = "0.1" async-trait.workspace = true
bytes = "1.5" bytes.workspace = true
easytier-core = { path = "../../easytier-core", default-features = false } easytier = { workspace = true, default-features = true }
easytier-ohos-features = { path = "crates/easytier-ohos-features" } easytier-core.workspace = true
easytier-ohos-core = { path = "crates/easytier-ohos-core" } easytier-ohos-core.workspace = true
easytier-proto = { path = "../../easytier-proto", default-features = false, features = [ easytier-ohos-features.workspace = true
easytier-proto = { workspace = true, features = [
"api", "api",
"core", "core",
"json-rpc", "json-rpc",
] } ] }
futures = "0.3" futures.workspace = true
easytier = { path = "../../easytier" }
napi-derive-ohos = "1.1" napi-derive-ohos = "1.1"
napi-ohos = { version = "1.1", default-features = false, features = [ napi-ohos = { version = "1.1", default-features = false, features = [
"serde-json", "serde-json",
@@ -42,15 +36,14 @@ napi-ohos = { version = "1.1", default-features = false, features = [
"napi8", "napi8",
"web_stream", "web_stream",
] } ] }
once_cell = "1.21.3" once_cell.workspace = true
serde = { version = "1.0", features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = "1.0.125" serde_json.workspace = true
tracing-subscriber = "0.3.19" tokio = { workspace = true, features = ["rt-multi-thread", "sync", "time"] }
tracing-core = "0.1.33" tracing.workspace = true
tracing = "0.1.41" tracing-subscriber = { workspace = true, default-features = true }
tokio = { version = "1", features = ["rt-multi-thread", "sync", "time"] } url.workspace = true
url = "2.5" uuid = { workspace = true, features = [
uuid = { version = "1.5.0", features = [
"v4", "v4",
"fast-rng", "fast-rng",
"macro-diagnostics", "macro-diagnostics",
@@ -59,13 +52,3 @@ uuid = { version = "1.5.0", features = [
[build-dependencies] [build-dependencies]
napi-build-ohos = "1.1" napi-build-ohos = "1.1"
[profile.dev]
panic = "unwind"
debug = true
[profile.release]
panic = "abort"
lto = true
codegen-units = 1
opt-level = 3
strip = true
@@ -1,17 +1,18 @@
[package] [package]
name = "easytier-ohos-core" name = "easytier-ohos-core"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition.workspace = true
rust-version.workspace = true
description = "HarmonyOS-side EasyTier runtime, instance lifecycle and kernel interaction state" description = "HarmonyOS-side EasyTier runtime, instance lifecycle and kernel interaction state"
publish = false publish = false
[dependencies] [dependencies]
async-trait = "0.1" async-trait.workspace = true
easytier = { path = "../../../../easytier" } easytier = { workspace = true, default-features = true }
easytier-core = { path = "../../../../easytier-core", default-features = false }
ipnet = "2.10" ipnet = "2.10"
once_cell = "1.21.3" once_cell.workspace = true
serde = { version = "1.0", features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = "1.0.125" serde_json.workspace = true
tokio = { version = "1", features = ["rt", "rt-multi-thread", "sync", "time"] } tokio = { workspace = true, features = ["rt", "rt-multi-thread", "sync", "time"] }
url = "2.5" url.workspace = true
@@ -305,10 +305,10 @@ fn route_to_view(route: api::instance::Route) -> RouteView {
ipv6_cidr: stringify_ipv6_inet(route.ipv6_addr), ipv6_cidr: stringify_ipv6_inet(route.ipv6_addr),
proxy_cidrs: route.proxy_cidrs, proxy_cidrs: route.proxy_cidrs,
next_hop_peer_id: optional_u32_to_i64(route.next_hop_peer_id_latency_first) next_hop_peer_id: optional_u32_to_i64(route.next_hop_peer_id_latency_first)
.or_else(|| Some(route.next_hop_peer_id as i64)), .or(Some(route.next_hop_peer_id as i64)),
cost: Some(route.cost), cost: Some(route.cost),
path_latency: optional_i32_to_i64(route.path_latency_latency_first) path_latency: optional_i32_to_i64(route.path_latency_latency_first)
.or_else(|| Some(route.path_latency as i64)), .or(Some(route.path_latency as i64)),
udp_nat_type: stun.as_ref().map(|info| info.udp_nat_type), udp_nat_type: stun.as_ref().map(|info| info.udp_nat_type),
tcp_nat_type: stun.as_ref().map(|info| info.tcp_nat_type), tcp_nat_type: stun.as_ref().map(|info| info.tcp_nat_type),
inst_id: (!route.inst_id.is_empty()).then_some(route.inst_id), inst_id: (!route.inst_id.is_empty()).then_some(route.inst_id),
@@ -368,6 +368,7 @@ fn peer_to_view(peer: api::instance::PeerInfo) -> PeerInfo {
} }
fn my_node_info_to_view(info: api::manage::MyNodeInfo) -> MyNodeInfo { fn my_node_info_to_view(info: api::manage::MyNodeInfo) -> MyNodeInfo {
#[allow(deprecated)]
MyNodeInfo { MyNodeInfo {
virtual_ipv4: info virtual_ipv4: info
.virtual_ipv4 .virtual_ipv4
@@ -1,20 +1,27 @@
[package] [package]
name = "easytier-ohos-features" name = "easytier-ohos-features"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition.workspace = true
rust-version.workspace = true
description = "HarmonyOS-side EasyTier configuration, schema, persistence and sharing features" description = "HarmonyOS-side EasyTier configuration, schema, persistence and sharing features"
publish = false publish = false
[dependencies] [dependencies]
base64 = "0.22" base64.workspace = true
easytier = { path = "../../../../easytier" } easytier = { workspace = true, default-features = true }
flate2 = "1.1" flate2 = "1.1"
gethostname = "1.1" gethostname.workspace = true
once_cell = "1.21.3" once_cell.workspace = true
prost-reflect = { version = "0.14.5", default-features = false, features = ["derive"] } prost-reflect = { version = "0.14.5", default-features = false, features = ["derive"] }
rusqlite = { version = "0.32", features = ["bundled"] } rusqlite = { version = "0.32", features = ["bundled"] }
serde = { version = "1.0", features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = "1.0.125" serde_json.workspace = true
tracing = "0.1.41" tracing.workspace = true
url = "2.5" url.workspace = true
uuid = { version = "1.5.0", features = ["v4", "fast-rng", "macro-diagnostics", "serde"] } uuid = { workspace = true, features = [
"v4",
"fast-rng",
"macro-diagnostics",
"serde",
] }
@@ -171,6 +171,7 @@ fn kind_to_value_kind(field: &FieldDescriptor) -> String {
} }
} }
#[allow(clippy::too_many_arguments)]
fn build_node( fn build_node(
node_kind: &str, node_kind: &str,
name: String, name: String,
@@ -228,10 +229,10 @@ fn build_map_entry_node(message_desc: &MessageDescriptor) -> NetworkConfigSchema
} }
fn field_children(field: &FieldDescriptor) -> Vec<NetworkConfigSchema> { fn field_children(field: &FieldDescriptor) -> Vec<NetworkConfigSchema> {
if field.is_map() { if field.is_map()
if let Kind::Message(message_desc) = field.kind() { && let Kind::Message(message_desc) = field.kind()
return vec![build_map_entry_node(&message_desc)]; {
} return vec![build_map_entry_node(&message_desc)];
} }
match field.kind() { match field.kind() {
@@ -151,10 +151,7 @@ pub fn save_config_record(
} }
}; };
let fields = match validation::config_to_top_level_map(&config) { let fields = validation::config_to_top_level_map(&config)?;
Some(fields) => fields,
None => return None,
};
let conn = open_db()?; let conn = open_db()?;
let tx = conn.unchecked_transaction().ok()?; let tx = conn.unchecked_transaction().ok()?;
@@ -173,10 +170,10 @@ pub fn save_config_record(
tx.commit().ok()?; tx.commit().ok()?;
if let Some(legacy_path) = legacy_config_file_path(&config_id) { if let Some(legacy_path) = legacy_config_file_path(&config_id)
if legacy_path.exists() { && legacy_path.exists()
let _ = std::fs::remove_file(legacy_path); {
} let _ = std::fs::remove_file(legacy_path);
} }
Some(StoredConfigRecord { Some(StoredConfigRecord {
@@ -289,10 +286,10 @@ pub fn delete_config_record(config_id: &str) -> bool {
if validation::validate_config_id(config_id).is_err() { if validation::validate_config_id(config_id).is_err() {
return false; return false;
} }
if let Some(path) = legacy_config_file_path(config_id) { if let Some(path) = legacy_config_file_path(config_id)
if path.exists() { && path.exists()
let _ = std::fs::remove_file(path); {
} let _ = std::fs::remove_file(path);
} }
let conn = match open_db() { let conn = match open_db() {
+1 -1
View File
@@ -215,7 +215,7 @@ fn maybe_stop_local_socket_server() {
} }
fn run_config_server_instance(config_id: &str, config: &NetworkConfig) -> bool { fn run_config_server_instance(config_id: &str, config: &NetworkConfig) -> bool {
if INSTANCE_MANAGER.instance_ids().iter().next().is_some() { if !INSTANCE_MANAGER.instance_ids().is_empty() {
ohrs_log_error!("[Rust] there is a running instance!"); ohrs_log_error!("[Rust] there is a running instance!");
return false; return false;
} }
@@ -24,21 +24,12 @@ pub struct LogFileInfo {
pub active: bool, pub active: bool,
} }
#[derive(Clone)] #[derive(Clone, Default)]
struct LogOptions { struct LogOptions {
core_log: bool, core_log: bool,
debug_log: bool, debug_log: bool,
} }
impl Default for LogOptions {
fn default() -> Self {
Self {
core_log: false,
debug_log: false,
}
}
}
#[derive(Default)] #[derive(Default)]
struct LogManagerState { struct LogManagerState {
log_dir: Option<PathBuf>, log_dir: Option<PathBuf>,
@@ -231,6 +222,7 @@ pub(crate) fn record_core_log(level: i32, target: &str, message: &str) {
} }
} }
#[allow(dead_code)]
#[napi] #[napi]
pub fn init_log_manager(root_dir: String, process_name: String) -> bool { pub fn init_log_manager(root_dir: String, process_name: String) -> bool {
let dir = log_dir(&root_dir); let dir = log_dir(&root_dir);
@@ -286,16 +278,19 @@ pub fn init_log_manager(root_dir: String, process_name: String) -> bool {
true true
} }
#[allow(dead_code)]
#[napi] #[napi]
pub fn configure_log_manager(core_log: bool, debug_log: bool) { pub fn configure_log_manager(core_log: bool, debug_log: bool) {
configure(core_log, debug_log); configure(core_log, debug_log);
} }
#[allow(dead_code)]
#[napi] #[napi]
pub fn write_app_log(level: i32, target: String, message: String) { pub fn write_app_log(level: i32, target: String, message: String) {
record_app_log(level, &target, &message); record_app_log(level, &target, &message);
} }
#[allow(dead_code)]
#[napi] #[napi]
pub fn drain_log_lines() -> Vec<String> { pub fn drain_log_lines() -> Vec<String> {
LOG_MANAGER LOG_MANAGER
@@ -304,6 +299,7 @@ pub fn drain_log_lines() -> Vec<String> {
.unwrap_or_default() .unwrap_or_default()
} }
#[allow(dead_code)]
#[napi] #[napi]
pub fn list_log_files() -> Vec<LogFileInfo> { pub fn list_log_files() -> Vec<LogFileInfo> {
let Some((log_dir, active_file)) = current_log_state() else { let Some((log_dir, active_file)) = current_log_state() else {
@@ -340,6 +336,7 @@ pub fn list_log_files() -> Vec<LogFileInfo> {
.collect() .collect()
} }
#[allow(dead_code)]
#[napi] #[napi]
pub fn read_log_file(file_name: String) -> Option<String> { pub fn read_log_file(file_name: String) -> Option<String> {
let (log_dir, _) = current_log_state()?; let (log_dir, _) = current_log_state()?;
@@ -347,6 +344,7 @@ pub fn read_log_file(file_name: String) -> Option<String> {
fs::read_to_string(path).ok() fs::read_to_string(path).ok()
} }
#[allow(dead_code)]
#[napi] #[napi]
pub fn export_log_file(file_name: String, target_path: String) -> bool { pub fn export_log_file(file_name: String, target_path: String) -> bool {
let Some((log_dir, _)) = current_log_state() else { let Some((log_dir, _)) = current_log_state() else {
@@ -358,6 +356,7 @@ pub fn export_log_file(file_name: String, target_path: String) -> bool {
fs::copy(path, target_path).is_ok() fs::copy(path, target_path).is_ok()
} }
#[allow(dead_code)]
#[napi] #[napi]
pub fn export_log_archive(target_path: String) -> bool { pub fn export_log_archive(target_path: String) -> bool {
let log_dir = LOG_MANAGER let log_dir = LOG_MANAGER
@@ -2,8 +2,7 @@ use super::log_manager;
use napi_derive_ohos::napi; use napi_derive_ohos::napi;
use std::collections::HashMap; use std::collections::HashMap;
use std::panic; use std::panic;
use tracing::{Event, Subscriber}; use tracing::{Event, Level, Subscriber};
use tracing_core::Level;
use tracing_subscriber::layer::{Context, Layer}; use tracing_subscriber::layer::{Context, Layer};
use tracing_subscriber::prelude::*; use tracing_subscriber::prelude::*;
@@ -13,6 +12,7 @@ fn panic_hook(info: &panic::PanicHookInfo) {
log_manager::record_core_log(5, "RustPanic", &format!("{}", info)); log_manager::record_core_log(5, "RustPanic", &format!("{}", info));
} }
#[allow(dead_code)]
#[napi] #[napi]
pub fn init_panic_hook() { pub fn init_panic_hook() {
INITIALIZED.call_once(|| { INITIALIZED.call_once(|| {
@@ -20,12 +20,14 @@ pub fn init_panic_hook() {
}); });
} }
#[allow(dead_code)]
#[napi] #[napi]
pub fn hilog_global_options(domain: u32, tag: String) { pub fn hilog_global_options(domain: u32, tag: String) {
let _ = domain; let _ = domain;
let _ = tag; let _ = tag;
} }
#[allow(dead_code)]
#[napi] #[napi]
pub fn init_tracing_subscriber() { pub fn init_tracing_subscriber() {
TRACING_INITIALIZED.call_once(|| { TRACING_INITIALIZED.call_once(|| {
@@ -58,8 +60,10 @@ fn tracing_callback(event: &Event, fields: HashMap<String, String>) {
log_manager::record_core_log(level, &format!("Rust:{}", loc), &values); log_manager::record_core_log(level, &format!("Rust:{}", loc), &values);
} }
type Callback = Box<dyn Fn(&Event, HashMap<String, String>) + Send + Sync>;
struct CallbackLayer { struct CallbackLayer {
callback: Box<dyn Fn(&Event, HashMap<String, String>) + Send + Sync>, callback: Callback,
} }
impl<S: Subscriber> Layer<S> for CallbackLayer { impl<S: Subscriber> Layer<S> for CallbackLayer {
+2 -1
View File
@@ -2,6 +2,7 @@
name = "easytier-uptime" name = "easytier-uptime"
version = "0.1.0" version = "0.1.0"
edition.workspace = true edition.workspace = true
rust-version.workspace = true
[dependencies] [dependencies]
tokio = { workspace = true, default-features = true, features = ["full"] } tokio = { workspace = true, default-features = true, features = ["full"] }
@@ -14,7 +15,7 @@ chrono = { workspace = true, features = ["serde"] }
uuid = { workspace = true, features = ["v4", "serde"] } uuid = { workspace = true, features = ["v4", "serde"] }
# Axum web framework # Axum web framework
axum = { version = "0.8.9", features = ["macros"] } axum = { workspace = true, features = ["macros"] }
axum-extra = { version = "0.12", features = ["query"] } axum-extra = { version = "0.12", features = ["query"] }
tower-http = { workspace = true, features = ["cors", "compression-full"] } tower-http = { workspace = true, features = ["cors", "compression-full"] }
+2 -1
View File
@@ -4,6 +4,7 @@ version = "2.7.0"
description = "EasyTier GUI" description = "EasyTier GUI"
authors = ["you"] authors = ["you"]
edition.workspace = true edition.workspace = true
rust-version.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -45,7 +46,7 @@ async-trait.workspace = true
url = { workspace = true, features = ["serde"] } url = { workspace = true, features = ["serde"] }
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
windows = { version = "0.62", features = ["Win32_Foundation", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] } windows = { workspace = true, features = ["Win32_Foundation", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
winapi = { workspace = true, features = ["securitybaseapi", "processthreadsapi"] } winapi = { workspace = true, features = ["securitybaseapi", "processthreadsapi"] }
[target.'cfg(target_family = "unix")'.dependencies] [target.'cfg(target_family = "unix")'.dependencies]
+2 -1
View File
@@ -2,6 +2,7 @@
name = "easytier-web" name = "easytier-web"
version = "2.7.0" version = "2.7.0"
edition.workspace = true edition.workspace = true
rust-version.workspace = true
description = "Config server for easytier. easytier-core gets config from this and web frontend use it as restful api server." description = "Config server for easytier. easytier-core gets config from this and web frontend use it as restful api server."
[dependencies] [dependencies]
@@ -18,7 +19,7 @@ async-trait.workspace = true
maxminddb = "0.32" maxminddb = "0.32"
axum = { version = "0.8", features = ["macros"] } axum = { workspace = true, features = ["macros"] }
axum-login = { version = "0.18" } axum-login = { version = "0.18" }
password-auth = { version = "1.0.0" } password-auth = { version = "1.0.0" }
axum-messages = "0.8.0" axum-messages = "0.8.0"
+2 -2
View File
@@ -59,7 +59,7 @@ quanta.workspace = true
strum = { workspace = true, features = ["derive"] } strum = { workspace = true, features = ["derive"] }
gethostname = "1.1.0" gethostname.workspace = true
futures = { workspace = true, features = ["bilock", "unstable"] } futures = { workspace = true, features = ["bilock", "unstable"] }
@@ -241,7 +241,7 @@ windivert = { git = "https://github.com/EasyTier/windivert-rust.git", rev = "adc
] } ] }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
windows = { version = "0.62.2", features = [ windows = { workspace = true, features = [
"Win32_Foundation", "Win32_Foundation",
"Win32_NetworkManagement_IpHelper", "Win32_NetworkManagement_IpHelper",
"Win32_NetworkManagement_Ndis", "Win32_NetworkManagement_Ndis",
+1
View File
@@ -4,6 +4,7 @@ version = "0.0.0"
authors = ["You"] authors = ["You"]
description = "" description = ""
edition.workspace = true edition.workspace = true
rust-version.workspace = true
exclude = ["/examples", "/webview-dist", "/webview-src", "/node_modules"] exclude = ["/examples", "/webview-dist", "/webview-src", "/node_modules"]
links = "tauri-plugin-vpnservice" links = "tauri-plugin-vpnservice"