From aab258f45bf1c41cbe3c2585f7586fc1154d63b0 Mon Sep 17 00:00:00 2001 From: Alexandr Bogomiakov Date: Thu, 24 Jul 2025 03:12:09 +0300 Subject: [PATCH] Fix Release action --- src/bin/cli.rs | 180 +++++++++++++++++++++++++++++++++++++++++++++ src/bin/desktop.rs | 142 +++++++++++++++++++++++++++++++++++ src/lib.rs | 113 ++++++++++++++++++++++++++++ 3 files changed, 435 insertions(+) create mode 100644 src/bin/cli.rs create mode 100644 src/bin/desktop.rs create mode 100644 src/lib.rs diff --git a/src/bin/cli.rs b/src/bin/cli.rs new file mode 100644 index 0000000..104aa6a --- /dev/null +++ b/src/bin/cli.rs @@ -0,0 +1,180 @@ +use khm::{client, server, Args}; + +use clap::Parser; +use env_logger; +use log::{error, info}; + +/// CLI version of KHM - Known Hosts Manager for SSH key management and synchronization +/// Supports server and client modes without GUI dependencies +#[derive(Parser, Debug, Clone)] +#[command( + author = env!("CARGO_PKG_AUTHORS"), + version = env!("CARGO_PKG_VERSION"), + about = "SSH Host Key Manager (CLI with Server)", + long_about = None, + after_help = "Examples:\n\ + \n\ + Running in server mode:\n\ + khm --server --ip 0.0.0.0 --port 1337 --db-host psql.psql.svc --db-name khm --db-user admin --db-password --flows work,home\n\ + \n\ + Running in client mode to send diff and sync ~/.ssh/known_hosts with remote flow `work` in place:\n\ + khm --host https://khm.example.com --flow work --known-hosts ~/.ssh/known_hosts --in-place\n\ + \n\ + " +)] +pub struct CliArgs { + /// Run in server mode (default: false) + #[arg(long, help = "Run in server mode")] + pub server: bool, + + /// Update the known_hosts file with keys from the server after sending keys (default: false) + #[arg( + long, + help = "Server mode: Sync the known_hosts file with keys from the server" + )] + pub in_place: bool, + + /// Comma-separated list of flows to manage (default: default) + #[arg(long, default_value = "default", value_parser, num_args = 1.., value_delimiter = ',', help = "Server mode: Comma-separated list of flows to manage")] + pub flows: Vec, + + /// IP address to bind the server or client to (default: 127.0.0.1) + #[arg( + short, + long, + default_value = "127.0.0.1", + help = "Server mode: IP address to bind the server to" + )] + pub ip: String, + + /// Port to bind the server or client to (default: 8080) + #[arg( + short, + long, + default_value = "8080", + help = "Server mode: Port to bind the server to" + )] + pub port: u16, + + /// Hostname or IP address of the PostgreSQL database (default: 127.0.0.1) + #[arg( + long, + default_value = "127.0.0.1", + help = "Server mode: Hostname or IP address of the PostgreSQL database" + )] + pub db_host: String, + + /// Name of the PostgreSQL database (default: khm) + #[arg( + long, + default_value = "khm", + help = "Server mode: Name of the PostgreSQL database" + )] + pub db_name: String, + + /// Username for the PostgreSQL database (required in server mode) + #[arg( + long, + required_if_eq("server", "true"), + help = "Server mode: Username for the PostgreSQL database" + )] + pub db_user: Option, + + /// Password for the PostgreSQL database (required in server mode) + #[arg( + long, + required_if_eq("server", "true"), + help = "Server mode: Password for the PostgreSQL database" + )] + pub db_password: Option, + + /// Host address of the server to connect to in client mode (required in client mode) + #[arg( + long, + required_if_eq("server", "false"), + help = "Client mode: Full host address of the server to connect to. Like https://khm.example.com" + )] + pub host: Option, + + /// Flow name to use on the server + #[arg( + long, + required_if_eq("server", "false"), + help = "Client mode: Flow name to use on the server" + )] + pub flow: Option, + + /// Path to the known_hosts file (default: ~/.ssh/known_hosts) + #[arg( + long, + default_value = "~/.ssh/known_hosts", + help = "Client mode: Path to the known_hosts file" + )] + pub known_hosts: String, + + /// Basic auth string for client mode. Format: user:pass + #[arg(long, default_value = "", help = "Client mode: Basic Auth credentials")] + pub basic_auth: String, +} + +impl From for Args { + fn from(cli_args: CliArgs) -> Self { + Args { + server: cli_args.server, + daemon: false, + settings_ui: false, + in_place: cli_args.in_place, + flows: cli_args.flows, + ip: cli_args.ip, + port: cli_args.port, + db_host: cli_args.db_host, + db_name: cli_args.db_name, + db_user: cli_args.db_user, + db_password: cli_args.db_password, + host: cli_args.host, + flow: cli_args.flow, + known_hosts: cli_args.known_hosts, + basic_auth: cli_args.basic_auth, + } + } +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + // Configure logging to show only khm logs, filtering out noisy library logs + env_logger::Builder::from_default_env() + .filter_level(log::LevelFilter::Warn) // Default level for all modules + .filter_module("khm", log::LevelFilter::Debug) // Our app logs + .filter_module("actix_web", log::LevelFilter::Info) // Server logs + .filter_module("reqwest", log::LevelFilter::Warn) // HTTP client + .init(); + + info!("Starting SSH Key Manager (CLI)"); + + let cli_args = CliArgs::parse(); + let args: Args = cli_args.into(); + + // Validate arguments - either server mode or client mode with required args + if !args.server && (args.host.is_none() || args.flow.is_none()) { + error!("CLI version requires either --server mode or client mode with --host and --flow arguments"); + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Invalid arguments for CLI mode", + )); + } + + if args.server { + info!("Running in server mode"); + if let Err(e) = server::run_server(args).await { + error!("Failed to run server: {}", e); + } + } else { + info!("Running in client mode"); + if let Err(e) = client::run_client(args).await { + error!("Failed to run client: {}", e); + } + } + + info!("Application has exited"); + Ok(()) +} \ No newline at end of file diff --git a/src/bin/desktop.rs b/src/bin/desktop.rs new file mode 100644 index 0000000..ee86a0b --- /dev/null +++ b/src/bin/desktop.rs @@ -0,0 +1,142 @@ +use khm::{gui, Args}; + +use clap::Parser; +use env_logger; +use log::{error, info}; + +/// Desktop version of KHM - Known Hosts Manager with GUI interface +/// Primarily runs in GUI mode with tray application and settings windows +#[derive(Parser, Debug, Clone)] +#[command( + author = env!("CARGO_PKG_AUTHORS"), + version = env!("CARGO_PKG_VERSION"), + about = "SSH Host Key Manager (Desktop)", + long_about = None, + after_help = "Examples:\n\ + \n\ + Running in GUI tray mode (default):\n\ + khm-desktop\n\ + \n\ + Running in GUI tray mode with background daemon:\n\ + khm-desktop --daemon\n\ + \n\ + Running settings window:\n\ + khm-desktop --settings-ui\n\ + \n\ + " +)] +pub struct DesktopArgs { + /// Hide console window and run in background (default: auto when no arguments) + #[arg(long, help = "Hide console window and run in background")] + pub daemon: bool, + + /// Run settings UI window + #[arg(long, help = "Run settings UI window")] + pub settings_ui: bool, +} + +impl From for Args { + fn from(desktop_args: DesktopArgs) -> Self { + Args { + server: false, + daemon: desktop_args.daemon, + settings_ui: desktop_args.settings_ui, + in_place: false, + flows: vec!["default".to_string()], + ip: "127.0.0.1".to_string(), + port: 8080, + db_host: "127.0.0.1".to_string(), + db_name: "khm".to_string(), + db_user: None, + db_password: None, + host: None, + flow: None, + known_hosts: "~/.ssh/known_hosts".to_string(), + basic_auth: String::new(), + } + } +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + // Configure logging to show only khm logs, filtering out noisy library logs + env_logger::Builder::from_default_env() + .filter_level(log::LevelFilter::Warn) // Default level for all modules + .filter_module("khm", log::LevelFilter::Debug) // Our app logs + .filter_module("winit", log::LevelFilter::Error) // Window management + .filter_module("egui", log::LevelFilter::Error) // GUI framework + .filter_module("eframe", log::LevelFilter::Error) // GUI framework + .filter_module("tray_icon", log::LevelFilter::Error) // Tray icon + .filter_module("wgpu", log::LevelFilter::Error) // Graphics + .filter_module("naga", log::LevelFilter::Error) // Graphics + .filter_module("glow", log::LevelFilter::Error) // Graphics + .filter_module("tracing", log::LevelFilter::Error) // Tracing spans + .init(); + + info!("Starting SSH Key Manager (Desktop)"); + + let desktop_args = DesktopArgs::parse(); + let args: Args = desktop_args.into(); + + // Hide console on Windows if daemon flag is set + if args.daemon { + #[cfg(target_os = "windows")] + { + extern "system" { + fn FreeConsole() -> i32; + } + unsafe { + FreeConsole(); + } + } + } + + // Settings UI mode - just show settings window and exit + if args.settings_ui { + // Always hide console for settings window + #[cfg(target_os = "windows")] + { + extern "system" { + fn FreeConsole() -> i32; + } + unsafe { + FreeConsole(); + } + } + + #[cfg(feature = "gui")] + { + info!("Running settings UI window"); + gui::run_settings_window(); + return Ok(()); + } + #[cfg(not(feature = "gui"))] + { + error!("GUI features not compiled. Install system dependencies and rebuild with --features gui"); + return Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "GUI features not compiled", + )); + } + } + + // Default to GUI mode for desktop version + info!("Running in GUI mode"); + #[cfg(feature = "gui")] + { + if let Err(e) = gui::run_gui().await { + error!("Failed to run GUI: {}", e); + } + } + #[cfg(not(feature = "gui"))] + { + error!("GUI features not compiled. Install system dependencies and rebuild with --features gui"); + return Err(std::io::Error::new( + std::io::ErrorKind::Unsupported, + "GUI features not compiled", + )); + } + + info!("Application has exited"); + Ok(()) +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1ecee44 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,113 @@ +pub mod client; +pub mod db; +pub mod gui; +pub mod server; +#[cfg(feature = "web")] +pub mod web; + +use clap::Parser; + +// Common Args structure used by all binaries +#[derive(Parser, Debug, Clone)] +pub struct Args { + /// Run in server mode (default: false) + #[arg(long, help = "Run in server mode")] + pub server: bool, + + /// Hide console window and run in background (default: auto when no arguments) + #[arg(long, help = "Hide console window and run in background")] + pub daemon: bool, + + /// Run settings UI window + #[arg(long, help = "Run settings UI window")] + pub settings_ui: bool, + + /// Update the known_hosts file with keys from the server after sending keys (default: false) + #[arg( + long, + help = "Server mode: Sync the known_hosts file with keys from the server" + )] + pub in_place: bool, + + /// Comma-separated list of flows to manage (default: default) + #[arg(long, default_value = "default", value_parser, num_args = 1.., value_delimiter = ',', help = "Server mode: Comma-separated list of flows to manage")] + pub flows: Vec, + + /// IP address to bind the server or client to (default: 127.0.0.1) + #[arg( + short, + long, + default_value = "127.0.0.1", + help = "Server mode: IP address to bind the server to" + )] + pub ip: String, + + /// Port to bind the server or client to (default: 8080) + #[arg( + short, + long, + default_value = "8080", + help = "Server mode: Port to bind the server to" + )] + pub port: u16, + + /// Hostname or IP address of the PostgreSQL database (default: 127.0.0.1) + #[arg( + long, + default_value = "127.0.0.1", + help = "Server mode: Hostname or IP address of the PostgreSQL database" + )] + pub db_host: String, + + /// Name of the PostgreSQL database (default: khm) + #[arg( + long, + default_value = "khm", + help = "Server mode: Name of the PostgreSQL database" + )] + pub db_name: String, + + /// Username for the PostgreSQL database (required in server mode) + #[arg( + long, + required_if_eq("server", "true"), + help = "Server mode: Username for the PostgreSQL database" + )] + pub db_user: Option, + + /// Password for the PostgreSQL database (required in server mode) + #[arg( + long, + required_if_eq("server", "true"), + help = "Server mode: Password for the PostgreSQL database" + )] + pub db_password: Option, + + /// Host address of the server to connect to in client mode (required in client mode) + #[arg( + long, + required_if_eq("server", "false"), + help = "Client mode: Full host address of the server to connect to. Like https://khm.example.com" + )] + pub host: Option, + + /// Flow name to use on the server + #[arg( + long, + required_if_eq("server", "false"), + help = "Client mode: Flow name to use on the server" + )] + pub flow: Option, + + /// Path to the known_hosts file (default: ~/.ssh/known_hosts) + #[arg( + long, + default_value = "~/.ssh/known_hosts", + help = "Client mode: Path to the known_hosts file" + )] + pub known_hosts: String, + + /// Basic auth string for client mode. Format: user:pass + #[arg(long, default_value = "", help = "Client mode: Basic Auth credentials")] + pub basic_auth: String, +} \ No newline at end of file