From 0ad15c579cf3239680a6f5e17250fefea6c0caca Mon Sep 17 00:00:00 2001 From: JKearnsl Date: Thu, 22 May 2025 13:05:30 +0300 Subject: [PATCH 1/3] colorize errors --- Cargo.toml | 5 ++ crates/server/Cargo.toml | 3 +- crates/server/src/bin/command/start.rs | 12 ++-- crates/server/src/bin/command/users.rs | 3 +- crates/server/src/bin/command/users/add.rs | 27 +++++--- crates/server/src/bin/command/users/remove.rs | 3 +- crates/server/src/bin/mod.rs | 24 +++++-- crates/server/src/bin/opt.rs | 5 -- crates/server/src/bin/style.rs | 69 +++++++++++++++++++ crates/shared/Cargo.toml | 6 +- crates/shared/src/connection_config.rs | 28 +++++--- 11 files changed, 143 insertions(+), 42 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 71c4da1..207e30e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ socket2 = "0.5.9" tokio-tungstenite = "0.26.2" futures = "0.3.31" ctrlc = "3.4" +rand_core = "=0.6.4" # data chrono = { version = "0.4", features = ["serde"] } @@ -28,6 +29,10 @@ serde = { version = "1.0", features = ["derive"] } anyhow = "1.0.95" etherparse = "0.18" bincode = "2.0.1" +toml = "0.8.22" + +# crypto +x25519-dalek = "2.0.1" # logging tracing = "0.1" diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index c5cc053..4e2814f 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -31,7 +31,7 @@ thiserror = "2.0.12" dashmap = "7.0.0-rc2" bincode = { workspace = true } derive_more = { version = "2.0.1", features = ["display"] } -toml = "0.8.19" +toml = { workspace = true } # logging tracing-subscriber = { workspace = true } @@ -48,6 +48,7 @@ tokio = { workspace = true } socket2 = { workspace = true, optional = true } async-trait = "0.1" rand = "0.9.1" +qrcode = { version = "0.14.1", default-features = false, features = ["pic"] } tokio-tungstenite = { workspace = true, optional = true } futures = { workspace = true, optional = true } diff --git a/crates/server/src/bin/command/start.rs b/crates/server/src/bin/command/start.rs index a5bbc78..fd0c971 100644 --- a/crates/server/src/bin/command/start.rs +++ b/crates/server/src/bin/command/start.rs @@ -1,10 +1,11 @@ use std::process; use clap::Parser; -use tracing::{debug, error, info, warn}; +use tracing::{debug, error, info}; use server::config::Config; use server::runtime::error::RuntimeError; use server::runtime::Runtime; use crate::storage::{database, Clients}; +use crate::{success_err, success_warn}; #[derive(Debug, Parser)] pub struct StartCmd { @@ -34,19 +35,19 @@ impl StartCmd { } if let Err(err) = config.save() { - warn!("cant update configuration: {}", err); + success_warn!("cant update configuration: {}", err); } let clients = match database(&config.general.storage) { Ok(db) => match Clients::new(db) { Ok(store) => store, Err(err) => { - error!("failed to create client storage: {}", err); + success_err!("failed to create client storage: {}\n", err); process::exit(1); } }, Err(err) => { - error!("load storage: {}", err); + success_err!("load storage: {}\n", err); process::exit(1); } }; @@ -54,7 +55,7 @@ impl StartCmd { let mut runtime = match Runtime::from_config(config) { Ok(runtime) => runtime, Err(err) => { - error!("create runtime: {}", err); + success_err!("create runtime: {}\n", err); process::exit(1); } }; @@ -91,6 +92,5 @@ impl StartCmd { } } } - } } \ No newline at end of file diff --git a/crates/server/src/bin/command/users.rs b/crates/server/src/bin/command/users.rs index eec1cea..4b41564 100644 --- a/crates/server/src/bin/command/users.rs +++ b/crates/server/src/bin/command/users.rs @@ -7,6 +7,7 @@ use server::config::Config; use crate::command::users::add::AddCmd; use crate::command::users::list::ListCmd; use crate::command::users::remove::RemoveCmd; +use crate::success_err; #[derive(Debug, Subcommand)] pub enum UsersCmd { @@ -25,7 +26,7 @@ impl UsersCmd { UsersCmd::List(cmd) => cmd.exec(config).await, UsersCmd::Remove(cmd) => cmd.exec(config).await } { - eprintln!("{}", error); + success_err!("{}\n", error); std::process::exit(1); } } diff --git a/crates/server/src/bin/command/users/add.rs b/crates/server/src/bin/command/users/add.rs index 0b872fe..a1801cc 100644 --- a/crates/server/src/bin/command/users/add.rs +++ b/crates/server/src/bin/command/users/add.rs @@ -7,7 +7,11 @@ use shared::connection_config::{ConnectionConfig, CredentialsConfig, GeneralConf use shared::keys::handshake::{PublicKey, SecretKey}; use shared::session::Alg; use crate::storage::{database, Client, Clients}; -use crate::style::format_opaque_bytes; +use crate::style::{format_opaque_bytes, generate_qrcode}; +use std::fmt::Write; +use qrcode::{EcLevel, QrCode, Version}; +use qrcode::render::unicode; +use crate::success_ok; #[derive(Debug, Parser)] pub struct AddCmd { @@ -62,11 +66,11 @@ impl AddCmd { None => SecretKey::generate_x25519() }; - println!("\nClient has been successfully created!"); - - println!("\nPublicKey {}", pk); - println!("PrivateKey {}", format_opaque_bytes(sk.as_slice())); - println!("PreSharedKey {}", format_opaque_bytes(psk.as_slice())); + println!(); + success_ok!("PubKey", pk); + success_ok!("PrivKey", format_opaque_bytes(sk.as_slice())); + success_ok!("SharedKey", format_opaque_bytes(psk.as_slice())); + println!(); let clients = Clients::new(database(&config.general.storage)?)?; clients.save(Client { @@ -90,8 +94,6 @@ impl AddCmd { runtime: None, }; - println!("\nConnection key\n{}", connection_config.to_base64()?); - let config_path = PathBuf::from(format!( "connection-{}.toml", chrono::Utc::now().format("%Y-%m-%d_%H-%M-%S") @@ -101,7 +103,14 @@ impl AddCmd { anyhow::anyhow!("failed to save connection config: {}", error) })?; - println!("\nConnection config saved as {}\n", config_path.display()); + match generate_qrcode((connection_config.to_base64()?).as_ref()) { + Ok(qr) => println!("{}\n", qr), + Err(err) => panic!("failed to generate qrcode: {}", err) + } + + success_ok!("Saved", "config to {}", config_path.display()); + success_ok!("Generated", "key: {}\n", connection_config.to_base64()?); + Ok(()) } } \ No newline at end of file diff --git a/crates/server/src/bin/command/users/remove.rs b/crates/server/src/bin/command/users/remove.rs index cac273e..c604a5b 100644 --- a/crates/server/src/bin/command/users/remove.rs +++ b/crates/server/src/bin/command/users/remove.rs @@ -2,6 +2,7 @@ use crate::storage::{database, Clients}; use clap::Parser; use server::config::Config; use shared::keys::handshake::PublicKey; +use crate::success_ok; #[derive(Debug, Parser)] pub struct RemoveCmd { @@ -18,7 +19,7 @@ impl RemoveCmd { let clients = Clients::new(database(&config.general.storage)?)?; clients.delete(&pk).await?; - println!("Client has been successfully removed"); + success_ok!("Client has been successfully removed"); Ok(()) } } \ No newline at end of file diff --git a/crates/server/src/bin/mod.rs b/crates/server/src/bin/mod.rs index f881de5..ac232cb 100644 --- a/crates/server/src/bin/mod.rs +++ b/crates/server/src/bin/mod.rs @@ -9,24 +9,34 @@ use crate::command::Commands; use crate::opt::Opt; use clap::Parser; -const CONFIG_PATH_ENV: &str = "HOLYNET_VPN_SERVER_CONFIG"; +const CONFIG_PATH_ENV: &str = "HOLYNET_CONFIG"; const LOG_DIR: &str = "logs"; const LOG_PREFIX: &str = "server.log"; #[tokio::main(flavor = "multi_thread")] -async fn main() -> anyhow::Result<()> { +async fn main() { let mut opt = Opt::parse(); - let _guard = opt.init_logging()?; + let _guard = match opt.init_logging() { + Ok(guard) => guard, + Err(err) => { + success_err!("{}\n", err); + std::process::exit(1); + } + }; inquire::set_global_render_config(render_config()); - let config = opt.load_config(true)?; + let config = match opt.load_config(true) { + Ok(config) => config, + Err(err) => { + success_err!("load config: {}\n", err); + std::process::exit(1); + } + }; match opt.cmd { Commands::Start(cmd) => cmd.exec(config).await, Commands::Users(cmd) => cmd.exec(config).await, Commands::Monitor => unimplemented!("Monitor command is not implemented"), Commands::Logs => unimplemented!("Logs command is not implemented"), - } - - Ok(()) + } } diff --git a/crates/server/src/bin/opt.rs b/crates/server/src/bin/opt.rs index b2544d4..391415b 100644 --- a/crates/server/src/bin/opt.rs +++ b/crates/server/src/bin/opt.rs @@ -33,11 +33,6 @@ pub struct Opt { impl Opt { pub fn init_logging(&mut self) -> anyhow::Result { - let log_dir_path = Path::new(LOG_DIR); - if !Path::new(LOG_DIR).exists() { - fs::create_dir_all(log_dir_path).expect("Failed to create log directory"); - } - let appender = RollingFileAppender::builder() .rotation(Rotation::DAILY) .filename_prefix(LOG_PREFIX) diff --git a/crates/server/src/bin/style.rs b/crates/server/src/bin/style.rs index f66c8b2..48e0229 100644 --- a/crates/server/src/bin/style.rs +++ b/crates/server/src/bin/style.rs @@ -1,5 +1,7 @@ use std::io::Read; use inquire::ui::{Attributes, Color, RenderConfig, StyleSheet, Styled}; +use qrcode::{EcLevel, QrCode, Version}; +use qrcode::render::unicode; pub fn styles() -> clap::builder::Styles { clap::builder::Styles::styled() @@ -94,3 +96,70 @@ pub fn format_opaque_bytes(bytes: &[u8]) -> String { .collect() } } + +#[macro_export] +macro_rules! success_ok { + ($message:expr) => { + success_ok!("OK", $message) + }; + ($level:expr, $message:expr) => { + println!( + "{}{:>12}{} {}", + anstyle::Style::new() + .bold() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Green))), + $level, + anstyle::Reset.render(), + $message + ) + }; + ($level:expr, $message:expr, $($arg:tt)*) => { + success_ok!($level, format!($message, $($arg)*)) + }; +} + +#[macro_export] +macro_rules! success_err { + ($message:expr) => { + eprintln!( + "{}error:{} {}", + anstyle::Style::new() + .bold() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red))), + anstyle::Reset.render(), + $message + ) + }; + ($message:expr, $($arg:tt)*) => { + success_err!(format!($message, $($arg)*)) + }; +} + +#[macro_export] +macro_rules! success_warn { + ($message:expr) => { + eprintln!( + "{}warning:{} {}", + anstyle::Style::new() + .bold() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Yellow))), + anstyle::Reset.render(), + $message + ) + }; + ($message:expr, $($arg:tt)*) => { + success_warn!(format!($message, $($arg)*)) + }; +} + + +pub fn generate_qrcode(data: &[u8]) -> anyhow::Result { + let code = QrCode::with_version(data, Version::Normal(7), EcLevel::L)?; + let string = code.render::() + .dark_color(unicode::Dense1x2::Dark) + .light_color(unicode::Dense1x2::Light) + .module_dimensions(1, 1) + .build(); + + Ok(string) +} \ No newline at end of file diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index 7d57dd8..ba5574d 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -9,10 +9,10 @@ snow = { workspace = true } serde = { workspace = true } tun-rs = { workspace = true } base64 = "0.22.1" -toml = "0.8.19" +toml = { workspace = true } bincode = { workspace = true, features = ["serde"]} -rand_core = "0.6.4" +rand_core = { workspace = true } lazy_static = "1.5.0" anyhow = { workspace = true } -x25519-dalek = { version = "2.0.1", features = ["static_secrets"] } +x25519-dalek = { workspace = true, features = ["static_secrets"] } once_cell = "1.21.3" \ No newline at end of file diff --git a/crates/shared/src/connection_config.rs b/crates/shared/src/connection_config.rs index 6f936a9..b9d6a34 100644 --- a/crates/shared/src/connection_config.rs +++ b/crates/shared/src/connection_config.rs @@ -62,22 +62,32 @@ impl ConnectionConfig { let config = toml::to_string(self)?; std::fs::write(path, &config).map_err(anyhow::Error::from) } - - pub fn from_base64(base64: &str) -> anyhow::Result { - let bytes = STANDARD_NO_PAD.decode(base64)?; + + pub fn to_bytes(&self) -> Vec { + match bincode::serde::encode_to_vec( + self, + bincode::config::standard() + ) { + Ok(bytes) => bytes, + Err(err) => panic!("failed to serialize connection config: {}", err) + } + } + + pub fn from_bytes(bytes: &[u8]) -> anyhow::Result { let (obj, _) = bincode::serde::decode_from_slice( - &bytes, + bytes, bincode::config::standard() ).map_err(anyhow::Error::from)?; Ok(obj) } + + pub fn from_base64(base64: &str) -> anyhow::Result { + let bytes = STANDARD_NO_PAD.decode(base64)?; + Self::from_bytes(&bytes) + } pub fn to_base64(&self) -> anyhow::Result { - let bytes = bincode::serde::encode_to_vec( - self, - bincode::config::standard() - )?; - Ok(STANDARD_NO_PAD.encode(&bytes)) + Ok(STANDARD_NO_PAD.encode(self.to_bytes())) } } From 425ac09c9b6b92a3595e3fa884489b5f43cd357b Mon Sep 17 00:00:00 2001 From: JKearnsl Date: Thu, 22 May 2025 23:05:22 +0300 Subject: [PATCH 2/3] improve cli --- crates/server/src/bin/command/users/add.rs | 19 +++++------- crates/server/src/bin/command/users/list.rs | 31 ++++++++++++------- crates/server/src/bin/command/users/remove.rs | 20 ++++++++---- crates/server/src/bin/opt.rs | 4 +-- crates/server/src/bin/style.rs | 21 +++++++------ 5 files changed, 53 insertions(+), 42 deletions(-) diff --git a/crates/server/src/bin/command/users/add.rs b/crates/server/src/bin/command/users/add.rs index a1801cc..766256d 100644 --- a/crates/server/src/bin/command/users/add.rs +++ b/crates/server/src/bin/command/users/add.rs @@ -1,4 +1,6 @@ -use std::path::PathBuf; +use crate::storage::{database, Client, Clients}; +use crate::style::{format_opaque_bytes, generate_qrcode}; +use crate::{success_err, success_ok}; use clap::Parser; use inquire::required; use inquire::validator::Validation; @@ -6,12 +8,7 @@ use server::config::Config; use shared::connection_config::{ConnectionConfig, CredentialsConfig, GeneralConfig}; use shared::keys::handshake::{PublicKey, SecretKey}; use shared::session::Alg; -use crate::storage::{database, Client, Clients}; -use crate::style::{format_opaque_bytes, generate_qrcode}; -use std::fmt::Write; -use qrcode::{EcLevel, QrCode, Version}; -use qrcode::render::unicode; -use crate::success_ok; +use std::path::PathBuf; #[derive(Debug, Parser)] pub struct AddCmd { @@ -53,7 +50,7 @@ impl AddCmd { let sk = match self.sk { Some(sk) => SecretKey::try_from(sk.as_str()).map_err(|error| { - anyhow::anyhow!("failed to parse private key: {}", error) + anyhow::anyhow!("parse private key: {}", error) })?, None => SecretKey::generate_x25519() }; @@ -61,7 +58,7 @@ impl AddCmd { let pk = PublicKey::derive_from(sk.clone()); let psk = match self.psk { Some(psk) => SecretKey::try_from(psk.as_str()).map_err(|error| { - anyhow::anyhow!("failed to parse pre-shared key: {}", error) + anyhow::anyhow!("parse pre-shared key: {}", error) })?, None => SecretKey::generate_x25519() }; @@ -100,12 +97,12 @@ impl AddCmd { )); connection_config.save(config_path.as_path()).map_err(|error| { - anyhow::anyhow!("failed to save connection config: {}", error) + anyhow::anyhow!("save connection config: {}", error) })?; match generate_qrcode((connection_config.to_base64()?).as_ref()) { Ok(qr) => println!("{}\n", qr), - Err(err) => panic!("failed to generate qrcode: {}", err) + Err(err) => success_err!("generate qrcode: {}", err) } success_ok!("Saved", "config to {}", config_path.display()); diff --git a/crates/server/src/bin/command/users/list.rs b/crates/server/src/bin/command/users/list.rs index e3b6344..aededd9 100644 --- a/crates/server/src/bin/command/users/list.rs +++ b/crates/server/src/bin/command/users/list.rs @@ -1,12 +1,17 @@ use crate::storage::{database, Clients}; use clap::Parser; use derive_more::Display; +use inquire::Select; use server::config::Config; +use shared::keys::handshake::{PublicKey, SecretKey}; +use crate::style::format_opaque_bytes; +use crate::success_ok; #[derive(Clone, Display)] -#[display("{}\tcreated at {}", pk, created_at)] +#[display("{:.8}\t{}", pk.to_string(), created_at)] pub struct UserRow { - pub pk: String, + pub pk: PublicKey, + pub psk: SecretKey, pub created_at: String, } @@ -16,20 +21,22 @@ pub struct ListCmd; impl ListCmd { pub async fn exec(self, config: Config) -> anyhow::Result<()> { let clients = Clients::new(database(&config.general.storage)?)?; - let users: Vec<_> = clients.get_all().await.iter().map(|client| { + let mut users: Vec<_> = clients.get_all().await.into_iter().map(|client| { UserRow { - pk: client.peer_pk.to_string(), - created_at: client.created_at.to_rfc2822() + pk: client.peer_pk, + psk: client.psk, + created_at: client.created_at.format("%Y-%m-%d %H:%M:%S").to_string() } }).collect(); + users.sort_by_key(|user| user.created_at.clone()); + let selected_row = Select::new("Select user", users).prompt()?; - if users.is_empty() { - return Err(anyhow::anyhow!("no users found")); - } - - for user in users { - println!("{}", user); - } + println!(); + success_ok!("PubKey", selected_row.pk); + success_ok!("SharedKey", format_opaque_bytes(selected_row.psk.as_slice())); + success_ok!("CreatedAt", selected_row.created_at); + println!(); + Ok(()) } } \ No newline at end of file diff --git a/crates/server/src/bin/command/users/remove.rs b/crates/server/src/bin/command/users/remove.rs index c604a5b..d86f797 100644 --- a/crates/server/src/bin/command/users/remove.rs +++ b/crates/server/src/bin/command/users/remove.rs @@ -1,3 +1,4 @@ +use anyhow::anyhow; use crate::storage::{database, Clients}; use clap::Parser; use server::config::Config; @@ -6,20 +7,27 @@ use crate::success_ok; #[derive(Debug, Parser)] pub struct RemoveCmd { - /// public key (hex) - #[arg(short, long)] + /// public key + #[arg()] pk: String, } impl RemoveCmd { pub async fn exec(self, config: Config) -> anyhow::Result<()> { let pk = PublicKey::try_from(self.pk.as_str()).map_err(|error| { - anyhow::anyhow!("failed to parse public key: {}", error) + anyhow::anyhow!("parse public key: {}", error) })?; let clients = Clients::new(database(&config.general.storage)?)?; - clients.delete(&pk).await?; - success_ok!("Client has been successfully removed"); - Ok(()) + match clients.get(&pk).await { + Some(_) => { + clients.delete(&pk).await?; + success_ok!("Success", "client {:8} removed", pk); + Ok(()) + }, + None => { + Err(anyhow!("client {:8} not found", pk)) + } + } } } \ No newline at end of file diff --git a/crates/server/src/bin/opt.rs b/crates/server/src/bin/opt.rs index 391415b..1700b64 100644 --- a/crates/server/src/bin/opt.rs +++ b/crates/server/src/bin/opt.rs @@ -1,6 +1,4 @@ -use std::{path::PathBuf, io::IsTerminal, fs}; -use std::path::Path; -use chrono::Local; +use std::{path::PathBuf, io::IsTerminal}; use clap::Parser; use tracing::{debug, info}; use tracing_appender::non_blocking::WorkerGuard; diff --git a/crates/server/src/bin/style.rs b/crates/server/src/bin/style.rs index 48e0229..59b994a 100644 --- a/crates/server/src/bin/style.rs +++ b/crates/server/src/bin/style.rs @@ -1,7 +1,6 @@ -use std::io::Read; -use inquire::ui::{Attributes, Color, RenderConfig, StyleSheet, Styled}; -use qrcode::{EcLevel, QrCode, Version}; +use inquire::ui::{Attributes, Color, IndexPrefix, RenderConfig, StyleSheet, Styled}; use qrcode::render::unicode; +use qrcode::{EcLevel, QrCode, Version}; pub fn styles() -> clap::builder::Styles { clap::builder::Styles::styled() @@ -45,11 +44,13 @@ pub fn render_config() -> RenderConfig<'static> { let mut render_config = RenderConfig::default(); render_config.answered_prompt_prefix = Styled::new("✔").with_fg(Color::LightGreen); render_config.prompt_prefix = Styled::new(">").with_fg(Color::LightRed); - render_config.highlighted_option_prefix = Styled::new("➠").with_fg(Color::LightYellow); + render_config.highlighted_option_prefix = Styled::new("➠").with_fg(Color::DarkGreen); render_config.selected_checkbox = Styled::new("☑").with_fg(Color::LightGreen); - render_config.scroll_up_prefix = Styled::new("⇞"); - render_config.scroll_down_prefix = Styled::new("⇟"); + render_config.scroll_up_prefix = Styled::new("⮝"); + render_config.scroll_down_prefix = Styled::new("⮟"); render_config.unselected_checkbox = Styled::new("☐"); + render_config.selected_option = Some(StyleSheet::new().with_fg(Color::DarkGreen).with_attr(Attributes::BOLD)); + render_config.option_index_prefix = IndexPrefix::SpacePadded; render_config.error_message.message = StyleSheet::new().with_fg(Color::DarkRed); render_config.error_message = render_config @@ -60,7 +61,7 @@ pub fn render_config() -> RenderConfig<'static> { .with_attr(Attributes::ITALIC) .with_fg(Color::DarkGreen); - render_config.help_message = StyleSheet::new().with_fg(Color::DarkCyan); + render_config.help_message = StyleSheet::new().with_fg(Color::DarkCyan).with_attr(Attributes::BOLD); render_config } @@ -80,9 +81,9 @@ pub fn format_opaque_bytes(bytes: &[u8]) -> String { let hex_str: String = rem.iter().map(|b| format!("{:02x}", b)).collect(); let block_chars = [ - "\u{2595}", "\u{2581}", "\u{2582}", "\u{2583}", "\u{2584}", "\u{2585}", - "\u{2586}", "\u{2587}", "\u{2588}", "\u{2589}", "\u{259A}", "\u{259B}", - "\u{259C}", "\u{259D}", "\u{259E}", "\u{259F}" + '\u{2595}', '\u{2581}', '\u{2582}', '\u{2583}', '\u{2584}', '\u{2585}', + '\u{2586}', '\u{2587}', '\u{2588}', '\u{2589}', '\u{259A}', '\u{259B}', + '\u{259C}', '\u{259D}', '\u{259E}', '\u{259F}' ]; hex_str.chars() From 8c4a54a73146a9cadd74c0e2d1ffbab04795136d Mon Sep 17 00:00:00 2001 From: JKearnsl Date: Thu, 22 May 2025 23:48:55 +0300 Subject: [PATCH 3/3] improve cli --- Cargo.toml | 3 + crates/client/Cargo.toml | 8 +- crates/client/src/bin/command/connect.rs | 44 ++++++--- crates/client/src/bin/mod.rs | 9 +- crates/client/src/bin/opt.rs | 36 +++---- crates/server/Cargo.toml | 10 +- crates/server/src/bin/command/start.rs | 9 +- crates/server/src/bin/command/users/add.rs | 2 +- crates/server/src/bin/command/users/list.rs | 2 +- crates/server/src/bin/command/users/remove.rs | 2 +- crates/server/src/bin/mod.rs | 3 +- crates/server/src/bin/opt.rs | 2 +- crates/server/src/bin/style.rs | 56 ----------- crates/server/src/runtime/worker/data.rs | 4 +- crates/server/src/runtime/worker/handshake.rs | 4 +- crates/shared/Cargo.toml | 2 + crates/shared/src/lib.rs | 1 + crates/shared/src/style.rs | 93 +++++++++++++++++++ 18 files changed, 182 insertions(+), 108 deletions(-) create mode 100644 crates/shared/src/style.rs diff --git a/Cargo.toml b/Cargo.toml index 207e30e..b65718a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,8 @@ tokio-tungstenite = "0.26.2" futures = "0.3.31" ctrlc = "3.4" rand_core = "=0.6.4" +anstyle = "1.0" +clap = "4.5" # data chrono = { version = "0.4", features = ["serde"] } @@ -30,6 +32,7 @@ anyhow = "1.0.95" etherparse = "0.18" bincode = "2.0.1" toml = "0.8.22" +thiserror = "2.0.12" # crypto x25519-dalek = "2.0.1" diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index dd2e5f1..9b714a7 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -5,7 +5,7 @@ edition = { workspace = true } autobins = false [features] -default = ["cli"] +default = ["cli", "udp"] udp = ["socket2"] ws = ["tokio-tungstenite", "futures"] cli = ["clap", "ctrlc", "anstyle"] @@ -24,18 +24,18 @@ tokio = { workspace = true } tun-rs = { workspace = true } socket2 = { workspace = true, optional = true} ctrlc = { workspace = true, optional = true} -clap = { version = "4.5.23", features = ["derive"], optional = true} +clap = { workspace = true, features = ["derive", "wrap_help"], optional = true} anstyle = { version = "1.0", optional = true } tokio-tungstenite = { workspace = true, optional = true } futures = { workspace = true, optional = true } ipnetwork = "0.21.1" -console-subscriber = "0.4.1" +# console-subscriber = "0.4.1" # Crypto snow = "0.9" # Data -thiserror = "2.0" +thiserror = { workspace = true } anyhow = { workspace = true } bincode = { workspace = true } diff --git a/crates/client/src/bin/command/connect.rs b/crates/client/src/bin/command/connect.rs index 590cad1..ff7253d 100644 --- a/crates/client/src/bin/command/connect.rs +++ b/crates/client/src/bin/command/connect.rs @@ -14,10 +14,14 @@ use client::runtime::Runtime; use client::runtime::state::RuntimeState; use shared::connection_config::{ConnectionConfig, InterfaceConfig, RuntimeConfig}; use shared::network::find_available_ifname; +use shared::success_err; #[derive(Debug, Args)] #[group(required = true, multiple = false)] pub struct ConnectCmd { + /// common connection (config or key) + #[arg(value_name = "CONNECTION")] + connection: Option, /// config file #[arg(short, long, value_name = "FILE PATH")] config: Option, @@ -29,23 +33,39 @@ pub struct ConnectCmd { impl ConnectCmd { pub async fn exec(self) { - let mut config = match self.config { - Some(ref path) => match ConnectionConfig::load(path) { + let mut config = match self.connection { + Some(ref connection) => match ConnectionConfig::from_base64(connection) { Ok(config) => config, - Err(err) => { - eprintln!("failed to load config: {}", err); - process::exit(1); + Err(parse_key_err) => match ConnectionConfig::load(&PathBuf::from(connection)) { + Ok(config) => config, + Err(parse_config_err) => { + success_err!( + "parse connection\n\n\t if this key: {}\n\n\t if this config path: {}\n", + parse_key_err, + parse_config_err + ); + process::exit(1); + } } }, None => match self.key { Some(key) => match ConnectionConfig::from_base64(&key) { Ok(config) => config, Err(err) => { - eprintln!("failed to parse config key: {}", err); + success_err!("parse config key: {}", err); process::exit(1); } }, - None => unreachable!("config or key is required should protected by clap") + None => match self.config { + Some(ref path) => match ConnectionConfig::load(path) { + Ok(config) => config, + Err(err) => { + success_err!("load config: {}", err); + process::exit(1); + } + }, + None => unreachable!("config or key is required should protected by clap") + } } }; @@ -62,7 +82,7 @@ impl ConnectCmd { if let Some(path) = self.config { if let Err(err) = config.save(&path) { - eprintln!("failed to save config: {}", err); + success_err!("save config: {}", err); process::exit(1); } } @@ -70,7 +90,7 @@ impl ConnectCmd { let sock_addr = match config.general.host.parse() { Ok(addr) => SocketAddr::new(addr, config.general.port), Err(err) => { - eprintln!("failed to resolve host: {}", err); + success_err!("resolve host: {}", err); process::exit(1); } }; @@ -83,7 +103,7 @@ impl ConnectCmd { ).await { Ok(tun) => Arc::new(tun), Err(err) => { - eprintln!("failed to setup tun: {}", err); + success_err!("setup tun: {}", err); process::exit(1); } }; @@ -92,7 +112,7 @@ impl ConnectCmd { { Ok(routes) => Arc::new(routes), Err(err) => { - eprintln!("failed to setup routes: {}", err); + success_err!("setup routes: {}", err); process::exit(1); } }; @@ -135,7 +155,7 @@ impl ConnectCmd { } _ => { routes_clone.restore(); - error!("{}", error); + success_err!("{}", error); } } } diff --git a/crates/client/src/bin/mod.rs b/crates/client/src/bin/mod.rs index 3deb48f..b913c3d 100644 --- a/crates/client/src/bin/mod.rs +++ b/crates/client/src/bin/mod.rs @@ -4,6 +4,7 @@ mod style; use crate::command::Commands; use clap::Parser; +use shared::success_err; const LOG_DIR: &str = "logs"; const LOG_PREFIX: &str = "client.log"; @@ -11,7 +12,13 @@ const LOG_PREFIX: &str = "client.log"; #[tokio::main(flavor = "multi_thread")] async fn main() { let opt = opt::Opt::parse(); - opt.init_logging(); + let _guard = match opt.init_logging() { + Ok(guard) => guard, + Err(err) => { + success_err!("{}\n", err); + std::process::exit(1); + } + }; // console_subscriber::init(); match opt.cmd { diff --git a/crates/client/src/bin/opt.rs b/crates/client/src/bin/opt.rs index c4d9888..96f3357 100644 --- a/crates/client/src/bin/opt.rs +++ b/crates/client/src/bin/opt.rs @@ -3,8 +3,9 @@ use crate::command::Commands; use crate::{LOG_DIR, LOG_PREFIX}; use clap::Parser; use std::io::IsTerminal; -use tracing::level_filters::LevelFilter; -use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, Layer}; +use tracing_appender::non_blocking::WorkerGuard; +use tracing_appender::rolling::{RollingFileAppender, Rotation}; +use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer}; #[derive(Debug, Parser)] #[clap(about = "The Holynet vpn client command-line interface.", version, arg_required_else_help = true, styles=styles())] @@ -19,29 +20,30 @@ pub struct Opt { } impl Opt { - pub fn init_logging(&self) { - let log_level = LevelFilter::from_level(if self.debug { - tracing::Level::DEBUG - } else { - tracing::Level::INFO - }); - - let file_appender = tracing_appender::rolling::daily(LOG_DIR, LOG_PREFIX); - let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender); - + pub fn init_logging(&self) -> anyhow::Result { + let appender = RollingFileAppender::builder() + .rotation(Rotation::DAILY) + .filename_prefix(LOG_PREFIX) + .build(LOG_DIR)?; + + let (non_blocking, guard) = tracing_appender::non_blocking(appender); + + let filter = if self.debug { "server=debug" } else { "server=info" }; + let file_layer = fmt::layer() .with_writer(non_blocking) .with_ansi(false) - .with_filter(log_level); - + .with_filter(EnvFilter::new(filter)); + let console_layer = fmt::layer() .with_ansi(std::io::stdout().is_terminal()) - .with_filter(log_level); - + .with_filter(EnvFilter::new(filter)); + tracing_subscriber::registry() .with(file_layer) .with(console_layer) .init(); - + + Ok(guard) } } diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index 4e2814f..d9b12f4 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -8,7 +8,7 @@ autobins = false default = ["cli"] udp = ["socket2"] ws = ["tokio-tungstenite", "futures", "socket2"] -cli = ["clap", "inquire", "anstyle", "ctrlc", "fjall"] +cli = ["clap", "inquire", "anstyle", "ctrlc", "fjall", "qrcode"] [[bin]] name = "server" @@ -17,9 +17,9 @@ required-features = ["cli"] [dependencies] shared = { workspace = true } -clap = { version = "4.5", features = ["cargo", "color", "derive", "env"], optional = true} +clap = { workspace = true, features = ["derive", "env", "wrap_help"], optional = true} inquire = { version = "0.7", optional = true } -anstyle = { version = "1.0", optional = true } +anstyle = { workspace = true, optional = true } ctrlc = { version = "3.4", optional = true } # data @@ -27,7 +27,7 @@ chrono = { workspace = true } etherparse = { workspace = true } serde = { workspace = true } anyhow = { workspace = true } -thiserror = "2.0.12" +thiserror = { workspace = true } dashmap = "7.0.0-rc2" bincode = { workspace = true } derive_more = { version = "2.0.1", features = ["display"] } @@ -48,7 +48,7 @@ tokio = { workspace = true } socket2 = { workspace = true, optional = true } async-trait = "0.1" rand = "0.9.1" -qrcode = { version = "0.14.1", default-features = false, features = ["pic"] } +qrcode = { version = "0.14.1", default-features = false, features = ["pic"], optional = true } tokio-tungstenite = { workspace = true, optional = true } futures = { workspace = true, optional = true } diff --git a/crates/server/src/bin/command/start.rs b/crates/server/src/bin/command/start.rs index fd0c971..40cf5ad 100644 --- a/crates/server/src/bin/command/start.rs +++ b/crates/server/src/bin/command/start.rs @@ -1,11 +1,12 @@ -use std::process; +use std::{process, thread}; +use std::time::Duration; use clap::Parser; use tracing::{debug, error, info}; use server::config::Config; use server::runtime::error::RuntimeError; use server::runtime::Runtime; use crate::storage::{database, Clients}; -use crate::{success_err, success_warn}; +use shared::{success_err, success_warn}; #[derive(Debug, Parser)] pub struct StartCmd { @@ -76,8 +77,8 @@ impl StartCmd { debug!("stop signal not sent from Ctrl-C handler: {}", err); } } - // thread::sleep(Duration::from_secs(1)); - // process::exit(0); + thread::sleep(Duration::from_secs(1)); + process::exit(0); }).expect("error setting Ctrl-C handler"); if let Err(errors) = runtime.run().await { diff --git a/crates/server/src/bin/command/users/add.rs b/crates/server/src/bin/command/users/add.rs index 766256d..69ec9d7 100644 --- a/crates/server/src/bin/command/users/add.rs +++ b/crates/server/src/bin/command/users/add.rs @@ -1,6 +1,5 @@ use crate::storage::{database, Client, Clients}; use crate::style::{format_opaque_bytes, generate_qrcode}; -use crate::{success_err, success_ok}; use clap::Parser; use inquire::required; use inquire::validator::Validation; @@ -9,6 +8,7 @@ use shared::connection_config::{ConnectionConfig, CredentialsConfig, GeneralConf use shared::keys::handshake::{PublicKey, SecretKey}; use shared::session::Alg; use std::path::PathBuf; +use shared::{success_err, success_ok}; #[derive(Debug, Parser)] pub struct AddCmd { diff --git a/crates/server/src/bin/command/users/list.rs b/crates/server/src/bin/command/users/list.rs index aededd9..0e4a5f0 100644 --- a/crates/server/src/bin/command/users/list.rs +++ b/crates/server/src/bin/command/users/list.rs @@ -5,7 +5,7 @@ use inquire::Select; use server::config::Config; use shared::keys::handshake::{PublicKey, SecretKey}; use crate::style::format_opaque_bytes; -use crate::success_ok; +use shared::success_ok; #[derive(Clone, Display)] #[display("{:.8}\t{}", pk.to_string(), created_at)] diff --git a/crates/server/src/bin/command/users/remove.rs b/crates/server/src/bin/command/users/remove.rs index d86f797..0e2c12b 100644 --- a/crates/server/src/bin/command/users/remove.rs +++ b/crates/server/src/bin/command/users/remove.rs @@ -3,7 +3,7 @@ use crate::storage::{database, Clients}; use clap::Parser; use server::config::Config; use shared::keys::handshake::PublicKey; -use crate::success_ok; +use shared::success_ok; #[derive(Debug, Parser)] pub struct RemoveCmd { diff --git a/crates/server/src/bin/mod.rs b/crates/server/src/bin/mod.rs index ac232cb..714a524 100644 --- a/crates/server/src/bin/mod.rs +++ b/crates/server/src/bin/mod.rs @@ -8,6 +8,7 @@ use style::render_config; use crate::command::Commands; use crate::opt::Opt; use clap::Parser; +use shared::success_err; const CONFIG_PATH_ENV: &str = "HOLYNET_CONFIG"; const LOG_DIR: &str = "logs"; @@ -15,7 +16,7 @@ const LOG_PREFIX: &str = "server.log"; #[tokio::main(flavor = "multi_thread")] async fn main() { - let mut opt = Opt::parse(); + let opt = Opt::parse(); let _guard = match opt.init_logging() { Ok(guard) => guard, Err(err) => { diff --git a/crates/server/src/bin/opt.rs b/crates/server/src/bin/opt.rs index 1700b64..d461618 100644 --- a/crates/server/src/bin/opt.rs +++ b/crates/server/src/bin/opt.rs @@ -30,7 +30,7 @@ pub struct Opt { } impl Opt { - pub fn init_logging(&mut self) -> anyhow::Result { + pub fn init_logging(&self) -> anyhow::Result { let appender = RollingFileAppender::builder() .rotation(Rotation::DAILY) .filename_prefix(LOG_PREFIX) diff --git a/crates/server/src/bin/style.rs b/crates/server/src/bin/style.rs index 59b994a..066996f 100644 --- a/crates/server/src/bin/style.rs +++ b/crates/server/src/bin/style.rs @@ -98,62 +98,6 @@ pub fn format_opaque_bytes(bytes: &[u8]) -> String { } } -#[macro_export] -macro_rules! success_ok { - ($message:expr) => { - success_ok!("OK", $message) - }; - ($level:expr, $message:expr) => { - println!( - "{}{:>12}{} {}", - anstyle::Style::new() - .bold() - .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Green))), - $level, - anstyle::Reset.render(), - $message - ) - }; - ($level:expr, $message:expr, $($arg:tt)*) => { - success_ok!($level, format!($message, $($arg)*)) - }; -} - -#[macro_export] -macro_rules! success_err { - ($message:expr) => { - eprintln!( - "{}error:{} {}", - anstyle::Style::new() - .bold() - .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red))), - anstyle::Reset.render(), - $message - ) - }; - ($message:expr, $($arg:tt)*) => { - success_err!(format!($message, $($arg)*)) - }; -} - -#[macro_export] -macro_rules! success_warn { - ($message:expr) => { - eprintln!( - "{}warning:{} {}", - anstyle::Style::new() - .bold() - .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Yellow))), - anstyle::Reset.render(), - $message - ) - }; - ($message:expr, $($arg:tt)*) => { - success_warn!(format!($message, $($arg)*)) - }; -} - - pub fn generate_qrcode(data: &[u8]) -> anyhow::Result { let code = QrCode::with_version(data, Version::Normal(7), EcLevel::L)?; let string = code.render::() diff --git a/crates/server/src/runtime/worker/data.rs b/crates/server/src/runtime/worker/data.rs index 5805e48..7cab9b8 100644 --- a/crates/server/src/runtime/worker/data.rs +++ b/crates/server/src/runtime/worker/data.rs @@ -97,7 +97,7 @@ pub(super) async fn data_transport_executor( None => warn!("[{}] received data packet for unknown session {}", addr, sid) }, None => { - error!("data_transport_executor channel is closed"); + debug!("data_transport_executor channel is closed"); break } } @@ -127,7 +127,7 @@ pub(super) async fn data_tun_executor( None => warn!("[{}] received data packet for unknown session", holy_ip) }, None => { - error!("data_tun_executor channel is closed"); + debug!("data_tun_executor channel is closed"); break } } diff --git a/crates/server/src/runtime/worker/handshake.rs b/crates/server/src/runtime/worker/handshake.rs index f5075a1..7b1d0e7 100644 --- a/crates/server/src/runtime/worker/handshake.rs +++ b/crates/server/src/runtime/worker/handshake.rs @@ -4,7 +4,7 @@ use dashmap::DashMap; use snow::{Builder}; use tokio::sync::broadcast::Receiver; use tokio::sync::mpsc; -use tracing::{error, info, warn}; +use tracing::{debug, error, info, warn}; use shared::credential::Credential; use shared::keys::handshake::{PublicKey, SecretKey}; @@ -140,7 +140,7 @@ pub(super) async fn handshake_executor( } }, None => { - error!("handshake_executor channel is closed"); + debug!("handshake_executor channel is closed"); break } } diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml index ba5574d..fe4477d 100644 --- a/crates/shared/Cargo.toml +++ b/crates/shared/Cargo.toml @@ -10,6 +10,8 @@ serde = { workspace = true } tun-rs = { workspace = true } base64 = "0.22.1" toml = { workspace = true } +anstyle = { workspace = true } +clap = { workspace = true } bincode = { workspace = true, features = ["serde"]} rand_core = { workspace = true } lazy_static = "1.5.0" diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs index 3e2bb1f..d0328ae 100644 --- a/crates/shared/src/lib.rs +++ b/crates/shared/src/lib.rs @@ -8,3 +8,4 @@ pub mod time; pub mod protocol; pub mod tun; pub mod types; +pub mod style; diff --git a/crates/shared/src/style.rs b/crates/shared/src/style.rs new file mode 100644 index 0000000..a54a30e --- /dev/null +++ b/crates/shared/src/style.rs @@ -0,0 +1,93 @@ + +pub fn styles() -> clap::builder::Styles { + clap::builder::Styles::styled() + .usage( + anstyle::Style::new() + .bold() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Green))) + ) + .header( + anstyle::Style::new() + .bold() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Green))) + ) + .literal( + anstyle::Style::new() + .bold() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Cyan))) + ) + .invalid( + anstyle::Style::new() + .bold() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red))) + ) + .error( + anstyle::Style::new() + .bold() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red))) + ) + .valid( + anstyle::Style::new() + .bold() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Green))) + ) + .placeholder( + anstyle::Style::new() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Cyan))) + ) +} + +#[macro_export] +macro_rules! success_ok { + ($message:expr) => { + success_ok!("OK", $message) + }; + ($level:expr, $message:expr) => { + println!( + "{}{:>12}{} {}", + anstyle::Style::new() + .bold() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Green))), + $level, + anstyle::Reset.render(), + $message + ) + }; + ($level:expr, $message:expr, $($arg:tt)*) => { + success_ok!($level, format!($message, $($arg)*)) + }; +} + +#[macro_export] +macro_rules! success_err { + ($message:expr) => { + eprintln!( + "{}error:{} {}", + anstyle::Style::new() + .bold() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red))), + anstyle::Reset.render(), + $message + ) + }; + ($message:expr, $($arg:tt)*) => { + success_err!(format!($message, $($arg)*)) + }; +} + +#[macro_export] +macro_rules! success_warn { + ($message:expr) => { + eprintln!( + "{}warning:{} {}", + anstyle::Style::new() + .bold() + .fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Yellow))), + anstyle::Reset.render(), + $message + ) + }; + ($message:expr, $($arg:tt)*) => { + success_warn!(format!($message, $($arg)*)) + }; +}