Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions nmrs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
All notable changes to the `nmrs` crate will be documented in this file.

## [Unreleased]
### Changed
- Introduce `VpnConfig` trait and refactor `connect_vpn` signature ([#303](https://github.com/cachebag/nmrs/pull/303))

## [2.2.0] - 2026-03-17
### Added
Expand Down
24 changes: 11 additions & 13 deletions nmrs/examples/vpn_connect.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/// Connect to a WireGuard VPN using NetworkManager and print the assigned IP address.
///
/// This example demonstrates using the builder pattern for creating VPN credentials,
/// which provides a more ergonomic and readable API compared to the traditional constructor.
use nmrs::{NetworkManager, VpnCredentials, WireGuardPeer};
/// This example demonstrates creating a `WireGuardConfig`,
/// the preferred API for configuring VPN connections.
use nmrs::{NetworkManager, WireGuardConfig, WireGuardPeer};

#[tokio::main]
async fn main() -> nmrs::Result<()> {
Expand All @@ -16,16 +16,14 @@ async fn main() -> nmrs::Result<()> {
)
.with_persistent_keepalive(25);

// Use the builder pattern for a more readable configuration
let creds = VpnCredentials::builder()
.name("ExampleVPN")
.wireguard()
.gateway("vpn.example.com:51820")
.private_key(std::env::var("WG_PRIVATE_KEY").expect("Set WG_PRIVATE_KEY env var"))
.address("10.0.0.2/24")
.add_peer(peer)
.with_dns(vec!["1.1.1.1".into()])
.build();
let creds = WireGuardConfig::new(
"ExampleVPN",
"vpn.example.com:51820",
std::env::var("WG_PRIVATE_KEY").expect("Set WG_PRIVATE_KEY env var"),
"10.0.0.2/24",
vec![peer],
)
.with_dns(vec!["1.1.1.1".into()]);

println!("Connecting to VPN...");
nm.connect_vpn(creds).await?;
Expand Down
1 change: 1 addition & 0 deletions nmrs/src/api/builders/vpn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
//! let settings = build_wireguard_connection(&creds, &opts).unwrap();
//! // Pass settings to NetworkManager's AddAndActivateConnection
//! ```
#![allow(deprecated)]

use std::collections::HashMap;
use zvariant::Value;
Expand Down
89 changes: 89 additions & 0 deletions nmrs/src/api/models/tests.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(deprecated)]

use std::time::Duration;
use uuid::Uuid;

Expand Down Expand Up @@ -616,6 +618,93 @@ fn test_vpn_credentials_builder_basic() {
assert!(creds.mtu.is_none());
}

#[test]
fn test_wireguard_config_basic() {
let peer = WireGuardPeer::new(
"HIgo9xNzJMWLKAShlKl6/bUT1VI9Q0SDBXGtLXkPFXc=",
"vpn.example.com:51820",
vec!["0.0.0.0/0".into()],
);

let config = WireGuardConfig::new(
"TestVPN",
"vpn.example.com:51820",
"YBk6X3pP8KjKz7+HFWzVHNqL3qTZq8hX9VxFQJ4zVmM=",
"10.0.0.2/24",
vec![peer],
);

assert_eq!(config.name, "TestVPN");
assert_eq!(config.gateway, "vpn.example.com:51820");
assert_eq!(
config.private_key,
"YBk6X3pP8KjKz7+HFWzVHNqL3qTZq8hX9VxFQJ4zVmM="
);
assert_eq!(config.address, "10.0.0.2/24");
assert_eq!(config.peers.len(), 1);
assert!(config.dns.is_none());
assert!(config.mtu.is_none());
}

#[test]
fn test_wireguard_config_implements_vpn_config() {
let uuid = Uuid::new_v4();
let config = WireGuardConfig::new(
"TestVPN",
"vpn.example.com:51820",
"private_key",
"10.0.0.2/24",
vec![WireGuardPeer::new(
"public_key",
"vpn.example.com:51820",
vec!["0.0.0.0/0".into()],
)],
)
.with_dns(vec!["1.1.1.1".into(), "8.8.8.8".into()])
.with_mtu(1420)
.with_uuid(uuid);

let vpn_config: &dyn VpnConfig = &config;

assert_eq!(vpn_config.vpn_type(), VpnType::WireGuard);
assert_eq!(vpn_config.name(), "TestVPN");
assert_eq!(vpn_config.gateway(), "vpn.example.com:51820");
assert_eq!(
vpn_config.dns(),
Some(["1.1.1.1".to_string(), "8.8.8.8".to_string()].as_slice())
);
assert_eq!(vpn_config.mtu(), Some(1420));
assert_eq!(vpn_config.uuid(), Some(uuid));
}

#[test]
fn test_wireguard_config_roundtrips_through_vpn_credentials() {
let config = WireGuardConfig::new(
"TestVPN",
"vpn.example.com:51820",
"private_key",
"10.0.0.2/24",
vec![WireGuardPeer::new(
"public_key",
"vpn.example.com:51820",
vec!["0.0.0.0/0".into()],
)],
)
.with_dns(vec!["1.1.1.1".into()])
.with_mtu(1420);

let legacy: VpnCredentials = config.clone().into();
let roundtrip = WireGuardConfig::from(legacy);

assert_eq!(roundtrip.name, config.name);
assert_eq!(roundtrip.gateway, config.gateway);
assert_eq!(roundtrip.private_key, config.private_key);
assert_eq!(roundtrip.address, config.address);
assert_eq!(roundtrip.peers.len(), config.peers.len());
assert_eq!(roundtrip.dns, config.dns);
assert_eq!(roundtrip.mtu, config.mtu);
}

#[test]
fn test_vpn_credentials_builder_with_optionals() {
let peer = WireGuardPeer::new(
Expand Down
Loading
Loading