#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum DaemonControlRequest {
Stop,
Reload,
Pause,
Resume,
}
impl DaemonControlRequest {
fn as_str(self) -> &'static str {
match self {
Self::Stop => DAEMON_CONTROL_STOP_REQUEST,
Self::Reload => DAEMON_CONTROL_RELOAD_REQUEST,
Self::Pause => DAEMON_CONTROL_PAUSE_REQUEST,
Self::Resume => DAEMON_CONTROL_RESUME_REQUEST,
}
}
fn parse(value: &str) -> Option<Self> {
let normalized = value.trim().to_ascii_lowercase();
match normalized.as_str() {
DAEMON_CONTROL_STOP_REQUEST => Some(Self::Stop),
DAEMON_CONTROL_RELOAD_REQUEST => Some(Self::Reload),
DAEMON_CONTROL_PAUSE_REQUEST => Some(Self::Pause),
DAEMON_CONTROL_RESUME_REQUEST => Some(Self::Resume),
_ => None,
}
}
}
#[derive(Debug, Parser)]
#[command(name = "nvpn")]
#[command(version)]
#[command(about = "FIPS private mesh VPN")]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
#[allow(clippy::large_enum_variant)]
enum Command {
Init {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
force: bool,
#[arg(long = "device", alias = "participant")]
devices: Vec<String>,
},
Version(VersionArgs),
Update(UpdateArgs),
InstallCli(InstallCliArgs),
UninstallCli(UninstallCliArgs),
Service(ServiceArgs),
Start(StartArgs),
Stop(StopArgs),
RepairNetwork(RepairNetworkArgs),
Reload(ReloadArgs),
Pause(ControlArgs),
Resume(ControlArgs),
Connect(ConnectArgs),
#[command(name = "join-request")]
JoinRequest(JoinRequestArgs),
#[command(name = "join-manual", alias = "manual-join")]
JoinManual(ManualJoinArgs),
Status(StatusArgs),
Set(SetArgs),
#[command(alias = "add-participant")]
AddDevice(UpdateRosterArgs),
#[command(alias = "remove-participant")]
RemoveDevice(UpdateRosterArgs),
AddAdmin(UpdateRosterArgs),
RemoveAdmin(UpdateRosterArgs),
Ping(PingArgs),
Doctor(DoctorArgs),
Ip(IpArgs),
Whois(WhoisArgs),
Pubsub(PubsubArgs),
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
WgUpstreamTest(WgUpstreamTestArgs),
#[cfg(feature = "paid-exit")]
#[command(name = "paid-exit")]
PaidExit(PaidExitArgs),
#[command(hide = true)]
ApplyConfig(ApplyConfigArgs),
#[command(hide = true)]
ApplyConfigDaemon(ApplyConfigArgs),
#[command(hide = true)]
Daemon(DaemonArgs),
}
#[derive(Debug, Clone, Args)]
struct JoinRequestArgs {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
no_wait: bool,
#[arg(long)]
no_qr: bool,
#[arg(long)]
reset: bool,
}
#[derive(Debug, Clone, Args)]
struct ManualJoinArgs {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long = "admin-device-id", alias = "admin")]
admin_device_id: String,
#[arg(long = "network-id")]
network_id: String,
#[arg(long)]
json: bool,
}
#[cfg(any(target_os = "linux", target_os = "macos", target_os = "windows"))]
#[derive(Debug, Clone, Args)]
struct WgUpstreamTestArgs {
#[arg(long, required_unless_present = "self_test")]
config_file: Option<PathBuf>,
#[arg(long, default_value_t = false)]
self_test: bool,
#[arg(long, default_value_t = 30)]
timeout_secs: u64,
#[arg(long, conflicts_with = "replace_default")]
scoped_host: Option<std::net::IpAddr>,
#[arg(long, default_value_t = false)]
replace_default: bool,
#[arg(long)]
probe_target: Option<std::net::IpAddr>,
#[arg(long, default_value_t = 5)]
ping_count: u8,
#[arg(long, default_value_t = 0)]
hold_secs: u64,
#[cfg(any(target_os = "linux", target_os = "macos"))]
#[arg(long)]
tun_name: Option<String>,
}
#[derive(Debug, Args)]
struct InstallCliArgs {
#[arg(long)]
path: Option<PathBuf>,
#[arg(long)]
force: bool,
}
#[derive(Debug, Args)]
struct VersionArgs {
#[arg(long)]
json: bool,
#[arg(long)]
verbose: bool,
}
#[derive(Debug, Args)]
struct UpdateArgs {
#[arg(long)]
check: bool,
#[arg(long)]
app: bool,
#[arg(long)]
download_only: bool,
#[arg(long)]
download_dir: Option<PathBuf>,
#[arg(long)]
json: bool,
#[arg(long)]
path: Option<PathBuf>,
#[arg(long)]
force: bool,
#[arg(long, value_enum, default_value = "auto")]
source: UpdateSource,
}
#[derive(Debug, Clone, Copy, ValueEnum)]
enum UpdateSource {
Auto,
Github,
#[value(alias = "htree")]
Hashtree,
}
#[derive(Debug, Args)]
struct UninstallCliArgs {
#[arg(long)]
path: Option<PathBuf>,
}
#[derive(Debug, Args)]
struct ServiceArgs {
#[command(subcommand)]
command: ServiceCommand,
}
#[derive(Debug, Subcommand)]
enum ServiceCommand {
Install(ServiceInstallArgs),
Enable(ServiceControlArgs),
Disable(ServiceControlArgs),
Uninstall(ServiceUninstallArgs),
Status(ServiceStatusArgs),
}
#[derive(Debug, Args)]
struct ServiceInstallArgs {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long, default_value_t = default_tunnel_iface())]
iface: String,
#[arg(long, alias = "announce-interval-secs", default_value_t = 60)]
mesh_refresh_interval_secs: u64,
#[arg(long)]
force: bool,
}
#[derive(Debug, Args)]
struct ServiceUninstallArgs {
#[arg(long)]
config: Option<PathBuf>,
}
#[derive(Debug, Args)]
struct ServiceStatusArgs {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
json: bool,
#[arg(long, hide = true)]
skip_binary_version: bool,
}
#[derive(Debug, Args)]
struct ServiceControlArgs {
#[arg(long)]
config: Option<PathBuf>,
}
#[derive(Debug, Args)]
struct ConnectArgs {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
network_id: Option<String>,
#[arg(long = "device", alias = "participant")]
devices: Vec<String>,
#[arg(long, default_value_t = default_tunnel_iface())]
iface: String,
#[arg(long, alias = "announce-interval-secs", default_value_t = 60)]
mesh_refresh_interval_secs: u64,
}
#[derive(Debug, Args, Clone)]
struct DaemonArgs {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
network_id: Option<String>,
#[arg(long = "device", alias = "participant")]
devices: Vec<String>,
#[arg(long, default_value_t = default_tunnel_iface())]
iface: String,
#[arg(long, alias = "announce-interval-secs", default_value_t = 60)]
mesh_refresh_interval_secs: u64,
#[arg(long, requires = "fips_ethernet_discovery_scope")]
fips_ethernet_interface: Option<String>,
#[arg(long, requires = "fips_ethernet_interface")]
fips_ethernet_discovery_scope: Option<String>,
#[arg(long = "fips-websocket-seed-url", value_parser = parse_fips_websocket_seed_url)]
fips_websocket_seed_urls: Vec<String>,
#[arg(long)]
fips_websocket_bind: Option<String>,
#[arg(long, requires = "fips_websocket_bind", value_parser = parse_fips_websocket_public_url)]
fips_websocket_public_url: Option<String>,
#[arg(long, hide = true, default_value_t = false)]
paused: bool,
#[arg(long, hide = true, default_value_t = false)]
service: bool,
}
fn parse_fips_websocket_seed_url(value: &str) -> Result<String, String> {
let value = value.trim().to_string();
fips_core::config::WebSocketConfig {
seed_urls: vec![value.clone()],
..Default::default()
}
.validate()?;
Ok(value)
}
fn parse_fips_websocket_public_url(value: &str) -> Result<String, String> {
let value = value.trim().to_string();
fips_core::config::WebSocketConfig {
bind_addr: Some("127.0.0.1:1".into()),
public_url: Some(value.clone()),
..Default::default()
}
.validate()?;
Ok(value)
}
#[derive(Debug, Args)]
struct StartArgs {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
network_id: Option<String>,
#[arg(long = "device", alias = "participant")]
devices: Vec<String>,
#[arg(long, default_value_t = default_tunnel_iface())]
iface: String,
#[arg(long, alias = "announce-interval-secs", default_value_t = 60)]
mesh_refresh_interval_secs: u64,
#[arg(long)]
daemon: bool,
#[arg(long, conflicts_with = "no_connect")]
connect: bool,
#[arg(long, conflicts_with = "connect")]
no_connect: bool,
}
#[derive(Debug, Args)]
struct StopArgs {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long, default_value_t = 5)]
timeout_secs: u64,
#[arg(long)]
force: bool,
}
#[derive(Debug, Args)]
struct RepairNetworkArgs {
#[arg(long)]
config: Option<PathBuf>,
}
#[derive(Debug, Args)]
struct ReloadArgs {
#[arg(long)]
config: Option<PathBuf>,
}
#[derive(Debug, Args)]
struct ControlArgs {
#[arg(long)]
config: Option<PathBuf>,
}
#[derive(Debug, Args)]
struct StatusArgs {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
network_id: Option<String>,
#[arg(long = "device", alias = "participant")]
devices: Vec<String>,
#[arg(long, hide = true, default_value_t = 2)]
discover_secs: u64,
#[arg(long)]
json: bool,
#[arg(long, hide = true)]
include_join_request: bool,
}
#[derive(Debug, Args)]
struct SetArgs {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
network_id: Option<String>,
#[arg(long)]
node_name: Option<String>,
#[arg(long)]
node_id: Option<String>,
#[arg(long)]
endpoint: Option<String>,
#[arg(long)]
tunnel_ip: Option<String>,
#[arg(long)]
listen_port: Option<u16>,
#[arg(long = "device", alias = "participant")]
devices: Vec<String>,
#[arg(long)]
exit_node: Option<String>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
exit_node_leak_protection: Option<bool>,
#[arg(long)]
advertise_routes: Option<String>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
advertise_exit_node: Option<bool>,
#[cfg(feature = "paid-exit")]
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
paid_exit_enabled: Option<bool>,
#[cfg(feature = "paid-exit")]
#[arg(long)]
paid_exit_upstream: Option<String>,
#[cfg(feature = "paid-exit")]
#[arg(long)]
paid_exit_price_msat: Option<u64>,
#[cfg(feature = "paid-exit")]
#[arg(long, value_name = "UNITS")]
paid_exit_per_units: Option<String>,
#[cfg(feature = "paid-exit")]
#[arg(long)]
paid_exit_connection_minimum_msat_per_day: Option<u64>,
#[cfg(feature = "paid-exit")]
#[arg(long)]
paid_exit_accepted_mints: Option<String>,
#[cfg(feature = "paid-exit")]
#[arg(long)]
paid_exit_country_code: Option<String>,
#[cfg(feature = "paid-exit")]
#[arg(long)]
paid_exit_region: Option<String>,
#[cfg(feature = "paid-exit")]
#[arg(long)]
paid_exit_asn: Option<u32>,
#[cfg(feature = "paid-exit")]
#[arg(long)]
paid_exit_network_class: Option<String>,
#[cfg(feature = "paid-exit")]
#[arg(long)]
paid_exit_ipv4: Option<bool>,
#[cfg(feature = "paid-exit")]
#[arg(long)]
paid_exit_ipv6: Option<bool>,
#[cfg(feature = "paid-exit")]
#[arg(long)]
paid_exit_max_channel_capacity_sat: Option<u64>,
#[cfg(feature = "paid-exit")]
#[arg(long)]
paid_exit_channel_expiry_secs: Option<u64>,
#[cfg(feature = "paid-exit")]
#[arg(long, value_name = "UNITS")]
paid_exit_free_probe_units: Option<String>,
#[cfg(feature = "paid-exit")]
#[arg(long, value_name = "UNITS")]
paid_exit_grace_units: Option<String>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
wireguard_exit_enabled: Option<bool>,
#[arg(long)]
wireguard_exit_interface: Option<String>,
#[arg(long)]
wireguard_exit_address: Option<String>,
#[arg(long)]
wireguard_exit_private_key: Option<String>,
#[arg(long)]
wireguard_exit_peer_public_key: Option<String>,
#[arg(long)]
wireguard_exit_peer_preshared_key: Option<String>,
#[arg(long)]
wireguard_exit_endpoint: Option<String>,
#[arg(long)]
wireguard_exit_allowed_ips: Option<String>,
#[arg(long)]
wireguard_exit_dns: Option<String>,
#[arg(long)]
wireguard_exit_mtu: Option<u16>,
#[arg(long)]
wireguard_exit_keepalive: Option<u16>,
#[arg(long)]
wireguard_exit_config: Option<String>,
#[arg(long)]
wireguard_exit_config_file: Option<PathBuf>,
#[arg(long)]
exit_dns_mode: Option<String>,
#[arg(long)]
exit_dns_doh_provider: Option<String>,
#[arg(long)]
exit_dns_custom_doh_url: Option<String>,
#[arg(long)]
exit_dns_custom_doh_bootstrap_ips: Option<String>,
#[arg(long)]
exit_dns_through_exit_servers: Option<String>,
#[arg(long)]
autoconnect: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
join_requests_enabled: Option<bool>,
#[arg(
long = "fips-advertise-public-endpoint",
alias = "fips-advertise-endpoint",
num_args = 0..=1,
default_missing_value = "true"
)]
fips_advertise_public_endpoint: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
fips_host_tunnel_enabled: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
connect_to_non_roster_fips_peers: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
fips_nostr_discovery_enabled: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
lan_discovery_enabled: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
fips_webrtc_enabled: Option<bool>,
#[arg(long, num_args = 0..=1, default_missing_value = "true")]
fips_bootstrap_enabled: Option<bool>,
#[arg(long)]
fips_host_inbound_tcp_ports: Option<String>,
#[arg(long = "fips-peer-endpoint")]
fips_peer_endpoints: Vec<String>,
#[arg(long)]
json: bool,
}
#[derive(Debug, Args)]
struct UpdateRosterArgs {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
network_id: Option<String>,
#[arg(long = "device", alias = "participant", required = true)]
devices: Vec<String>,
#[arg(long)]
publish: bool,
#[arg(long)]
json: bool,
}
#[derive(Debug, Args)]
struct PingArgs {
target: String,
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
network_id: Option<String>,
#[arg(long = "device", alias = "participant")]
devices: Vec<String>,
#[arg(long, hide = true, default_value_t = 2)]
discover_secs: u64,
#[arg(long, default_value_t = 3)]
count: u32,
#[arg(long, default_value_t = 2)]
timeout_secs: u64,
}
#[derive(Debug, Args)]
struct DoctorArgs {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
network_id: Option<String>,
#[arg(long = "device", alias = "participant")]
devices: Vec<String>,
#[arg(long, default_value_t = 4)]
timeout_secs: u64,
#[arg(long)]
json: bool,
#[arg(long)]
write_bundle: Option<PathBuf>,
}
#[derive(Debug, Args)]
struct IpArgs {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
network_id: Option<String>,
#[arg(long = "device", alias = "participant")]
devices: Vec<String>,
#[arg(long, hide = true, default_value_t = 2)]
discover_secs: u64,
#[arg(long)]
peer: bool,
#[arg(long)]
json: bool,
}
#[derive(Debug, Args)]
struct WhoisArgs {
query: String,
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
network_id: Option<String>,
#[arg(long = "device", alias = "participant")]
devices: Vec<String>,
#[arg(long, hide = true, default_value_t = 2)]
discover_secs: u64,
#[arg(long)]
json: bool,
}
#[derive(Debug, Args)]
struct PubsubArgs {
#[command(subcommand)]
command: PubsubCommand,
}
#[derive(Debug, Subcommand)]
enum PubsubCommand {
Publish(PubsubPublishArgs),
}
#[derive(Debug, Args)]
struct PubsubPublishArgs {
#[arg(long)]
event: PathBuf,
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
json: bool,
}
#[derive(Debug, Args)]
struct ApplyConfigArgs {
#[arg(long)]
source: PathBuf,
#[arg(long)]
config: Option<PathBuf>,
}