Files
rust-work/ssh-proxy/src/main.rs
绝尘 f59ed9aae0 优化: ssh-proxy russh 迁移后代码整理
- 移除未使用的 async-trait 依赖
- 添加断线重连逻辑,会话失效时自动重连
- 修复 get_or_create_session TOCTOU 竞态条件
- 日志智能分级: 慢请求告警、退出码识别
- 用 time crate 替换手写日期计算 (删除40行)
- UTF-8 安全截断修复
- 同步优化 mysql-proxy 日志模块
2026-03-21 00:53:27 +08:00

170 lines
5.7 KiB
Rust

mod cli;
mod config;
mod handler;
mod logger;
mod session;
use axum::{routing::{get, post}, Router};
use clap::{Parser, Subcommand};
use std::sync::Arc;
#[derive(Parser, Debug)]
#[command(name = "ssh-proxy")]
#[command(about = "SSH HTTP proxy with session pooling")]
struct Args {
#[command(subcommand)]
command: Option<Commands>,
#[arg(long, default_value = "ssh-proxy.toml", global = true)]
config: String,
#[arg(short = 'S', long, default_value = "http://127.0.0.1:3308", global = true)]
server: String,
}
#[derive(Debug, Subcommand)]
enum Commands {
Server {
#[arg(short = 'P', long)]
port: Option<u16>,
#[arg(short = 'H', long)]
host: Option<String>,
},
Exec {
#[arg(short = 'n', long)]
name: String,
#[arg(short, long)]
command: String,
/// Output format: text, json
#[arg(short = 'F', long, default_value = "text")]
format: String,
},
Servers,
/// Add server dynamically (temporary)
AddServer {
#[arg(short = 'n', long)]
name: String,
#[arg(short = 'H', long)]
host: String,
#[arg(short = 'P', long, default_value = "22")]
port: u16,
#[arg(short = 'u', long, default_value = "root")]
user: String,
#[arg(short = 'p', long)]
password: Option<String>,
#[arg(short = 'k', long)]
private_key: Option<String>,
},
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Args::parse();
match args.command {
Some(Commands::Server { port, host }) => run_server(&args.config, port, host).await,
Some(Commands::Exec { name, command, format }) => run_cli_exec(&args.server, &name, &command, &format),
Some(Commands::Servers) => run_cli_servers(&args.server),
Some(Commands::AddServer { name, host, port, user, password, private_key }) => {
run_cli_add_server(&args.server, name, host, port, user, password, private_key)
}
None => run_server(&args.config, None, None).await,
}
}
async fn run_server(config_path: &str, port: Option<u16>, host: Option<String>) -> anyhow::Result<()> {
println!("SSH HTTP Proxy v0.1.0\n");
let mut config = config::Config::from_file(config_path)?;
if let Some(p) = port { config.server.port = p; }
if let Some(h) = host { config.server.host = h; }
// 初始化日志
let log_path = std::env::var("SSH_PROXY_LOG").ok();
let logger = Arc::new(logger::RequestLogger::new(log_path.as_deref()));
if logger.is_enabled() {
println!("Request logging: enabled");
}
println!("Initializing SSH sessions...\n");
let manager = Arc::new(session::SessionManager::new(&config.servers)?);
let manager_clone = manager.clone();
let idle_timeout = config.pool.idle_timeout_secs;
let check_interval = config.pool.check_interval_secs;
tokio::spawn(async move {
let mut interval = tokio::time::interval(std::time::Duration::from_secs(check_interval));
loop {
interval.tick().await;
manager_clone.cleanup_idle(idle_timeout).await;
}
});
let app = Router::new()
.route("/exec", post(handler::exec))
.route("/servers", get(handler::servers))
.route("/servers/add", post(handler::add_server))
.route("/health", get(handler::health))
.with_state(Arc::new((manager, logger)));
let addr = format!("{}:{}", config.server.host, config.server.port);
let listener = tokio::net::TcpListener::bind(&addr).await?;
println!("\nServer started at http://{}", addr);
println!("\nAPI Endpoints:");
println!(" POST /exec - Execute remote command");
println!(" POST /servers/add - Add server (temporary)");
println!(" GET /servers - List all servers");
println!(" GET /health - Health check");
println!("\nCLI Usage:");
println!(" ssh-proxy exec -n flux_dev -c \"docker ps\"");
println!(" ssh-proxy exec -n flux_dev -c \"docker ps\" -F json");
println!(" ssh-proxy servers");
axum::serve(listener, app).await?;
Ok(())
}
fn run_cli_exec(server_url: &str, server: &str, command: &str, format: &str) -> anyhow::Result<()> {
let cli = cli::Cli::new(Some(server_url.to_string()));
if !cli.check_server()? {
eprintln!();
eprintln!("=== ssh-proxy 未运行,请使用降级方案 ===");
eprintln!();
eprintln!("启动代理: ssh-proxy");
eprintln!();
eprintln!("或直接使用 ssh 命令:");
eprintln!(" ssh <user>@<host> \"<command>\"");
eprintln!();
eprintln!("配置文件中的服务器信息见: ssh-proxy.toml");
anyhow::bail!("Proxy server not running at {}", server_url);
}
cli.exec(server, command, format)
}
fn run_cli_servers(server_url: &str) -> anyhow::Result<()> {
let cli = cli::Cli::new(Some(server_url.to_string()));
if !cli.check_server()? {
eprintln!();
eprintln!("=== ssh-proxy 未运行 ===");
eprintln!("启动代理: ssh-proxy");
eprintln!("配置文件: ssh-proxy.toml");
anyhow::bail!("Proxy server not running at {}", server_url);
}
cli.list_servers()
}
fn run_cli_add_server(
server_url: &str,
name: String,
host: String,
port: u16,
user: String,
password: Option<String>,
private_key: Option<String>,
) -> anyhow::Result<()> {
let cli = cli::Cli::new(Some(server_url.to_string()));
if !cli.check_server()? {
eprintln!();
eprintln!("=== ssh-proxy 未运行 ===");
eprintln!("启动代理: ssh-proxy");
anyhow::bail!("Proxy server not running at {}", server_url);
}
cli.add_server(name, host, port, user, password, private_key)
}