#[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),
Status(StatusArgs),
Set(SetArgs),
CreateInvite(CreateInviteArgs),
ImportInvite(ImportInviteArgs),
InviteBroadcast(InviteBroadcastArgs),
Discover(DiscoverArgs),
#[command(alias = "add-participant")]
AddDevice(UpdateRosterArgs),
#[command(alias = "remove-participant")]
RemoveDevice(UpdateRosterArgs),
AddAdmin(UpdateRosterArgs),
RemoveAdmin(UpdateRosterArgs),
Ping(PingArgs),
Doctor(DoctorArgs),
Ip(IpArgs),
Whois(WhoisArgs),
#[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),
}
#[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,
}
#[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 = 20)]
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 = 20)]
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 = 20)]
mesh_refresh_interval_secs: u64,
#[arg(long, hide = true, default_value_t = false)]
paused: bool,
#[arg(long, hide = true, default_value_t = false)]
service: bool,
}
#[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 = 20)]
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,
}
#[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_meter: Option<String>,
#[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_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)]
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")]
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 CreateInviteArgs {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long)]
json: bool,
}
#[derive(Debug, Args)]
struct ImportInviteArgs {
#[arg(long)]
config: Option<PathBuf>,
invite: String,
#[arg(long)]
json: bool,
}
#[derive(Debug, Args)]
struct InviteBroadcastArgs {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long, value_name = "SECS")]
duration_secs: Option<u64>,
}
#[derive(Debug, Args)]
struct DiscoverArgs {
#[arg(long)]
config: Option<PathBuf>,
#[arg(long, value_name = "SECS")]
duration_secs: Option<u64>,
#[arg(long)]
accept: bool,
#[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 ApplyConfigArgs {
#[arg(long)]
source: PathBuf,
#[arg(long)]
config: Option<PathBuf>,
}