mirror of
https://github.com/TrustTunnel/TrustTunnel.git
synced 2026-04-21 02:11:45 +00:00
Pull request 78: Add command to generate client's config
Squashed commit of the following: commit d7c1d780d9b58a9108330fb37f7278f01397dc2f Author: Andrey Yakushin <a.yakushin@adguard.com> Date: Tue Jul 22 22:40:56 2025 +0400 Fix docs for certificate commit 045c8d3170f825335cca084b9f0b46e2b0e99553 Author: Andrey Yakushin <a.yakushin@adguard.com> Date: Tue Jul 22 22:39:59 2025 +0400 Fulfill the generated config with all remaing fields from [endpoint] config section commit 1ab3271c0785a75fc89ec4f7f9bd214516d3d16d Author: Andrey Yakushin <a.yakushin@adguard.com> Date: Tue Jul 22 22:30:10 2025 +0400 Fix has_ipv6 description commit 21f138edca65d7aa1606881d58789a51790f601c Author: Andrey Yakushin <a.yakushin@adguard.com> Date: Tue Jul 22 19:08:34 2025 +0400 Place has_ipv6 after addresses commit 0b520c398cede67557fbb2669d0d46e8daaf5823 Author: Andrey Yakushin <a.yakushin@adguard.com> Date: Tue Jul 22 19:01:58 2025 +0400 Add has_ipv6 field to client's config commit 03c692e63f2d6a91f4ccd332627f44eb00f6066a Author: Andrey Yakushin <a.yakushin@adguard.com> Date: Mon Jun 16 14:38:40 2025 +0400 Do not create authenticator if there are no clients commit e16d2de1063dfadc244c0605bddecbcd56e55514 Author: Andrey Yakushin <a.yakushin@adguard.com> Date: Thu Jun 5 18:45:10 2025 +0400 Introduce client's config generator commit bb83e046c1ae71fca63033c515152780be4412ba Author: Andrey Yakushin <a.yakushin@adguard.com> Date: Thu Jun 5 17:08:01 2025 +0400 Move ToTomlComment trait to utils commit a170584d76684c8960c218146e7a10672a373863 Author: Andrey Yakushin <a.yakushin@adguard.com> Date: Thu Jun 5 17:07:01 2025 +0400 Move authenticator out of settings
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
use std::sync::Arc;
|
||||
use log::{error, info, LevelFilter};
|
||||
use tokio::signal;
|
||||
use vpn_libs_endpoint::authentication::registry_based::RegistryBasedAuthenticator;
|
||||
use vpn_libs_endpoint::authentication::Authenticator;
|
||||
use vpn_libs_endpoint::core::Core;
|
||||
use vpn_libs_endpoint::{log_utils, settings};
|
||||
use vpn_libs_endpoint::settings::Settings;
|
||||
use vpn_libs_endpoint::shutdown::Shutdown;
|
||||
use vpn_libs_endpoint::client_config;
|
||||
|
||||
|
||||
const VERSION_STRING: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
@@ -13,6 +17,8 @@ const LOG_LEVEL_PARAM_NAME: &str = "log_level";
|
||||
const LOG_FILE_PARAM_NAME: &str = "log_file";
|
||||
const SETTINGS_PARAM_NAME: &str = "settings";
|
||||
const TLS_HOSTS_SETTINGS_PARAM_NAME: &str = "tls_hosts_settings";
|
||||
const CLIENT_CONFIG_PARAM_NAME: &str = "client_config";
|
||||
const ADDRESS_PARAM_NAME: &str = "address";
|
||||
const SENTRY_DSN_PARAM_NAME: &str = "sentry_dsn";
|
||||
const THREADS_NUM_PARAM_NAME: &str = "threads_num";
|
||||
|
||||
@@ -56,6 +62,19 @@ fn main() {
|
||||
.action(clap::ArgAction::Set)
|
||||
.required_unless_present(VERSION_PARAM_NAME)
|
||||
.help("Path to a file containing TLS hosts settings. Sending SIGHUP to the process causes reloading the settings."),
|
||||
clap::Arg::new(CLIENT_CONFIG_PARAM_NAME)
|
||||
.action(clap::ArgAction::Set)
|
||||
.requires(ADDRESS_PARAM_NAME)
|
||||
.short('c')
|
||||
.long("client_config")
|
||||
.value_names(["client_name"])
|
||||
.help("Print the endpoint config for specified client and exit."),
|
||||
clap::Arg::new(ADDRESS_PARAM_NAME)
|
||||
.action(clap::ArgAction::Append)
|
||||
.requires(CLIENT_CONFIG_PARAM_NAME)
|
||||
.short('a')
|
||||
.long("address")
|
||||
.help("Endpoint address to be added to client's config.")
|
||||
])
|
||||
.disable_version_flag(true)
|
||||
.get_matches();
|
||||
@@ -105,6 +124,18 @@ fn main() {
|
||||
.expect("Couldn't read the TLS hosts settings file")
|
||||
).expect("Couldn't parse the TLS hosts settings file");
|
||||
|
||||
if args.contains_id(CLIENT_CONFIG_PARAM_NAME) {
|
||||
let username = args.get_one::<String>(CLIENT_CONFIG_PARAM_NAME).unwrap();
|
||||
let addresses: Vec<String> = args.get_many::<String>(ADDRESS_PARAM_NAME)
|
||||
.map(Iterator::cloned)
|
||||
.map(Iterator::collect)
|
||||
.expect("Addresses should be specified");
|
||||
|
||||
let client_config = client_config::build(&username, &addresses, settings.get_clients(), &tls_hosts_settings);
|
||||
println!("{}", client_config.compose_toml());
|
||||
return;
|
||||
}
|
||||
|
||||
let rt = {
|
||||
let mut builder = tokio::runtime::Builder::new_multi_thread();
|
||||
builder.enable_io();
|
||||
@@ -119,8 +150,13 @@ fn main() {
|
||||
};
|
||||
|
||||
let shutdown = Shutdown::new();
|
||||
let authenticator: Option<Arc<dyn Authenticator>> = if !settings.get_clients().is_empty() {
|
||||
Some(Arc::new(RegistryBasedAuthenticator::new(settings.get_clients())))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let core = Arc::new(Core::new(
|
||||
settings, tls_hosts_settings, shutdown.clone(),
|
||||
settings, authenticator, tls_hosts_settings, shutdown.clone(),
|
||||
).expect("Couldn't create core instance"));
|
||||
|
||||
let listen_task = {
|
||||
|
||||
@@ -23,11 +23,10 @@ pub struct RegistryBasedAuthenticator {
|
||||
}
|
||||
|
||||
impl RegistryBasedAuthenticator {
|
||||
pub fn new<I>(clients: I) -> Self
|
||||
where I: Iterator<Item=Client>
|
||||
pub fn new(clients: &Vec<Client>) -> Self
|
||||
{
|
||||
Self {
|
||||
clients: clients
|
||||
clients: clients.iter()
|
||||
.map(|x| BASE64_ENGINE.encode(format!("{}:{}", x.username, x.password)))
|
||||
.map(Cow::Owned)
|
||||
.collect(),
|
||||
|
||||
118
lib/src/client_config.rs
Normal file
118
lib/src/client_config.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
#[cfg(feature = "rt_doc")]
|
||||
use macros::{Getter, RuntimeDoc};
|
||||
use once_cell::sync::Lazy;
|
||||
use toml_edit::{value, Document};
|
||||
use crate::{authentication::registry_based, settings::TlsHostsSettings, utils::ToTomlComment};
|
||||
|
||||
|
||||
pub fn build(client: &String, addresses: &Vec<String>, username: &Vec<registry_based::Client>, hostsettings: &TlsHostsSettings) -> ClientConfig {
|
||||
let user = username.iter().find(|x| {
|
||||
x.username == *client
|
||||
}) .expect("There is no user config for specified username");
|
||||
|
||||
let host = hostsettings.main_hosts.first().expect("Can't find main host inside hosts config");
|
||||
|
||||
ClientConfig {
|
||||
hostname: host.hostname.clone(),
|
||||
addresses: addresses.clone(),
|
||||
has_ipv6: true, // Hardcoded to true, client could change this himself
|
||||
username: user.username.clone(),
|
||||
password: user.password.clone(),
|
||||
skip_verification: false,
|
||||
certificate: std::fs::read_to_string(&host.cert_chain_path)
|
||||
.expect("Failed to load certificate"),
|
||||
upstream_protocol: "http2".into(),
|
||||
upstream_fallback_protocol: "".into(),
|
||||
anti_dpi: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "rt_doc", derive(Getter, RuntimeDoc))]
|
||||
pub struct ClientConfig {
|
||||
/// Endpoint host name, used for TLS session establishment
|
||||
hostname: String,
|
||||
/// Endpoint addresses.
|
||||
addresses: Vec<String>,
|
||||
/// Whether IPv6 traffic can be routed through the endpoint
|
||||
has_ipv6: bool,
|
||||
/// Username for authorization
|
||||
username: String,
|
||||
/// Password for authorization
|
||||
password: String,
|
||||
/// Skip the endpoint certificate verification?
|
||||
/// That is, any certificate is accepted with this one set to true.
|
||||
skip_verification: bool,
|
||||
/// Endpoint certificate in PEM format.
|
||||
/// If not specified, the endpoint certificate is verified using the system storage.
|
||||
certificate: String,
|
||||
/// Protocol to be used to communicate with the endpoint [http2, http3]
|
||||
upstream_protocol: String,
|
||||
/// Fallback protocol to be used in case the main one fails [<none>, http2, http3]
|
||||
upstream_fallback_protocol: String,
|
||||
/// Is anti-DPI measures should be enabled
|
||||
anti_dpi: bool
|
||||
}
|
||||
|
||||
impl ClientConfig {
|
||||
pub fn compose_toml(&self) -> String {
|
||||
let mut doc: Document = TEMPLATE.parse().unwrap();
|
||||
doc["hostname"] = value(&self.hostname);
|
||||
let vec = toml_edit::Array::from_iter(self.addresses.iter());
|
||||
doc["addresses"] = value(vec);
|
||||
doc["has_ipv6"] = value(self.has_ipv6);
|
||||
doc["username"] = value(&self.username);
|
||||
doc["password"] = value(&self.password);
|
||||
doc["skip_verification"] = value(self.skip_verification);
|
||||
doc["certificate"] = value(&self.certificate);
|
||||
doc["upstream_protocol"] = value(&self.upstream_protocol);
|
||||
doc["upstream_fallback_protocol"] = value(&self.upstream_fallback_protocol);
|
||||
doc["anti_dpi"] = value(self.anti_dpi);
|
||||
doc.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
static TEMPLATE: Lazy<String> = Lazy::new(|| format!(
|
||||
r#"
|
||||
# This file was automatically generated by endpoint and could be used in vpn client.
|
||||
|
||||
{}
|
||||
hostname = ""
|
||||
|
||||
{}
|
||||
addresses = []
|
||||
|
||||
{}
|
||||
has_ipv6 = true
|
||||
|
||||
{}
|
||||
username = ""
|
||||
|
||||
{}
|
||||
password = ""
|
||||
|
||||
{}
|
||||
skip_verification = false
|
||||
|
||||
{}
|
||||
certificate = ""
|
||||
|
||||
{}
|
||||
upstream_protocol = ""
|
||||
|
||||
{}
|
||||
upstream_fallback_protocol = ""
|
||||
|
||||
{}
|
||||
anti_dpi = false
|
||||
"#,
|
||||
ClientConfig::doc_hostname().to_toml_comment(),
|
||||
ClientConfig::doc_addresses().to_toml_comment(),
|
||||
ClientConfig::doc_has_ipv6().to_toml_comment(),
|
||||
ClientConfig::doc_username().to_toml_comment(),
|
||||
ClientConfig::doc_password().to_toml_comment(),
|
||||
ClientConfig::doc_skip_verification().to_toml_comment(),
|
||||
ClientConfig::doc_certificate().to_toml_comment(),
|
||||
ClientConfig::doc_upstream_protocol().to_toml_comment(),
|
||||
ClientConfig::doc_upstream_fallback_protocol().to_toml_comment(),
|
||||
ClientConfig::doc_anti_dpi().to_toml_comment(),
|
||||
));
|
||||
@@ -40,6 +40,7 @@ pub struct Core {
|
||||
|
||||
pub(crate) struct Context {
|
||||
pub settings: Arc<Settings>,
|
||||
pub authenticator: Option<Arc<dyn authentication::Authenticator>>,
|
||||
tls_demux: Arc<RwLock<TlsDemux>>,
|
||||
pub icmp_forwarder: Option<Arc<IcmpForwarder>>,
|
||||
pub shutdown: Arc<Mutex<Shutdown>>,
|
||||
@@ -51,6 +52,7 @@ pub(crate) struct Context {
|
||||
impl Core {
|
||||
pub fn new(
|
||||
settings: Settings,
|
||||
authenticator: Option<Arc<dyn authentication::Authenticator>>,
|
||||
tls_hosts_settings: settings::TlsHostsSettings,
|
||||
shutdown: Arc<Mutex<Shutdown>>,
|
||||
) -> Result<Self, Error> {
|
||||
@@ -66,6 +68,7 @@ impl Core {
|
||||
Ok(Self {
|
||||
context: Arc::new(Context {
|
||||
settings: settings.clone(),
|
||||
authenticator,
|
||||
tls_demux: Arc::new(RwLock::new(
|
||||
TlsDemux::new(&settings, &tls_hosts_settings)
|
||||
.map_err(|e| Error::TlsDemultiplexer(e.to_string()))?
|
||||
@@ -390,7 +393,7 @@ impl Core {
|
||||
) {
|
||||
let _metrics_guard = Metrics::client_sessions_counter(context.metrics.clone(), protocol);
|
||||
|
||||
let authentication_policy = match context.settings.authenticator.as_ref().zip(sni_auth_creds) {
|
||||
let authentication_policy = match context.authenticator.as_ref().zip(sni_auth_creds) {
|
||||
None => tunnel::AuthenticationPolicy::Default,
|
||||
Some((authenticator, credentials)) => {
|
||||
let auth = authentication::Source::Sni(credentials.into());
|
||||
@@ -458,6 +461,7 @@ impl Default for Context {
|
||||
let settings = Arc::new(Settings::default());
|
||||
Self {
|
||||
settings: settings.clone(),
|
||||
authenticator: None,
|
||||
tls_demux: Arc::new(RwLock::new(
|
||||
TlsDemux::new(&settings, &settings::TlsHostsSettings::default()).unwrap()
|
||||
)),
|
||||
|
||||
@@ -10,6 +10,7 @@ pub mod log_utils;
|
||||
pub mod shutdown;
|
||||
pub mod net_utils;
|
||||
pub mod utils;
|
||||
pub mod client_config;
|
||||
|
||||
mod direct_forwarder;
|
||||
mod downstream;
|
||||
|
||||
@@ -4,15 +4,13 @@ use std::io;
|
||||
use std::io::ErrorKind;
|
||||
use std::net::{Ipv4Addr, SocketAddr, ToSocketAddrs};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(feature = "rt_doc")]
|
||||
use macros::{Getter, RuntimeDoc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use toml_edit::{Document, Item};
|
||||
use authentication::registry_based::RegistryBasedAuthenticator;
|
||||
use crate::authentication::Authenticator;
|
||||
use authentication::registry_based::Client;
|
||||
use crate::{authentication, utils};
|
||||
|
||||
pub type Socks5BuilderResult<T> = Result<T, Socks5Error>;
|
||||
@@ -101,6 +99,7 @@ pub struct Settings {
|
||||
pub(crate) forward_protocol: ForwardProtocolSettings,
|
||||
/// The set of enabled client listener codecs
|
||||
pub(crate) listen_protocols: ListenProtocolSettings,
|
||||
// TODO (ayakushin): fix docs
|
||||
/// The client authenticator.
|
||||
///
|
||||
/// If [forward_protocol](Settings.forward_protocol) is set to
|
||||
@@ -125,8 +124,8 @@ pub struct Settings {
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing)]
|
||||
#[serde(rename(deserialize = "credentials_file"))]
|
||||
#[serde(deserialize_with = "deserialize_authenticator")]
|
||||
pub(crate) authenticator: Option<Arc<dyn Authenticator>>,
|
||||
#[serde(deserialize_with = "deserialize_clients")]
|
||||
pub(crate) clients: Vec<Client>,
|
||||
/// The reverse proxy settings.
|
||||
/// With this one set up the endpoint does TLS termination on such connections and
|
||||
/// translates HTTP/x traffic into HTTP/1.1 protocol towards the server and back
|
||||
@@ -488,12 +487,12 @@ impl Default for Settings {
|
||||
tcp_connections_timeout: Settings::default_tcp_connections_timeout(),
|
||||
udp_connections_timeout: Settings::default_udp_connections_timeout(),
|
||||
forward_protocol: Default::default(),
|
||||
clients: Default::default(),
|
||||
listen_protocols: ListenProtocolSettings {
|
||||
http1: Some(Http1Settings::builder().build()),
|
||||
http2: Some(Http2Settings::builder().build()),
|
||||
quic: Some(QuicSettings::builder().build()),
|
||||
},
|
||||
authenticator: None,
|
||||
reverse_proxy: None,
|
||||
icmp: None,
|
||||
metrics: Default::default(),
|
||||
@@ -729,7 +728,7 @@ impl SettingsBuilder {
|
||||
udp_connections_timeout: Settings::default_udp_connections_timeout(),
|
||||
forward_protocol: Default::default(),
|
||||
listen_protocols: Default::default(),
|
||||
authenticator: None,
|
||||
clients: Default::default(),
|
||||
reverse_proxy: None,
|
||||
icmp: None,
|
||||
metrics: Default::default(),
|
||||
@@ -824,8 +823,8 @@ impl SettingsBuilder {
|
||||
}
|
||||
|
||||
/// Set the client authenticator
|
||||
pub fn authenticator(mut self, x: Box<dyn Authenticator>) -> Self {
|
||||
self.settings.authenticator = Some(Arc::from(x));
|
||||
pub fn clients(mut self, x: Vec<Client>) -> Self {
|
||||
self.settings.clients = x;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -1279,7 +1278,7 @@ fn deserialize_file_path<'de, D>(deserializer: D) -> Result<String, D::Error>
|
||||
deserializer.deserialize_str(Visitor)
|
||||
}
|
||||
|
||||
fn deserialize_authenticator<'de, D>(deserializer: D) -> Result<Option<Arc<dyn Authenticator>>, D::Error>
|
||||
fn deserialize_clients<'de, D>(deserializer: D) -> Result<Vec<Client>, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
@@ -1297,26 +1296,19 @@ fn deserialize_authenticator<'de, D>(deserializer: D) -> Result<Option<Arc<dyn A
|
||||
&"A TOML-formatted file",
|
||||
))?;
|
||||
|
||||
let mut clients = clients.get("client")
|
||||
let res = clients.get("client")
|
||||
.and_then(Item::as_array_of_tables)
|
||||
.ok_or(serde::de::Error::invalid_value(
|
||||
serde::de::Unexpected::Other("Not an array of clients"),
|
||||
&"An array of clients",
|
||||
))?
|
||||
.iter()
|
||||
.map(|x| (authentication::registry_based::Client {
|
||||
.map(|x| (Client {
|
||||
username: demangle_toml_string(x["username"].to_string()),
|
||||
password: demangle_toml_string(x["password"].to_string()),
|
||||
}))
|
||||
.peekable();
|
||||
if clients.peek().is_none() {
|
||||
return Err(serde::de::Error::invalid_value(
|
||||
serde::de::Unexpected::Other("Empty client list"),
|
||||
&"Non-empty client list",
|
||||
));
|
||||
}
|
||||
})).collect();
|
||||
|
||||
Ok(Some(Arc::new(RegistryBasedAuthenticator::new(clients))))
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn demangle_toml_string(x: String) -> String {
|
||||
|
||||
@@ -120,7 +120,7 @@ impl Tunnel {
|
||||
let forwarder_auth = match (
|
||||
auth_info,
|
||||
authentication_policy,
|
||||
context.settings.authenticator.clone(),
|
||||
context.authenticator.clone(),
|
||||
) {
|
||||
(Ok(Some(source)), _, Some(authenticator)) =>
|
||||
match authenticator.authenticate(&source, &log_id) {
|
||||
|
||||
@@ -112,6 +112,27 @@ impl<I, T> IterJoin for I
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToTomlComment {
|
||||
/// Prepend each line of string with "# " turning
|
||||
/// the whole string it into TOML comment.
|
||||
fn to_toml_comment(&self) -> String;
|
||||
}
|
||||
|
||||
impl ToTomlComment for &str {
|
||||
fn to_toml_comment(&self) -> String {
|
||||
self.lines()
|
||||
.map(|x| format!("# {x}"))
|
||||
.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTomlComment for String {
|
||||
fn to_toml_comment(&self) -> String {
|
||||
self.as_str().to_toml_comment()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::utils::IterJoin;
|
||||
|
||||
@@ -120,12 +120,12 @@ async fn run_endpoint(listen_address: &SocketAddr, with_auth: bool, socks_proxy:
|
||||
.allow_private_network_connections(true);
|
||||
|
||||
if with_auth {
|
||||
builder = builder.authenticator(Box::new(RegistryBasedAuthenticator::new(std::iter::once(
|
||||
builder = builder.clients(Vec::from_iter(std::iter::once(
|
||||
authentication::registry_based::Client {
|
||||
username: "a".into(),
|
||||
password: "b".into(),
|
||||
}
|
||||
))));
|
||||
)));
|
||||
}
|
||||
|
||||
if let Some(address) = socks_proxy {
|
||||
|
||||
@@ -20,6 +20,7 @@ use rustls::client::ServerCertVerified;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio::net::{TcpStream, UdpSocket};
|
||||
use tokio_rustls::TlsConnector;
|
||||
use vpn_libs_endpoint::authentication::{Authenticator, registry_based::RegistryBasedAuthenticator};
|
||||
use vpn_libs_endpoint::core::Core;
|
||||
use vpn_libs_endpoint::log_utils;
|
||||
use vpn_libs_endpoint::settings::{Http1Settings, Http2Settings, ListenProtocolSettings, QuicSettings, Settings, TlsHostInfo, TlsHostsSettings};
|
||||
@@ -219,8 +220,13 @@ pub async fn run_endpoint(listen_address: &SocketAddr) {
|
||||
|
||||
pub async fn run_endpoint_with_settings(settings: Settings, hosts_settings: TlsHostsSettings) {
|
||||
let shutdown = Shutdown::new();
|
||||
let authenticator: Option<Arc<dyn Authenticator>> = if !settings.get_clients().is_empty() {
|
||||
Some(Arc::new(RegistryBasedAuthenticator::new(settings.get_clients())))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let endpoint = Core::new(settings, hosts_settings, shutdown).unwrap();
|
||||
let endpoint = Core::new(settings, authenticator, hosts_settings, shutdown).unwrap();
|
||||
endpoint.listen().await.unwrap();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use std::iter::once;
|
||||
use toml_edit::{Document, value};
|
||||
use vpn_libs_endpoint::settings::{ForwardProtocolSettings, Http1Settings, Http2Settings, IcmpSettings, ListenProtocolSettings, MetricsSettings, QuicSettings, Settings};
|
||||
use vpn_libs_endpoint::utils::IterJoin;
|
||||
use vpn_libs_endpoint::utils::{IterJoin, ToTomlComment};
|
||||
use crate::template_settings;
|
||||
use crate::template_settings::ToTomlComment;
|
||||
|
||||
pub fn compose_document(settings: &Settings, credentials_path: &str) -> String {
|
||||
once(compose_main_table(settings, credentials_path))
|
||||
|
||||
@@ -1,26 +1,6 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use vpn_libs_endpoint::settings::{ForwardProtocolSettings, Http1Settings, Http2Settings, IcmpSettings, ListenProtocolSettings, MetricsSettings, QuicSettings, Settings, Socks5ForwarderSettings};
|
||||
use vpn_libs_endpoint::utils::IterJoin;
|
||||
|
||||
pub trait ToTomlComment {
|
||||
/// Prepend each line of string with "# " turning
|
||||
/// the whole string it into TOML comment.
|
||||
fn to_toml_comment(&self) -> String;
|
||||
}
|
||||
|
||||
impl ToTomlComment for &str {
|
||||
fn to_toml_comment(&self) -> String {
|
||||
self.lines()
|
||||
.map(|x| format!("# {x}"))
|
||||
.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTomlComment for String {
|
||||
fn to_toml_comment(&self) -> String {
|
||||
self.as_str().to_toml_comment()
|
||||
}
|
||||
}
|
||||
use vpn_libs_endpoint::utils::ToTomlComment;
|
||||
|
||||
pub static MAIN_TABLE: Lazy<String> = Lazy::new(|| format!(
|
||||
r#"{}
|
||||
|
||||
Reference in New Issue
Block a user