Files
khm/src/main.rs

241 lines
7.7 KiB
Rust
Raw Normal View History

2024-07-07 21:02:39 +03:00
mod client;
2025-04-29 21:12:27 +00:00
mod db;
2025-07-23 23:53:46 +03:00
mod gui;
2024-07-07 21:13:19 +03:00
mod server;
2025-07-18 17:52:58 +03:00
mod web;
2024-07-07 21:02:39 +03:00
use clap::Parser;
use env_logger;
use log::{error, info};
2024-07-07 21:02:39 +03:00
/// This application manages SSH keys and flows, either as a server or client.
/// In server mode, it stores keys and flows in a PostgreSQL database.
/// In client mode, it sends keys to the server and can update the known_hosts file with keys from the server.
2025-07-22 14:22:57 +03:00
#[derive(Parser, Debug, Clone)]
2024-07-07 21:02:39 +03:00
#[command(
2024-07-07 21:13:19 +03:00
author = env!("CARGO_PKG_AUTHORS"),
version = env!("CARGO_PKG_VERSION"),
2024-07-09 10:03:33 +03:00
about = "SSH Host Key Manager",
2024-07-07 21:02:39 +03:00
long_about = None,
after_help = "Examples:\n\
\n\
2025-07-24 02:36:21 +03:00
Running in GUI tray mode (default):\n\
khm\n\
\n\
Running in GUI tray mode with background daemon:\n\
khm --daemon\n\
\n\
2024-07-07 21:02:39 +03:00
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 <SECRET> --flows work,home\n\
\n\
2024-11-11 12:46:55 +02:00
Running in client mode to send diff and sync ~/.ssh/known_hosts with remote flow `work` in place:\n\
2025-07-19 15:51:17 +03:00
khm --host https://khm.example.com --flow work --known-hosts ~/.ssh/known_hosts --in-place\n\
2024-07-07 21:02:39 +03:00
\n\
2025-07-24 02:36:21 +03:00
Running settings window:\n\
khm --settings-ui\n\
\n\
2024-07-07 21:02:39 +03:00
"
2024-07-07 21:13:19 +03:00
)]
2025-07-22 14:22:57 +03:00
pub struct Args {
/// Run in server mode (default: false)
#[arg(long, help = "Run in server mode")]
2025-07-22 17:27:19 +03:00
pub server: bool,
2025-07-24 02:36:21 +03:00
/// 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,
2025-07-22 12:50:01 +03:00
2025-07-24 02:36:21 +03:00
/// Run settings UI window
#[arg(long, help = "Run settings UI window")]
2025-07-22 17:27:19 +03:00
pub settings_ui: bool,
2025-07-22 12:50:01 +03:00
/// 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"
)]
2025-07-22 17:27:19 +03:00
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")]
2025-07-22 17:27:19 +03:00
pub flows: Vec<String>,
2024-07-07 21:02:39 +03:00
/// IP address to bind the server or client to (default: 127.0.0.1)
2024-07-07 21:13:19 +03:00
#[arg(
short,
long,
default_value = "127.0.0.1",
help = "Server mode: IP address to bind the server to"
)]
2025-07-22 17:27:19 +03:00
pub ip: String,
2024-07-07 21:02:39 +03:00
/// Port to bind the server or client to (default: 8080)
2024-07-07 21:13:19 +03:00
#[arg(
short,
long,
default_value = "8080",
help = "Server mode: Port to bind the server to"
)]
2025-07-22 17:27:19 +03:00
pub port: u16,
2024-07-07 21:02:39 +03:00
/// Hostname or IP address of the PostgreSQL database (default: 127.0.0.1)
2024-07-07 21:13:19 +03:00
#[arg(
long,
default_value = "127.0.0.1",
help = "Server mode: Hostname or IP address of the PostgreSQL database"
)]
2025-07-22 17:27:19 +03:00
pub db_host: String,
2024-07-07 21:02:39 +03:00
/// Name of the PostgreSQL database (default: khm)
2024-07-07 21:13:19 +03:00
#[arg(
long,
default_value = "khm",
help = "Server mode: Name of the PostgreSQL database"
)]
2025-07-22 17:27:19 +03:00
pub db_name: String,
2024-07-07 21:02:39 +03:00
/// Username for the PostgreSQL database (required in server mode)
2024-07-07 21:13:19 +03:00
#[arg(
long,
required_if_eq("server", "true"),
help = "Server mode: Username for the PostgreSQL database"
)]
2025-07-22 17:27:19 +03:00
pub db_user: Option<String>,
2024-07-07 21:02:39 +03:00
/// Password for the PostgreSQL database (required in server mode)
2024-07-07 21:13:19 +03:00
#[arg(
long,
required_if_eq("server", "true"),
help = "Server mode: Password for the PostgreSQL database"
)]
2025-07-22 17:27:19 +03:00
pub db_password: Option<String>,
2024-07-07 21:02:39 +03:00
/// Host address of the server to connect to in client mode (required in client mode)
2024-07-07 21:13:19 +03:00
#[arg(
long,
required_if_eq("server", "false"),
2025-07-19 15:51:17 +03:00
help = "Client mode: Full host address of the server to connect to. Like https://khm.example.com"
2024-07-07 21:13:19 +03:00
)]
2025-07-22 17:27:19 +03:00
pub host: Option<String>,
2024-07-07 21:02:39 +03:00
2025-07-19 15:51:17 +03:00
/// Flow name to use on the server
#[arg(
long,
required_if_eq("server", "false"),
help = "Client mode: Flow name to use on the server"
)]
2025-07-22 17:27:19 +03:00
pub flow: Option<String>,
2025-07-19 15:51:17 +03:00
2024-07-07 21:02:39 +03:00
/// Path to the known_hosts file (default: ~/.ssh/known_hosts)
2024-07-07 21:13:19 +03:00
#[arg(
long,
default_value = "~/.ssh/known_hosts",
help = "Client mode: Path to the known_hosts file"
)]
2025-07-22 17:27:19 +03:00
pub known_hosts: String,
2025-03-14 02:14:15 +02:00
/// Basic auth string for client mode. Format: user:pass
#[arg(long, default_value = "", help = "Client mode: Basic Auth credentials")]
2025-07-22 17:27:19 +03:00
pub basic_auth: String,
2024-07-07 21:02:39 +03:00
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
2025-07-22 14:12:09 +03:00
// 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
.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();
2025-07-23 23:53:46 +03:00
2024-07-09 02:28:44 +03:00
info!("Starting SSH Key Manager");
2024-07-07 21:02:39 +03:00
let args = Args::parse();
2025-07-24 02:36:21 +03:00
// Hide console on Windows if daemon flag is set
if args.daemon {
#[cfg(target_os = "windows")]
{
extern "system" {
fn FreeConsole() -> i32;
}
unsafe {
FreeConsole();
}
}
}
2025-07-22 12:50:01 +03:00
// Settings UI mode - just show settings window and exit
if args.settings_ui {
2025-07-24 02:36:21 +03:00
// Always hide console for settings window
#[cfg(target_os = "windows")]
{
extern "system" {
fn FreeConsole() -> i32;
}
unsafe {
FreeConsole();
}
}
2025-07-22 23:14:55 +03:00
#[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,
2025-07-23 23:53:46 +03:00
"GUI features not compiled",
2025-07-22 23:14:55 +03:00
));
}
2025-07-22 12:50:01 +03:00
}
2025-07-24 02:36:21 +03:00
// Check if we should run GUI mode (default when no server/client args)
if !args.server && (args.host.is_none() || args.flow.is_none()) {
2025-07-22 12:50:01 +03:00
info!("Running in GUI mode");
2025-07-24 02:36:21 +03:00
#[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",
));
2025-07-22 12:50:01 +03:00
}
return Ok(());
}
2024-07-07 21:02:39 +03:00
if args.server {
2024-07-09 02:28:44 +03:00
info!("Running in server mode");
if let Err(e) = server::run_server(args).await {
error!("Failed to run server: {}", e);
}
2024-07-07 21:02:39 +03:00
} else {
2024-07-09 02:28:44 +03:00
info!("Running in client mode");
if let Err(e) = client::run_client(args).await {
error!("Failed to run client: {}", e);
}
2024-07-07 21:02:39 +03:00
}
2024-07-09 02:28:44 +03:00
info!("Application has exited");
Ok(())
2024-07-07 21:02:39 +03:00
}