From 3cac99c24c8394081df74bb8f8685d9e39137c6e Mon Sep 17 00:00:00 2001 From: lucalise Date: Thu, 12 Feb 2026 22:21:07 -0800 Subject: [PATCH] feat: add config generation --- Cargo.lock | 105 ++++++++++++++++++++++++++++++++++++++++ client/Cargo.toml | 3 ++ client/src/config.rs | 38 +++++++++++++++ client/src/error.rs | 20 ++++++++ client/src/main.rs | 55 +++++++++++++++++---- client/src/wireguard.rs | 24 +++++++++ 6 files changed, 235 insertions(+), 10 deletions(-) create mode 100644 client/src/config.rs create mode 100644 client/src/wireguard.rs diff --git a/Cargo.lock b/Cargo.lock index 40872f9..c0e263a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -367,6 +367,7 @@ name = "client" version = "0.1.0" dependencies = [ "console", + "dirs", "futures", "futures-util", "registry", @@ -376,8 +377,10 @@ dependencies = [ "thiserror-ext", "tokio", "tokio-tungstenite", + "toml", "tracing", "tracing-subscriber", + "url", ] [[package]] @@ -528,6 +531,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -982,6 +1006,16 @@ version = "0.2.181" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "litemap" version = "0.8.1" @@ -1124,6 +1158,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "parking_lot" version = "0.12.5" @@ -1304,6 +1344,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.12.3" @@ -1532,6 +1583,15 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1817,6 +1877,45 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "1.0.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe30f93627849fa362d4a602212d41bb237dc2bd0f8ba0b2ce785012e124220" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.0.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.0.8+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + [[package]] name = "tracing" version = "0.1.44" @@ -2216,6 +2315,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/client/Cargo.toml b/client/Cargo.toml index 6b2adf6..bf61a2e 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -16,3 +16,6 @@ tokio-tungstenite = { version = "0.28.0", features = ["rustls-tls-native-roots"] tracing = "0.1.44" tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } registry = { path = "../registry" } +dirs = "6.0.0" +toml = "1.0.1" +url = "2.5.8" diff --git a/client/src/config.rs b/client/src/config.rs new file mode 100644 index 0000000..0cd09b9 --- /dev/null +++ b/client/src/config.rs @@ -0,0 +1,38 @@ +use std::path::PathBuf; + +use crate::error::{Error, Result}; +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct Config { + pub interface: InterfaceConfig, + pub server: ServerConfig, +} +#[derive(Deserialize)] +pub struct InterfaceConfig { + pub private_key: String, + pub listen_port: u16, + pub address: String, +} +#[derive(Deserialize)] +pub struct ServerConfig { + pub url: String, +} + +impl Config { + pub fn load() -> Result { + let path = base_path().join("config.toml"); + let bytes = std::fs::read(&path).map_err(|e| Error::read_config(e, &path))?; + Ok(toml::from_slice(&bytes)?) + } +} + +pub fn base_path() -> PathBuf { + dirs::config_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join("wg-mesh") +} + +pub fn wg_config_path() -> PathBuf { + PathBuf::from("/etc/wireguard/mesh0.conf") +} diff --git a/client/src/error.rs b/client/src/error.rs index 1d68dc7..29daf35 100644 --- a/client/src/error.rs +++ b/client/src/error.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use thiserror::Error; use thiserror_ext::{Box, Construct}; use tokio_tungstenite::tungstenite; @@ -15,6 +17,24 @@ pub enum ErrorKind { WsRead(#[source] tungstenite::Error), #[error("error deserializing json")] DeserializeJson(#[source] serde_json::Error), + #[error("error reading configuration at {path}")] + ReadConfig { + path: PathBuf, + #[source] + source: std::io::Error, + }, + #[error("error deserializing toml")] + DeserializeToml(#[from] toml::de::Error), + #[error("invalid url")] + Url(#[from] url::ParseError), + #[error("invalid url scheme: {url}")] + UrlScheme { url: String }, + #[error("error writing wireguard config to {path}")] + WriteConfig { + path: PathBuf, + #[source] + source: std::io::Error, + }, } pub type Result = core::result::Result; diff --git a/client/src/main.rs b/client/src/main.rs index cfe3e69..65993ee 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,33 +1,72 @@ +mod config; +mod error; +mod wireguard; + use console::style; use futures::StreamExt; -use registry::PeerMessage; +use registry::{Peer, PeerMessage}; use thiserror_ext::AsReport; use tokio_tungstenite::tungstenite::Message; use tracing::level_filters::LevelFilter; use tracing_subscriber::{ EnvFilter, fmt::format::FmtSpan, layer::SubscriberExt, util::SubscriberInitExt, }; +use url::Url; -use crate::error::Error; +use crate::{ + config::{Config, InterfaceConfig, wg_config_path}, + error::{Error, Result}, +}; -mod error; +fn parse_url(input: &str) -> Result { + let url = Url::parse(&input)?.join("/ws/peers")?; + if url.scheme() != "ws" && url.scheme() != "wss" { + return Err(Error::url_scheme(url.to_string())); + } + Ok(url.to_string()) +} + +fn write_wg_config(interface: &InterfaceConfig, peers: &[Peer]) -> Result<()> { + let path = wg_config_path(); + let config = wireguard::generate_config(interface, peers); + std::fs::write(&path, config).map_err(|e| Error::write_config(e, &path))?; + tracing::info!("wrote {} with {} peers", path.display(), peers.len()); + Ok(()) +} async fn run() -> crate::error::Result<()> { - let url = get_api_from_env(); + let config = Config::load()?; + let url = parse_url(&config.server.url)?; let (ws_stream, response) = tokio_tungstenite::connect_async(&url) .await .map_err(|e| Error::ws_connect(e, &url))?; let (_, mut read) = ws_stream.split(); tracing::info!("connected, response: {:?}", response.status()); + let mut peers: Vec = Vec::new(); + while let Some(msg) = read.next().await { match msg.map_err(|e| Error::ws_read(e))? { Message::Text(text) => { let server_msg: PeerMessage = serde_json::from_str(&text).map_err(|e| Error::deserialize_json(e))?; match server_msg { - PeerMessage::HydratePeers { peers } => {} - PeerMessage::PeerUpdate { peer } => {} + PeerMessage::HydratePeers { peers: new_peers } => { + tracing::info!("received {} peers", new_peers.len()); + peers = new_peers; + write_wg_config(&config.interface, &peers)?; + } + PeerMessage::PeerUpdate { peer } => { + tracing::info!("peer update: {}", peer.public_key); + if let Some(existing) = + peers.iter_mut().find(|p| p.public_key == peer.public_key) + { + *existing = peer; + } else { + peers.push(peer); + } + write_wg_config(&config.interface, &peers)?; + } } } _ => {} @@ -57,7 +96,3 @@ async fn main() { std::process::exit(1) } } - -fn get_api_from_env() -> String { - return "ws://localhost:8080/ws/peers".to_string(); -} diff --git a/client/src/wireguard.rs b/client/src/wireguard.rs new file mode 100644 index 0000000..2822a06 --- /dev/null +++ b/client/src/wireguard.rs @@ -0,0 +1,24 @@ +use registry::Peer; + +use crate::config::InterfaceConfig; + +pub fn generate_config(interface: &InterfaceConfig, peers: &[Peer]) -> String { + let mut config = format!( + "[Interface]\nPrivateKey = {}\nListenPort = {}\nAddress = {}\n", + interface.private_key, interface.listen_port, interface.address, + ); + for peer in peers { + config.push_str(&format!( + "\n[Peer]\nPublicKey = {}\nEndpoint = {}:{}\nAllowedIPs = {}\n", + peer.public_key, + peer.public_ip, + peer.port, + peer.allowed_ips + .iter() + .map(|ip| ip.to_string()) + .collect::>() + .join(", "), + )); + } + config +}