use clap::Parser;
use qssh::vault::SigningService;
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[clap(name = "qssh-sign")]
#[clap(about = "QSSH Signing Service — Isolated key enclave with Unix socket API")]
#[clap(version = env!("CARGO_PKG_VERSION"))]
struct Args {
#[clap(short = 's', long, default_value = "~/.qssh/sign.sock")]
socket: String,
#[clap(short = 'a', long, default_value = "~/.qssh/audit.jsonl")]
audit: String,
#[clap(short = 'r', long, default_value = "60")]
rate_limit: u32,
#[clap(short = 'f', long)]
foreground: bool,
#[clap(short = 'v', long)]
verbose: bool,
#[clap(short = 'k', long)]
kill: bool,
}
fn expand_tilde(path: &str) -> PathBuf {
if path.starts_with("~/") {
if let Ok(home) = std::env::var("HOME") {
return PathBuf::from(format!("{}{}", home, &path[1..]));
}
}
PathBuf::from(path)
}
#[tokio::main]
async fn main() {
let args = Args::parse();
if args.verbose {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug"))
.target(env_logger::Target::Stderr)
.init();
} else {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
.target(env_logger::Target::Stderr)
.init();
}
let socket_path = expand_tilde(&args.socket);
let audit_path = expand_tilde(&args.audit);
if args.kill {
if socket_path.exists() {
std::fs::remove_file(&socket_path).ok();
eprintln!("Signing service stopped (socket removed)");
} else {
eprintln!("No signing service running at {:?}", socket_path);
}
return;
}
if let Some(parent) = socket_path.parent() {
if let Err(e) = std::fs::create_dir_all(parent) {
eprintln!("Failed to create directory {:?}: {}", parent, e);
std::process::exit(1);
}
}
eprintln!("QSSH Signing Service v{}", env!("CARGO_PKG_VERSION"));
eprintln!("Socket: {:?}", socket_path);
eprintln!("Audit: {:?}", audit_path);
eprintln!("Rate: {} signs/min/key", args.rate_limit);
if !args.foreground {
use std::process::{Command, Stdio};
let exe = std::env::current_exe().expect("Failed to get current exe");
let mut cmd = Command::new(exe);
cmd.arg("--foreground")
.arg("--socket").arg(socket_path.to_str().unwrap())
.arg("--audit").arg(audit_path.to_str().unwrap())
.arg("--rate-limit").arg(args.rate_limit.to_string());
if args.verbose {
cmd.arg("--verbose");
}
cmd.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null());
match cmd.spawn() {
Ok(child) => {
eprintln!("Signing service started (pid {})", child.id());
return;
}
Err(e) => {
eprintln!("Failed to daemonize: {}", e);
std::process::exit(1);
}
}
}
let service = SigningService::new(socket_path, audit_path, args.rate_limit);
if let Err(e) = service.run().await {
log::error!("Signing service error: {}", e);
std::process::exit(1);
}
}