mod api;
mod config;
mod data_server;
mod store;
mod tunnel;
mod web;
use std::net::SocketAddr;
use anyhow::Result;
use clap::Parser;
use std::sync::Arc;
use tokio::net::TcpListener;
use tracing::{error, info, warn};
use config::ServerConfig;
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt().init();
let config = ServerConfig::parse();
let store = Arc::new(store::KeyStore::new(&config.data_dir));
let init_key = store::ensure_initial_key(&store).await?;
if !init_key.is_empty() {
info!("──────────────────────────────────────────");
info!(" Admin API Key: {}", init_key);
info!(" Save this key! It won't be shown again.");
info!("──────────────────────────────────────────");
}
if let Ok(content) = std::fs::read_to_string("/proc/sys/net/ipv4/ip_local_port_range") {
if let Some((a, b)) = content.trim().split_once(' ') {
let ephem_start: u16 = a.parse().unwrap_or(32768);
let ephem_end: u16 = b.parse().unwrap_or(60999);
info!("system ephemeral port range: {}-{}", ephem_start, ephem_end);
if config.port_start <= ephem_end && config.port_end >= ephem_start {
warn!(
"tunnel port range {}-{} overlaps with ephemeral range {}-{} — consider --port-end {} to avoid conflicts",
config.port_start, config.port_end, ephem_start, ephem_end,
ephem_start.saturating_sub(1)
);
}
}
}
let port_count = if config.port_end >= config.port_start {
(config.port_end - config.port_start + 1) as u32
} else {
0
};
info!("tunnel port range: {}-{} ({} ports)",
config.port_start, config.port_end, port_count);
let tunnel_mgr = Arc::new(tunnel::TunnelManager::new(
config.port_start,
config.port_end,
config.data_addr.parse::<SocketAddr>()?.port(),
));
tunnel_mgr.start_cleanup_loop();
let app = api::build_router(tunnel_mgr.clone(), store.clone());
let http_addr: SocketAddr = config.http_addr.parse()?;
let http_listener = TcpListener::bind(http_addr).await?;
info!("HTTP server listening on http://{}", http_addr);
let data_addr: SocketAddr = config.data_addr.parse()?;
let data_listener = TcpListener::bind(data_addr).await?;
info!("Data server listening on {}", data_addr);
let data_srv = data_server::DataServer::new(data_listener, tunnel_mgr.clone());
tokio::select! {
r = axum::serve(http_listener, app) => {
if let Err(e) = r { error!("HTTP server error: {e}"); }
}
r = data_srv.run() => {
if let Err(e) = r { error!("Data server error: {e}"); }
}
_ = shutdown_signal() => {
info!("Shutting down...");
}
}
Ok(())
}
async fn shutdown_signal() {
tokio::signal::ctrl_c()
.await
.expect("failed to listen for ctrl-c");
}