diff --git a/easytier/Cargo.toml b/easytier/Cargo.toml index 8d0a25d..f932c44 100644 --- a/easytier/Cargo.toml +++ b/easytier/Cargo.toml @@ -129,6 +129,7 @@ clap = { version = "4.5.30", features = [ "unicode", "derive", "wrap_help", + "env", ] } async-recursion = "1.0.5" diff --git a/easytier/src/easytier-core.rs b/easytier/src/easytier-core.rs index b911fb4..986b1a8 100644 --- a/easytier/src/easytier-core.rs +++ b/easytier/src/easytier-core.rs @@ -96,6 +96,7 @@ struct Cli { #[arg( short = 'w', long, + env = "ET_CONFIG_SERVER", help = t!("core_clap.config_server").to_string() )] config_server: Option, @@ -103,27 +104,29 @@ struct Cli { #[arg( short, long, + env = "ET_CONFIG_FILE", help = t!("core_clap.config_file").to_string() )] config_file: Option, #[arg( long, + env = "ET_NETWORK_NAME", help = t!("core_clap.network_name").to_string(), - default_value = "default" )] - network_name: String, + network_name: Option, #[arg( long, + env = "ET_NETWORK_SECRET", help = t!("core_clap.network_secret").to_string(), - default_value = "" )] - network_secret: String, + network_secret: Option, #[arg( short, long, + env = "ET_IPV4", help = t!("core_clap.ipv4").to_string() )] ipv4: Option, @@ -131,13 +134,18 @@ struct Cli { #[arg( short, long, - help = t!("core_clap.dhcp").to_string() + env = "ET_DHCP", + help = t!("core_clap.dhcp").to_string(), + num_args = 0..=1, + default_missing_value = "true" )] - dhcp: bool, + dhcp: Option, #[arg( short, long, + env = "ET_PEERS", + value_delimiter = ',', help = t!("core_clap.peers").to_string(), num_args = 0.. )] @@ -146,6 +154,7 @@ struct Cli { #[arg( short, long, + env = "ET_EXTERNAL_NODE", help = t!("core_clap.external_node").to_string() )] external_node: Option, @@ -153,6 +162,8 @@ struct Cli { #[arg( short = 'n', long, + env = "ET_PROXY_NETWORKS", + value_delimiter = ',', help = t!("core_clap.proxy_networks").to_string() )] proxy_networks: Vec, @@ -160,14 +171,16 @@ struct Cli { #[arg( short, long, + env = "ET_RPC_PORTAL", help = t!("core_clap.rpc_portal").to_string(), - default_value = "0" )] - rpc_portal: String, + rpc_portal: Option, #[arg( short, long, + env = "ET_LISTENERS", + value_delimiter = ',', help = t!("core_clap.listeners").to_string(), default_values_t = ["11010".to_string()], num_args = 0.. @@ -176,6 +189,8 @@ struct Cli { #[arg( long, + env = "ET_MAPPED_LISTENERS", + value_delimiter = ',', help = t!("core_clap.mapped_listeners").to_string(), num_args = 0.. )] @@ -183,31 +198,36 @@ struct Cli { #[arg( long, + env = "ET_NO_LISTENER", help = t!("core_clap.no_listener").to_string(), - default_value = "false" + default_value = "false", )] no_listener: bool, #[arg( long, + env = "ET_CONSOLE_LOG_LEVEL", help = t!("core_clap.console_log_level").to_string() )] console_log_level: Option, #[arg( long, + env = "ET_FILE_LOG_LEVEL", help = t!("core_clap.file_log_level").to_string() )] file_log_level: Option, #[arg( long, + env = "ET_FILE_LOG_DIR", help = t!("core_clap.file_log_dir").to_string() )] file_log_dir: Option, #[arg( long, + env = "ET_HOSTNAME", help = t!("core_clap.hostname").to_string() )] hostname: Option, @@ -215,19 +235,21 @@ struct Cli { #[arg( short = 'm', long, + env = "ET_INSTANCE_NAME", help = t!("core_clap.instance_name").to_string(), - default_value = "default" )] - instance_name: String, + instance_name: Option, #[arg( long, + env = "ET_VPN_PORTAL", help = t!("core_clap.vpn_portal").to_string() )] vpn_portal: Option, #[arg( long, + env = "ET_DEFAULT_PROTOCOL", help = t!("core_clap.default_protocol").to_string() )] default_protocol: Option, @@ -235,46 +257,58 @@ struct Cli { #[arg( short = 'u', long, + env = "ET_DISABLE_ENCRYPTION", help = t!("core_clap.disable_encryption").to_string(), - default_value = "false" + num_args = 0..=1, + default_missing_value = "true" )] - disable_encryption: bool, + disable_encryption: Option, #[arg( long, + env = "ET_MULTI_THREAD", help = t!("core_clap.multi_thread").to_string(), - default_value = "true" + num_args = 0..=1, + default_missing_value = "true" )] - multi_thread: bool, + multi_thread: Option, #[arg( long, + env = "ET_DISABLE_IPV6", help = t!("core_clap.disable_ipv6").to_string(), - default_value = "false" + num_args = 0..=1, + default_missing_value = "true" )] - disable_ipv6: bool, + disable_ipv6: Option, #[arg( long, + env = "ET_DEV_NAME", help = t!("core_clap.dev_name").to_string() )] dev_name: Option, #[arg( long, + env = "ET_MTU", help = t!("core_clap.mtu").to_string() )] mtu: Option, #[arg( long, + env = "ET_LATENCY_FIRST", help = t!("core_clap.latency_first").to_string(), - default_value = "false" + num_args = 0..=1, + default_missing_value = "true" )] - latency_first: bool, + latency_first: Option, #[arg( long, + env = "ET_EXIT_NODES", + value_delimiter = ',', help = t!("core_clap.exit_nodes").to_string(), num_args = 0.. )] @@ -282,34 +316,44 @@ struct Cli { #[arg( long, + env = "ET_ENABLE_EXIT_NODE", help = t!("core_clap.enable_exit_node").to_string(), - default_value = "false" + num_args = 0..=1, + default_missing_value = "true" )] - enable_exit_node: bool, + enable_exit_node: Option, #[arg( long, + env = "ET_PROXY_FORWARD_BY_SYSTEM", help = t!("core_clap.proxy_forward_by_system").to_string(), - default_value = "false" + num_args = 0..=1, + default_missing_value = "true" )] - proxy_forward_by_system: bool, + proxy_forward_by_system: Option, #[arg( long, + env = "ET_NO_TUN", help = t!("core_clap.no_tun").to_string(), - default_value = "false" + num_args = 0..=1, + default_missing_value = "true" )] - no_tun: bool, + no_tun: Option, #[arg( long, + env = "ET_USE_SMOLTCP", help = t!("core_clap.use_smoltcp").to_string(), - default_value = "false" + num_args = 0..=1, + default_missing_value = "true" )] - use_smoltcp: bool, + use_smoltcp: Option, #[arg( long, + env = "ET_MANUAL_ROUTES", + value_delimiter = ',', help = t!("core_clap.manual_routes").to_string(), num_args = 0.. )] @@ -320,6 +364,8 @@ struct Cli { // for local virtual network, will refuse relaying tun packet #[arg( long, + env = "ET_RELAY_NETWORK_WHITELIST", + value_delimiter = ',', help = t!("core_clap.relay_network_whitelist").to_string(), num_args = 0.. )] @@ -327,61 +373,75 @@ struct Cli { #[arg( long, + env = "ET_DISABLE_P2P", help = t!("core_clap.disable_p2p").to_string(), - default_value = "false" + num_args = 0..=1, + default_missing_value = "true" )] - disable_p2p: bool, + disable_p2p: Option, #[arg( long, + env = "ET_DISABLE_UDP_HOLE_PUNCHING", help = t!("core_clap.disable_udp_hole_punching").to_string(), - default_value = "false" + num_args = 0..=1, + default_missing_value = "true" )] - disable_udp_hole_punching: bool, + disable_udp_hole_punching: Option, #[arg( long, + env = "ET_RELAY_ALL_PEER_RPC", help = t!("core_clap.relay_all_peer_rpc").to_string(), - default_value = "false" + num_args = 0..=1, + default_missing_value = "true" )] - relay_all_peer_rpc: bool, + relay_all_peer_rpc: Option, #[cfg(feature = "socks5")] #[arg( long, + env = "ET_SOCKS5", help = t!("core_clap.socks5").to_string() )] socks5: Option, #[arg( long, + env = "ET_COMPRESSION", help = t!("core_clap.compression").to_string(), - default_value = "none", )] - compression: String, + compression: Option, #[arg( long, + env = "ET_BIND_DEVICE", help = t!("core_clap.bind_device").to_string() )] bind_device: Option, #[arg( long, + env = "ET_ENABLE_KCP_PROXY", help = t!("core_clap.enable_kcp_proxy").to_string(), - default_value = "false" + num_args = 0..=1, + default_missing_value = "true" )] - enable_kcp_proxy: bool, + enable_kcp_proxy: Option, #[arg( long, + env = "ET_DISABLE_KCP_INPUT", help = t!("core_clap.disable_kcp_input").to_string(), - default_value = "false" + num_args = 0..=1, + default_missing_value = "true" )] - disable_kcp_input: bool, + disable_kcp_input: Option, #[arg( long, + env = "ET_PORT_FORWARD", + value_delimiter = ',', help = t!("core_clap.port_forward").to_string(), num_args = 1.. )] @@ -449,25 +509,28 @@ impl TryFrom<&Cli> for TomlConfigLoader { type Error = anyhow::Error; fn try_from(cli: &Cli) -> Result { - if let Some(config_file) = &cli.config_file { - println!( - "NOTICE: loading config file: {:?}, will ignore all command line flags\n", - config_file - ); - return Ok(TomlConfigLoader::new(config_file) - .with_context(|| format!("failed to load config file: {:?}", cli.config_file))?); + let cfg = if let Some(config_file) = &cli.config_file { + TomlConfigLoader::new(config_file) + .with_context(|| format!("failed to load config file: {:?}", cli.config_file))? + } else { + TomlConfigLoader::default() + }; + + if cli.hostname.is_some() { + cfg.set_hostname(cli.hostname.clone()); } - let cfg = TomlConfigLoader::default(); + let old_ns = cfg.get_network_identity(); + let network_name = cli.network_name.clone().unwrap_or(old_ns.network_name); + let network_secret = cli + .network_secret + .clone() + .unwrap_or(old_ns.network_secret.unwrap_or_default()); + cfg.set_network_identity(NetworkIdentity::new(network_name, network_secret)); - cfg.set_hostname(cli.hostname.clone()); - - cfg.set_network_identity(NetworkIdentity::new( - cli.network_name.clone(), - cli.network_secret.clone(), - )); - - cfg.set_dhcp(cli.dhcp); + if let Some(dhcp) = cli.dhcp { + cfg.set_dhcp(dhcp); + } if let Some(ipv4) = &cli.ipv4 { cfg.set_ipv4(Some(ipv4.parse().with_context(|| { @@ -475,39 +538,46 @@ impl TryFrom<&Cli> for TomlConfigLoader { })?)) } - let mut peers = Vec::::with_capacity(cli.peers.len()); - for p in &cli.peers { - peers.push(PeerConfig { - uri: p - .parse() - .with_context(|| format!("failed to parse peer uri: {}", p))?, - }); + if !cli.peers.is_empty() { + let mut peers = cfg.get_peers(); + peers.reserve(peers.len() + cli.peers.len()); + for p in &cli.peers { + peers.push(PeerConfig { + uri: p + .parse() + .with_context(|| format!("failed to parse peer uri: {}", p))?, + }); + } + cfg.set_peers(peers); } - cfg.set_peers(peers); - cfg.set_listeners( - Cli::parse_listeners(cli.no_listener, cli.listeners.clone())? - .into_iter() - .map(|s| s.parse().unwrap()) - .collect(), - ); + if cli.no_listener || !cli.listeners.is_empty() { + cfg.set_listeners( + Cli::parse_listeners(cli.no_listener, cli.listeners.clone())? + .into_iter() + .map(|s| s.parse().unwrap()) + .collect(), + ); + } - cfg.set_mapped_listeners(Some( - cli.mapped_listeners - .iter() - .map(|s| { - s.parse() - .with_context(|| format!("mapped listener is not a valid url: {}", s)) - .unwrap() - }) - .map(|s: url::Url| { - if s.port().is_none() { - panic!("mapped listener port is missing: {}", s); - } - s - }) - .collect(), - )); + if !cli.mapped_listeners.is_empty() { + cfg.set_mapped_listeners(Some( + cli.mapped_listeners + .iter() + .map(|s| { + s.parse() + .with_context(|| format!("mapped listener is not a valid url: {}", s)) + .unwrap() + }) + .map(|s: url::Url| { + if s.port().is_none() { + panic!("mapped listener port is missing: {}", s); + } + s + }) + .collect(), + )); + } for n in cli.proxy_networks.iter() { cfg.add_proxy_cidr( @@ -516,10 +586,15 @@ impl TryFrom<&Cli> for TomlConfigLoader { ); } - cfg.set_rpc_portal( - Cli::parse_rpc_portal(cli.rpc_portal.clone()) - .with_context(|| format!("failed to parse rpc portal: {}", cli.rpc_portal))?, - ); + let rpc_portal = if let Some(r) = &cli.rpc_portal { + Cli::parse_rpc_portal(r.clone()) + .with_context(|| format!("failed to parse rpc portal: {}", r))? + } else if let Some(r) = cfg.get_rpc_portal() { + r + } else { + Cli::parse_rpc_portal("0".into())? + }; + cfg.set_rpc_portal(rpc_portal); if let Some(external_nodes) = cli.external_node.as_ref() { let mut old_peers = cfg.get_peers(); @@ -537,15 +612,29 @@ impl TryFrom<&Cli> for TomlConfigLoader { }); } - if cli.file_log_dir.is_some() || cli.file_log_level.is_some() { - cfg.set_file_logger_config(FileLoggerConfig { - level: cli.file_log_level.clone(), - dir: cli.file_log_dir.clone(), - file: Some(format!("easytier-{}", cli.instance_name)), - }); + if let Some(inst_name) = &cli.instance_name { + cfg.set_inst_name(inst_name.clone()); } - cfg.set_inst_name(cli.instance_name.clone()); + if cli.file_log_dir.is_some() || cli.file_log_level.is_some() { + let inst_name = cfg.get_inst_name(); + let old_fl = cfg.get_file_logger_config(); + let file_log_dir = if cli.file_log_dir.is_some() { + &cli.file_log_dir + } else { + &old_fl.dir + }; + let file_log_level = if cli.file_log_level.is_some() { + &cli.file_log_level + } else { + &old_fl.level + }; + cfg.set_file_logger_config(FileLoggerConfig { + level: file_log_level.clone(), + dir: file_log_dir.clone(), + file: Some(format!("easytier-{}", inst_name)), + }); + } if let Some(vpn_portal) = cli.vpn_portal.as_ref() { let url: url::Url = vpn_portal @@ -622,44 +711,56 @@ impl TryFrom<&Cli> for TomlConfigLoader { } let mut f = cfg.get_flags(); - if cli.default_protocol.is_some() { - f.default_protocol = cli.default_protocol.as_ref().unwrap().clone(); + if let Some(default_protocol) = &cli.default_protocol { + f.default_protocol = default_protocol.clone() + }; + if let Some(v) = cli.disable_encryption { + f.enable_encryption = !v; + } + if let Some(v) = cli.disable_ipv6 { + f.enable_ipv6 = !v; + } + f.latency_first = cli.latency_first.unwrap_or(f.latency_first); + if let Some(dev_name) = &cli.dev_name { + f.dev_name = dev_name.clone() } - f.enable_encryption = !cli.disable_encryption; - f.enable_ipv6 = !cli.disable_ipv6; - f.latency_first = cli.latency_first; - f.dev_name = cli.dev_name.clone().unwrap_or_default(); if let Some(mtu) = cli.mtu { f.mtu = mtu as u32; } - f.enable_exit_node = cli.enable_exit_node; - f.proxy_forward_by_system = cli.proxy_forward_by_system; - f.no_tun = cli.no_tun || cfg!(not(feature = "tun")); - f.use_smoltcp = cli.use_smoltcp; + f.enable_exit_node = cli.enable_exit_node.unwrap_or(f.enable_exit_node); + f.proxy_forward_by_system = cli + .proxy_forward_by_system + .unwrap_or(f.proxy_forward_by_system); + f.no_tun = cli.no_tun.unwrap_or(f.no_tun) || cfg!(not(feature = "tun")); + f.use_smoltcp = cli.use_smoltcp.unwrap_or(f.use_smoltcp); if let Some(wl) = cli.relay_network_whitelist.as_ref() { f.relay_network_whitelist = wl.join(" "); } - f.disable_p2p = cli.disable_p2p; - f.disable_udp_hole_punching = cli.disable_udp_hole_punching; - f.relay_all_peer_rpc = cli.relay_all_peer_rpc; - f.multi_thread = cli.multi_thread; - f.data_compress_algo = match cli.compression.as_str() { - "none" => CompressionAlgoPb::None, - "zstd" => CompressionAlgoPb::Zstd, - _ => panic!( - "unknown compression algorithm: {}, supported: none, zstd", - cli.compression - ), + f.disable_p2p = cli.disable_p2p.unwrap_or(f.disable_p2p); + f.disable_udp_hole_punching = cli + .disable_udp_hole_punching + .unwrap_or(f.disable_udp_hole_punching); + f.relay_all_peer_rpc = cli.relay_all_peer_rpc.unwrap_or(f.relay_all_peer_rpc); + f.multi_thread = cli.multi_thread.unwrap_or(f.multi_thread); + if let Some(compression) = &cli.compression { + f.data_compress_algo = match compression.as_str() { + "none" => CompressionAlgoPb::None, + "zstd" => CompressionAlgoPb::Zstd, + _ => panic!( + "unknown compression algorithm: {}, supported: none, zstd", + compression + ), + } + .into(); } - .into(); - if let Some(bind_device) = cli.bind_device { - f.bind_device = bind_device; - } - f.enable_kcp_proxy = cli.enable_kcp_proxy; - f.disable_kcp_input = cli.disable_kcp_input; + f.bind_device = cli.bind_device.unwrap_or(f.bind_device); + f.enable_kcp_proxy = cli.enable_kcp_proxy.unwrap_or(f.enable_kcp_proxy); + f.disable_kcp_input = cli.disable_kcp_input.unwrap_or(f.disable_kcp_input); cfg.set_flags(f); - cfg.set_exit_nodes(cli.exit_nodes.clone()); + if !cli.exit_nodes.is_empty() { + cfg.set_exit_nodes(cli.exit_nodes.clone()); + } Ok(cfg) }