Merge commit '10025f9fa5503865918cbae2af5366afe7fd7c54' as 'components/mycelium'

This commit is contained in:
2025-08-16 21:12:34 +02:00
132 changed files with 50951 additions and 0 deletions

View File

@@ -0,0 +1 @@
/target

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
[package]
name = "myceliumd-private"
version = "0.6.1"
edition = "2021"
license-file = "../LICENSE"
readme = "./README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
vendored-openssl = ["mycelium/vendored-openssl"]
[[bin]]
name = "mycelium-private"
path = "src/main.rs"
[dependencies]
clap = { version = "4.5.41", features = ["derive"] }
tracing = { version = "0.1.41", features = ["release_max_level_debug"] }
tracing-logfmt = { version = "0.3.5", features = ["ansi_logs"] }
tracing-subscriber = { version = "0.3.19", features = [
"env-filter",
"nu-ansi-term",
] }
mycelium = { path = "../mycelium", features = ["private-network", "message"] }
mycelium-metrics = { path = "../mycelium-metrics", features = ["prometheus"] }
mycelium-api = { path = "../mycelium-api", features = ["message"] }
mycelium-cli = { path = "../mycelium-cli/", features = ["message"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
tokio = { version = "1.46.1", features = [
"macros",
"rt-multi-thread",
"signal",
] }
reqwest = { version = "0.12.9", default-features = false, features = ["json"] }
base64 = "0.22.1"
config = "0.15.13"
dirs = "6.0.0"
toml = "0.9.2"

View File

@@ -0,0 +1,26 @@
# Myceliumd-private
This is the main binary to use when connecting to [a private network](../docs/private_network.md).
You can either get a release from [the GitHub release page](https://github.com/threefoldtech/myceliumd/releases),
or build the code yourself here. While we intend to keep the master branch as stable,
i.e. a build from latest master should succeed, this is not a hard guarantee. Additionally,
the master branch might not be compatible with the latest release, in case of a
breaking change.
Building, with the rust toolchain installed, can be done by running `cargo build`
in this directory. Since we rely on `openssl` for the private network functionality,
that will also need to be installed. The produced binary will be dynamically linked
with this system installation. Alternatively, you can use the `vendored-openssl`
feature to compile `openssl` from source and statically link it. You won't need
to have `openssl` installed, though building will of course take longer. To stay
in line with the regular `mycelium` binary, the produced binary here is renamed
to `mycelium-private`. Optionally, the `--release` flag can be used when building.
This is recommended if you are not just developing.
While this binary can currently be used to connect to the public network, it is
not guaranteed that this will always be the case. If you intend to connect to the
public network, consider using [`the mycelium binary`](../myceliumd/README.md)
instead.
For more information about the project, please refer to [the README file in the
repository root](../README.md).

View File

@@ -0,0 +1,840 @@
use std::io::{self, Read};
use std::net::Ipv4Addr;
use std::path::Path;
use std::sync::Arc;
use std::{
error::Error,
net::{IpAddr, SocketAddr},
path::PathBuf,
};
use std::{fmt::Display, str::FromStr};
use clap::{Args, Parser, Subcommand};
use mycelium::message::TopicConfig;
use serde::{Deserialize, Deserializer};
use tokio::fs::File;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[cfg(target_family = "unix")]
use tokio::signal::{self, unix::SignalKind};
use tokio::sync::Mutex;
use tracing::{debug, error, info, warn};
use crypto::PublicKey;
use mycelium::endpoint::Endpoint;
use mycelium::{crypto, Node};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
/// The default port on the underlay to listen on for incoming TCP connections.
const DEFAULT_TCP_LISTEN_PORT: u16 = 9651;
/// The default port on the underlay to listen on for incoming Quic connections.
const DEFAULT_QUIC_LISTEN_PORT: u16 = 9651;
/// The default port to use for IPv6 link local peer discovery (UDP).
const DEFAULT_PEER_DISCOVERY_PORT: u16 = 9650;
/// The default listening address for the HTTP API.
const DEFAULT_HTTP_API_SERVER_ADDRESS: SocketAddr =
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8989);
/// The default listening address for the JSON-RPC API.
const DEFAULT_JSONRPC_API_SERVER_ADDRESS: SocketAddr =
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8990);
const DEFAULT_KEY_FILE: &str = "priv_key.bin";
/// Default name of tun interface
#[cfg(not(target_os = "macos"))]
const TUN_NAME: &str = "mycelium";
/// Default name of tun interface
#[cfg(target_os = "macos")]
const TUN_NAME: &str = "utun0";
/// The logging formats that can be selected.
#[derive(Clone, PartialEq, Eq)]
enum LoggingFormat {
Compact,
Logfmt,
/// Same as Logfmt but with color statically disabled
Plain,
}
impl Display for LoggingFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
LoggingFormat::Compact => "compact",
LoggingFormat::Logfmt => "logfmt",
LoggingFormat::Plain => "plain",
}
)
}
}
impl FromStr for LoggingFormat {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"compact" => LoggingFormat::Compact,
"logfmt" => LoggingFormat::Logfmt,
"plain" => LoggingFormat::Plain,
_ => return Err("invalid logging format"),
})
}
}
#[derive(Parser)]
#[command(version)]
struct Cli {
/// Path to the private key file. This will be created if it does not exist. Default
/// [priv_key.bin].
#[arg(short = 'k', long = "key-file", global = true)]
key_file: Option<PathBuf>,
// Configuration file
#[arg(short = 'c', long = "config-file", global = true)]
config_file: Option<PathBuf>,
/// Enable debug logging. Does nothing if `--silent` is set.
#[arg(short = 'd', long = "debug", default_value_t = false)]
debug: bool,
/// Disable all logs except error logs.
#[arg(long = "silent", default_value_t = false)]
silent: bool,
/// The logging format to use. `logfmt` and `compact` is supported.
#[arg(long = "log-format", default_value_t = LoggingFormat::Compact)]
logging_format: LoggingFormat,
#[clap(flatten)]
node_args: NodeArguments,
#[command(subcommand)]
command: Option<Command>,
}
#[derive(Debug, Subcommand)]
pub enum Command {
/// Inspect a public key provided in hex format, or export the local public key if no key is
/// given.
Inspect {
/// Output in json format.
#[arg(long = "json")]
json: bool,
/// The key to inspect.
key: Option<String>,
},
/// Actions on the message subsystem
Message {
#[command(subcommand)]
command: MessageCommand,
},
/// Actions related to peers (list, remove, add)
Peers {
#[command(subcommand)]
command: PeersCommand,
},
/// Actions related to routes (selected, fallback)
Routes {
#[command(subcommand)]
command: RoutesCommand,
},
}
#[derive(Debug, Subcommand)]
pub enum MessageCommand {
Send {
/// Wait for a reply from the receiver.
#[arg(short = 'w', long = "wait", default_value_t = false)]
wait: bool,
/// An optional timeout to wait for. This does nothing if the `--wait` flag is not set. If
/// `--wait` is set and this flag isn't, wait forever for a reply.
#[arg(long = "timeout")]
timeout: Option<u64>,
/// Optional topic of the message. Receivers can filter on this to only receive messages
/// for a chosen topic.
#[arg(short = 't', long = "topic")]
topic: Option<String>,
/// Optional file to use as message body.
#[arg(long = "msg-path")]
msg_path: Option<PathBuf>,
/// Optional message ID to reply to.
#[arg(long = "reply-to")]
reply_to: Option<String>,
/// Destination of the message, either a hex encoded public key, or an IPv6 address in the
/// 400::/7 range.
destination: String,
/// The message to send. This is required if `--msg_path` is not set
message: Option<String>,
},
Receive {
/// An optional timeout to wait for a message. If this is not set, wait forever.
#[arg(long = "timeout")]
timeout: Option<u64>,
/// Optional topic of the message. Only messages with this topic will be received by this
/// command.
#[arg(short = 't', long = "topic")]
topic: Option<String>,
/// Optional file in which the message body will be saved.
#[arg(long = "msg-path")]
msg_path: Option<PathBuf>,
/// Don't print the metadata
#[arg(long = "raw")]
raw: bool,
},
}
#[derive(Debug, Subcommand)]
pub enum PeersCommand {
/// List the connected peers
List {
/// Print the peers list in JSON format
#[arg(long = "json", default_value_t = false)]
json: bool,
},
/// Add peer(s)
Add { peers: Vec<String> },
/// Remove peer(s)
Remove { peers: Vec<String> },
}
#[derive(Debug, Subcommand)]
pub enum RoutesCommand {
/// Print all selected routes
Selected {
/// Print selected routes in JSON format
#[arg(long = "json", default_value_t = false)]
json: bool,
},
/// Print all fallback routes
Fallback {
/// Print fallback routes in JSON format
#[arg(long = "json", default_value_t = false)]
json: bool,
},
/// Print the currently queried subnets
Queried {
/// Print queried subnets in JSON format
#[arg(long = "json", default_value_t = false)]
json: bool,
},
/// Print all subnets which are explicitly marked as not having a route
NoRoute {
/// Print subnets in JSON format
#[arg(long = "json", default_value_t = false)]
json: bool,
},
}
#[derive(Debug, Args)]
pub struct NodeArguments {
/// Peers to connect to.
#[arg(long = "peers", num_args = 1..)]
static_peers: Vec<Endpoint>,
/// Port to listen on for tcp connections.
#[arg(short = 't', long = "tcp-listen-port", default_value_t = DEFAULT_TCP_LISTEN_PORT)]
tcp_listen_port: u16,
/// Disable quic protocol for connecting to peers
#[arg(long = "disable-quic", default_value_t = false)]
disable_quic: bool,
/// Port to listen on for quic connections.
#[arg(short = 'q', long = "quic-listen-port", default_value_t = DEFAULT_QUIC_LISTEN_PORT)]
quic_listen_port: u16,
/// Port to use for link local peer discovery. This uses the UDP protocol.
#[arg(long = "peer-discovery-port", default_value_t = DEFAULT_PEER_DISCOVERY_PORT)]
peer_discovery_port: u16,
/// Disable peer discovery.
///
/// If this flag is passed, the automatic link local peer discovery will not be enabled, and
/// peers must be configured manually. If this is disabled on all local peers, communication
/// between them will go over configured external peers.
#[arg(long = "disable-peer-discovery", default_value_t = false)]
disable_peer_discovery: bool,
/// Address of the HTTP API server.
#[arg(long = "api-addr", default_value_t = DEFAULT_HTTP_API_SERVER_ADDRESS)]
api_addr: SocketAddr,
/// Address of the JSON-RPC API server.
#[arg(long = "jsonrpc-addr", default_value_t = DEFAULT_JSONRPC_API_SERVER_ADDRESS)]
jsonrpc_addr: SocketAddr,
/// Run without creating a TUN interface.
///
/// The system will participate in the network as usual, but won't be able to send out L3
/// packets. Inbound L3 traffic will be silently discarded. The message subsystem will still
/// work however.
#[arg(long = "no-tun", default_value_t = false)]
no_tun: bool,
/// Name to use for the TUN interface, if one is created.
///
/// Setting this only matters if a TUN interface is actually created, i.e. if the `--no-tun`
/// flag is **not** set. The name set here must be valid for the current platform, e.g. on OSX,
/// the name must start with `utun` and be followed by digits.
#[arg(long = "tun-name")]
tun_name: Option<String>,
/// Enable a private network, with this name.
///
/// If this flag is set, the system will run in "private network mode", and use Tls connections
/// instead of plain Tcp connections. The name provided here is used as the network name, other
/// nodes must use the same name or the connection will be rejected. Note that the name is
/// public, and is communicated when connecting to a remote. Do not put confidential data here.
#[arg(long = "network-name", requires = "network_key_file")]
network_name: Option<String>,
/// The path to the file with the key to use for the private network.
///
/// The key is expected to be exactly 32 bytes. The key must be shared between all nodes
/// participating in the network, and is secret. If the key leaks, anyone can then join the
/// network.
#[arg(long = "network-key-file", requires = "network_name")]
network_key_file: Option<PathBuf>,
/// The address on which to expose prometheus metrics, if desired.
///
/// Setting this flag will attempt to start an HTTP server on the provided address, to serve
/// prometheus metrics on the /metrics endpoint. If this flag is not set, metrics are also not
/// collected.
#[arg(long = "metrics-api-address")]
metrics_api_address: Option<SocketAddr>,
/// The firewall mark to set on the mycelium sockets.
///
/// This allows to identify packets that contain encapsulated mycelium packets so that
/// different routing policies can be applied to them.
/// This option only has an effect on Linux.
#[arg(long = "firewall-mark")]
firewall_mark: Option<u32>,
/// The amount of worker tasks to spawn to handle updates.
///
/// By default, updates are processed on a single task only. This is sufficient for most use
/// cases. In case you notice that the node can't keep up with the incoming updates (typically
/// because you are running a public node with a lot of connections), this value can be
/// increased to process updates in parallel.
#[arg(long = "update-workers", default_value_t = 1)]
update_workers: usize,
/// The topic configuration.
///
/// A .toml file containing topic configuration. This is a default action in case the topic is
/// not listed, and an explicit whitelist for allowed subnets/ips which are otherwise allowed
/// to use a topic.
#[arg(long = "topic-config")]
topic_config: Option<PathBuf>,
/// The cache directory for the mycelium CDN module
///
/// This directory will be used to cache reconstructed content blocks which were loaded through
/// the CDN functionallity for faster access next time.
#[arg(long = "cdn-cache")]
cdn_cache: Option<PathBuf>,
}
#[derive(Debug, Deserialize)]
pub struct MergedNodeConfig {
peers: Vec<Endpoint>,
tcp_listen_port: u16,
disable_quic: bool,
quic_listen_port: u16,
peer_discovery_port: u16,
disable_peer_discovery: bool,
api_addr: SocketAddr,
jsonrpc_addr: SocketAddr,
no_tun: bool,
tun_name: String,
metrics_api_address: Option<SocketAddr>,
network_key_file: Option<PathBuf>,
network_name: Option<String>,
firewall_mark: Option<u32>,
update_workers: usize,
topic_config: Option<PathBuf>,
cdn_cache: Option<PathBuf>,
}
#[derive(Debug, Deserialize, Default)]
struct MyceliumConfig {
#[serde(deserialize_with = "deserialize_optional_endpoint_str_from_toml")]
peers: Option<Vec<Endpoint>>,
tcp_listen_port: Option<u16>,
disable_quic: Option<bool>,
quic_listen_port: Option<u16>,
no_tun: Option<bool>,
tun_name: Option<String>,
disable_peer_discovery: Option<bool>,
peer_discovery_port: Option<u16>,
api_addr: Option<SocketAddr>,
jsonrpc_addr: Option<SocketAddr>,
metrics_api_address: Option<SocketAddr>,
network_name: Option<String>,
network_key_file: Option<PathBuf>,
firewall_mark: Option<u32>,
update_workers: Option<usize>,
topic_config: Option<PathBuf>,
cdn_cache: Option<PathBuf>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let cli = Cli::parse();
// Init default configuration
let mut mycelium_config = MyceliumConfig::default();
// Load configuration file
if let Some(config_file_path) = &cli.config_file {
if Path::new(config_file_path).exists() {
let config = config::Config::builder()
.add_source(config::File::new(
config_file_path.to_str().unwrap(),
config::FileFormat::Toml,
))
.build()?;
mycelium_config = config.try_deserialize()?;
} else {
let error_msg = format!("Config file {config_file_path:?} not found");
return Err(io::Error::new(io::ErrorKind::NotFound, error_msg).into());
}
} else if let Some(mut conf) = dirs::config_dir() {
// Windows: %APPDATA%/ThreeFold Tech/Mycelium/mycelium.conf
#[cfg(target_os = "windows")]
{
conf = conf
.join("ThreeFold Tech")
.join("Mycelium")
.join("mycelium.toml")
};
// Linux: $HOME/.config/mycelium/mycelium.conf
#[allow(clippy::unnecessary_operation)]
#[cfg(target_os = "linux")]
{
conf = conf.join("mycelium").join("mycelium.toml")
};
// MacOS: $HOME/Library/Application Support/ThreeFold Tech/Mycelium/mycelium.conf
#[cfg(target_os = "macos")]
{
conf = conf
.join("ThreeFold Tech")
.join("Mycelium")
.join("mycelium.toml")
};
if conf.exists() {
info!(
conf_dir = conf.to_str().unwrap(),
"Mycelium is starting with configuration file",
);
let config = config::Config::builder()
.add_source(config::File::new(
conf.to_str().unwrap(),
config::FileFormat::Toml,
))
.build()?;
mycelium_config = config.try_deserialize()?;
}
}
let level = if cli.silent {
tracing::Level::ERROR
} else if cli.debug {
tracing::Level::DEBUG
} else {
tracing::Level::INFO
};
tracing_subscriber::registry()
.with(
EnvFilter::builder()
.with_default_directive(level.into())
.from_env()
.expect("invalid RUST_LOG"),
)
.with(
(cli.logging_format == LoggingFormat::Compact)
.then(|| tracing_subscriber::fmt::Layer::new().compact()),
)
.with((cli.logging_format == LoggingFormat::Logfmt).then(tracing_logfmt::layer))
.with((cli.logging_format == LoggingFormat::Plain).then(|| {
tracing_logfmt::builder()
// Explicitly force color off
.with_ansi_color(false)
.layer()
}))
.init();
let key_path = cli
.key_file
.unwrap_or_else(|| PathBuf::from(DEFAULT_KEY_FILE));
match cli.command {
None => {
let merged_config = merge_config(cli.node_args, mycelium_config);
let topic_config = merged_config.topic_config.as_ref().and_then(|path| {
let mut content = String::new();
let mut file = std::fs::File::open(path).ok()?;
file.read_to_string(&mut content).ok()?;
toml::from_str::<TopicConfig>(&content).ok()
});
if topic_config.is_some() {
info!(path = ?merged_config.topic_config, "Loaded topic cofig");
}
let private_network_config =
match (merged_config.network_name, merged_config.network_key_file) {
(Some(network_name), Some(network_key_file)) => {
let net_key = load_key_file(&network_key_file).await?;
Some((network_name, net_key))
}
_ => None,
};
let node_keys = get_node_keys(&key_path).await?;
let node_secret_key = if let Some((node_secret_key, _)) = node_keys {
node_secret_key
} else {
warn!("Node key file {key_path:?} not found, generating new keys");
let secret_key = crypto::SecretKey::new();
save_key_file(&secret_key, &key_path).await?;
secret_key
};
let _api = if let Some(metrics_api_addr) = merged_config.metrics_api_address {
let metrics = mycelium_metrics::PrometheusExporter::new();
let config = mycelium::Config {
node_key: node_secret_key,
peers: merged_config.peers,
no_tun: merged_config.no_tun,
tcp_listen_port: merged_config.tcp_listen_port,
quic_listen_port: if merged_config.disable_quic {
None
} else {
Some(merged_config.quic_listen_port)
},
peer_discovery_port: if merged_config.disable_peer_discovery {
None
} else {
Some(merged_config.peer_discovery_port)
},
tun_name: merged_config.tun_name,
private_network_config,
metrics: metrics.clone(),
firewall_mark: merged_config.firewall_mark,
update_workers: merged_config.update_workers,
topic_config,
cdn_cache: merged_config.cdn_cache,
};
metrics.spawn(metrics_api_addr);
let node = Arc::new(Mutex::new(Node::new(config).await?));
let http_api = mycelium_api::Http::spawn(node.clone(), merged_config.api_addr);
// Initialize the JSON-RPC server
let rpc_api =
mycelium_api::rpc::JsonRpc::spawn(node, merged_config.jsonrpc_addr).await;
(http_api, rpc_api)
} else {
let config = mycelium::Config {
node_key: node_secret_key,
peers: merged_config.peers,
no_tun: merged_config.no_tun,
tcp_listen_port: merged_config.tcp_listen_port,
quic_listen_port: if merged_config.disable_quic {
None
} else {
Some(merged_config.quic_listen_port)
},
peer_discovery_port: if merged_config.disable_peer_discovery {
None
} else {
Some(merged_config.peer_discovery_port)
},
tun_name: merged_config.tun_name,
private_network_config,
metrics: mycelium_metrics::NoMetrics,
firewall_mark: merged_config.firewall_mark,
update_workers: merged_config.update_workers,
topic_config,
cdn_cache: merged_config.cdn_cache,
};
let node = Arc::new(Mutex::new(Node::new(config).await?));
let http_api = mycelium_api::Http::spawn(node.clone(), merged_config.api_addr);
// Initialize the JSON-RPC server
let rpc_api =
mycelium_api::rpc::JsonRpc::spawn(node, merged_config.jsonrpc_addr).await;
(http_api, rpc_api)
};
// TODO: put in dedicated file so we can only rely on certain signals on unix platforms
#[cfg(target_family = "unix")]
{
let mut sigint = signal::unix::signal(SignalKind::interrupt())
.expect("Can install SIGINT handler");
let mut sigterm = signal::unix::signal(SignalKind::terminate())
.expect("Can install SIGTERM handler");
tokio::select! {
_ = sigint.recv() => { }
_ = sigterm.recv() => { }
}
}
#[cfg(not(target_family = "unix"))]
{
if let Err(e) = tokio::signal::ctrl_c().await {
error!("Failed to wait for SIGINT: {e}");
}
}
}
Some(cmd) => match cmd {
Command::Inspect { json, key } => {
let node_keys = get_node_keys(&key_path).await?;
let key = if let Some(key) = key {
PublicKey::try_from(key.as_str())?
} else if let Some((_, node_pub_key)) = node_keys {
node_pub_key
} else {
error!("No key to inspect provided and no key found at {key_path:?}");
return Err(io::Error::new(
io::ErrorKind::NotFound,
"no key to inspect and key file not found",
)
.into());
};
mycelium_cli::inspect(key, json)?;
return Ok(());
}
Command::Message { command } => match command {
MessageCommand::Send {
wait,
timeout,
topic,
msg_path,
reply_to,
destination,
message,
} => {
return mycelium_cli::send_msg(
destination,
message,
wait,
timeout,
reply_to,
topic,
msg_path,
cli.node_args.api_addr,
)
.await
}
MessageCommand::Receive {
timeout,
topic,
msg_path,
raw,
} => {
return mycelium_cli::recv_msg(
timeout,
topic,
msg_path,
raw,
cli.node_args.api_addr,
)
.await
}
},
Command::Peers { command } => match command {
PeersCommand::List { json } => {
return mycelium_cli::list_peers(cli.node_args.api_addr, json).await;
}
PeersCommand::Add { peers } => {
return mycelium_cli::add_peers(cli.node_args.api_addr, peers).await;
}
PeersCommand::Remove { peers } => {
return mycelium_cli::remove_peers(cli.node_args.api_addr, peers).await;
}
},
Command::Routes { command } => match command {
RoutesCommand::Selected { json } => {
return mycelium_cli::list_selected_routes(cli.node_args.api_addr, json).await;
}
RoutesCommand::Fallback { json } => {
return mycelium_cli::list_fallback_routes(cli.node_args.api_addr, json).await;
}
RoutesCommand::Queried { json } => {
return mycelium_cli::list_queried_subnets(cli.node_args.api_addr, json).await;
}
RoutesCommand::NoRoute { json } => {
return mycelium_cli::list_no_route_entries(cli.node_args.api_addr, json).await;
}
},
},
}
Ok(())
}
async fn get_node_keys(
key_path: &PathBuf,
) -> Result<Option<(crypto::SecretKey, crypto::PublicKey)>, io::Error> {
if key_path.exists() {
let sk = load_key_file(key_path).await?;
let pk = crypto::PublicKey::from(&sk);
debug!("Loaded key file at {key_path:?}");
Ok(Some((sk, pk)))
} else {
Ok(None)
}
}
async fn load_key_file<T>(path: &Path) -> Result<T, io::Error>
where
T: From<[u8; 32]>,
{
let mut file = File::open(path).await?;
let mut secret_bytes = [0u8; 32];
file.read_exact(&mut secret_bytes).await?;
Ok(T::from(secret_bytes))
}
async fn save_key_file(key: &crypto::SecretKey, path: &Path) -> io::Result<()> {
#[cfg(target_family = "unix")]
{
use tokio::fs::OpenOptions;
let mut file = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.mode(0o600) // rw by the owner, not readable by group or others
.open(path)
.await?;
file.write_all(key.as_bytes()).await?;
}
#[cfg(not(target_family = "unix"))]
{
let mut file = File::create(path).await?;
file.write_all(key.as_bytes()).await?;
}
Ok(())
}
fn merge_config(cli_args: NodeArguments, file_config: MyceliumConfig) -> MergedNodeConfig {
MergedNodeConfig {
peers: if !cli_args.static_peers.is_empty() {
cli_args.static_peers
} else {
file_config.peers.unwrap_or_default()
},
tcp_listen_port: if cli_args.tcp_listen_port != DEFAULT_TCP_LISTEN_PORT {
cli_args.tcp_listen_port
} else {
file_config
.tcp_listen_port
.unwrap_or(DEFAULT_TCP_LISTEN_PORT)
},
disable_quic: cli_args.disable_quic || file_config.disable_quic.unwrap_or(false),
quic_listen_port: if cli_args.quic_listen_port != DEFAULT_QUIC_LISTEN_PORT {
cli_args.quic_listen_port
} else {
file_config
.quic_listen_port
.unwrap_or(DEFAULT_QUIC_LISTEN_PORT)
},
peer_discovery_port: if cli_args.peer_discovery_port != DEFAULT_PEER_DISCOVERY_PORT {
cli_args.peer_discovery_port
} else {
file_config
.peer_discovery_port
.unwrap_or(DEFAULT_PEER_DISCOVERY_PORT)
},
disable_peer_discovery: cli_args.disable_peer_discovery
|| file_config.disable_peer_discovery.unwrap_or(false),
api_addr: if cli_args.api_addr != DEFAULT_HTTP_API_SERVER_ADDRESS {
cli_args.api_addr
} else {
file_config
.api_addr
.unwrap_or(DEFAULT_HTTP_API_SERVER_ADDRESS)
},
jsonrpc_addr: if cli_args.jsonrpc_addr != DEFAULT_JSONRPC_API_SERVER_ADDRESS {
cli_args.jsonrpc_addr
} else {
file_config
.jsonrpc_addr
.unwrap_or(DEFAULT_JSONRPC_API_SERVER_ADDRESS)
},
no_tun: cli_args.no_tun || file_config.no_tun.unwrap_or(false),
tun_name: if let Some(tun_name_cli) = cli_args.tun_name {
tun_name_cli
} else if let Some(tun_name_config) = file_config.tun_name {
tun_name_config
} else {
TUN_NAME.to_string()
},
metrics_api_address: cli_args
.metrics_api_address
.or(file_config.metrics_api_address),
network_name: cli_args.network_name.or(file_config.network_name),
network_key_file: cli_args.network_key_file.or(file_config.network_key_file),
firewall_mark: cli_args.firewall_mark.or(file_config.firewall_mark),
update_workers: if cli_args.update_workers != 1 {
cli_args.update_workers
} else {
file_config.update_workers.unwrap_or(1)
},
topic_config: cli_args.topic_config.or(file_config.topic_config),
cdn_cache: cli_args.cdn_cache.or(file_config.cdn_cache),
}
}
/// Deserialize an optional list of endpoints from TOML format. The endpoints can be provided
/// either as a list `[...]`, or in case there is only 1 endpoint, it can also be provided as a
/// single string element. If no value is provided, it returns None.
fn deserialize_optional_endpoint_str_from_toml<'de, D>(
deserializer: D,
) -> Result<Option<Vec<Endpoint>>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrVec {
String(String),
Vec(Vec<String>),
}
Ok(match Option::<StringOrVec>::deserialize(deserializer)? {
Some(StringOrVec::Vec(v)) => Some(
v.into_iter()
.map(|s| {
<Endpoint as std::str::FromStr>::from_str(&s).map_err(serde::de::Error::custom)
})
.collect::<Result<Vec<_>, _>>()?,
),
Some(StringOrVec::String(s)) => Some(vec![
<Endpoint as std::str::FromStr>::from_str(&s).map_err(serde::de::Error::custom)?
]),
None => None,
})
}