use std::path::PathBuf;
use iridium_core::{Database, PersistentDatabase};
use iridium_server::{playground, Credentials, ServerConfig, TdsServer};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
init_logger();
let mut args: Vec<String> = std::env::args().collect();
args.remove(0);
let mut config = ServerConfig::default();
let mut playground_mode = false;
let mut memory_mode = false;
let mut database_arg_provided = false;
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"--host" | "-h" => {
i += 1;
config.host = args.get(i).cloned().unwrap_or_default();
}
"--port" | "-p" => {
i += 1;
config.port = args.get(i).and_then(|s| s.parse().ok()).unwrap_or(1433);
}
"--user" | "-u" => {
i += 1;
let user = args.get(i).cloned().unwrap_or_default();
if config.auth.is_none() {
config.auth = Some(Credentials {
user: user.clone(),
password: String::new(),
});
} else if let Some(ref mut creds) = config.auth {
creds.user = user;
}
}
"--password" | "-P" => {
i += 1;
let pass = args.get(i).cloned().unwrap_or_default();
if config.auth.is_none() {
config.auth = Some(Credentials {
user: String::new(),
password: pass,
});
} else if let Some(ref mut creds) = config.auth {
creds.password = pass;
}
}
"--database" | "-d" => {
i += 1;
config.database = args.get(i).cloned().unwrap_or_default();
database_arg_provided = true;
}
"--data-dir" => {
i += 1;
if let Some(path) = args.get(i) {
config.data_dir = Some(PathBuf::from(path));
} else {
eprintln!("Error: --data-dir requires a path argument.");
std::process::exit(1);
}
}
"--pool-min" => {
i += 1;
config.pool_min_size = args
.get(i)
.and_then(|s| s.parse().ok())
.unwrap_or(config.pool_min_size);
}
"--pool-max" => {
i += 1;
config.pool_max_size = args
.get(i)
.and_then(|s| s.parse().ok())
.unwrap_or(config.pool_max_size);
}
"--pool-idle-timeout" => {
i += 1;
config.pool_idle_timeout_secs = args
.get(i)
.and_then(|s| s.parse().ok())
.unwrap_or(config.pool_idle_timeout_secs);
}
"--tls" | "-t" => {
config.tls_enabled = true;
}
"--no-tls" => {
config.tls_enabled = false;
}
"--tls-cert" => {
i += 1;
config.tls_cert_path = args.get(i).cloned();
}
"--tls-key" => {
i += 1;
config.tls_key_path = args.get(i).cloned();
}
"--tls-gen" => {
let cert_path = "tls_cert.pem";
let key_path = "tls_key.pem";
println!("Generating self-signed TLS certificate...");
iridium_server::tls::generate_self_signed_cert(cert_path, key_path)?;
println!("Generated {} and {}", cert_path, key_path);
config.tls_cert_path = Some(cert_path.to_string());
config.tls_key_path = Some(key_path.to_string());
config.tls_enabled = true;
}
"--playground" => {
playground_mode = true;
}
"--memory" => {
memory_mode = true;
}
"--help" => {
print_help();
return Ok(());
}
_ => {
eprintln!("Unknown argument: {}", args[i]);
print_help();
std::process::exit(1);
}
}
i += 1;
}
if let Some(ref creds) = config.auth {
if creds.user.is_empty() && creds.password.is_empty() {
config.auth = None;
}
}
if config.tls_enabled && config.tls_cert_path.is_none() {
eprintln!("Error: TLS is enabled but no certificate specified.");
eprintln!("Use --tls-gen to generate a self-signed certificate, or --tls-cert and --tls-key to specify existing files.");
std::process::exit(1);
}
if playground_mode && !database_arg_provided {
config.database = "iridium_sql".to_string();
}
log::info!("iridium-server v{}", env!("CARGO_PKG_VERSION"));
log::info!("Host: {}", config.host);
log::info!("Port: {}", config.port);
log::info!(
"Auth: {}",
if config.auth.is_some() {
"enabled"
} else {
"disabled (accept any login)"
}
);
log::info!(
"TLS: {}",
if config.tls_enabled {
format!("enabled ({})", config.tls_cert_path.as_ref().unwrap())
} else {
"disabled".to_string()
}
);
log::info!("Database: {}", config.database);
let storage_description = if memory_mode {
"memory (ephemeral)".to_string()
} else {
format!("persistent ({})", config.resolved_data_dir().display())
};
log::info!("Storage: {}", storage_description);
log::info!(
"Session pool: min={}, max={}, idle_timeout={}s",
config.pool_min_size,
config.pool_max_size,
config.pool_idle_timeout_secs
);
let server = if memory_mode {
let db = Database::new();
if playground_mode {
log::info!("Starting in PLAYGROUND mode...");
log::info!("Seeding database with sample tables, views, and data...");
if let Err(e) = playground::seed_playground(&db) {
log::warn!("Some playground seed operations failed: {}", e);
}
log::info!("Playground database ready");
log::info!("Sample tables: dbo.Customers, dbo.Products, dbo.Orders, dbo.OrderItems, dbo.Employees, dbo.Categories");
log::info!("Sample views: dbo.vCustomerOrders, dbo.vOrderDetails, dbo.vProductSales, dbo.vEmployeeHierarchy, dbo.vMonthlySales");
}
TdsServer::new_with_database(db, config)
} else {
let db = PersistentDatabase::new_persistent(&config.resolved_data_dir())?;
if playground_mode {
log::info!("Starting in PLAYGROUND mode...");
log::info!("Seeding database with sample tables, views, and data...");
if let Err(e) = playground::seed_playground(&db) {
log::warn!("Some playground seed operations failed: {}", e);
}
log::info!("Playground database ready");
log::info!("Sample tables: dbo.Customers, dbo.Products, dbo.Orders, dbo.OrderItems, dbo.Employees, dbo.Categories");
log::info!("Sample views: dbo.vCustomerOrders, dbo.vOrderDetails, dbo.vProductSales, dbo.vEmployeeHierarchy, dbo.vMonthlySales");
}
TdsServer::new_with_database(db, config)
};
server.run().await?;
Ok(())
}
fn init_logger() {
use std::io::Write;
use std::sync::mpsc;
use std::thread;
struct AsyncPipeWriter {
sender: mpsc::Sender<Vec<u8>>,
}
impl Write for AsyncPipeWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.sender.send(buf.to_vec()).map_err(|_| {
std::io::Error::new(std::io::ErrorKind::BrokenPipe, "logger worker stopped")
})?;
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let (tx, rx) = mpsc::channel::<Vec<u8>>();
thread::spawn(move || {
let stderr = std::io::stderr();
let mut handle = stderr.lock();
while let Ok(buf) = rx.recv() {
if handle.write_all(&buf).is_err() {
break;
}
if handle.flush().is_err() {
break;
}
}
});
let mut builder =
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("debug"));
builder.target(env_logger::Target::Pipe(Box::new(AsyncPipeWriter {
sender: tx,
})));
builder.format(|buf, record| {
let ts = buf.timestamp_millis();
writeln!(
buf,
"[{} {:<5} {}] {}",
ts,
record.level(),
record.target(),
record.args()
)?;
writeln!(buf)
});
builder.init();
}
fn print_help() {
println!("iridium-server - TDS 7.4 SQL Server emulator");
println!();
println!("USAGE:");
println!(" iridium-server [OPTIONS]");
println!();
println!("OPTIONS:");
println!(" --host, -h <HOST> Hostname to bind to (default: 127.0.0.1)");
println!(" --port, -p <PORT> Port to listen on (default: 1433)");
println!(" --user, -u <USER> Username for authentication (optional)");
println!(" --password, -P <PASS> Password for authentication (optional)");
println!(" --database, -d <DB> Default database name (default: master)");
println!(" --data-dir <PATH> Directory for persistent storage");
println!(" --pool-min <N> Minimum pooled sessions to keep ready (default: 1)");
println!(" --pool-max <N> Maximum pooled sessions allowed (default: 50)");
println!(" --pool-idle-timeout <S> Idle timeout in seconds for extra sessions (default: 300)");
println!(" --tls, -t Enable TLS (default: enabled)");
println!(" --no-tls Disable TLS");
println!(" --tls-cert <PATH> TLS certificate file (required if TLS enabled)");
println!(" --tls-key <PATH> TLS private key file (required if TLS enabled)");
println!(" --tls-gen Generate self-signed TLS certificate");
println!(" --playground Start with sample tables, views, and data");
println!(" --memory Use ephemeral in-memory storage instead of disk");
println!(" --help Show this help message");
println!();
println!("EXAMPLES:");
println!(" iridium-server # TLS enabled, no auth");
println!(" iridium-server --tls-gen # Generate cert and start server");
println!(" iridium-server --port 14330 # No auth, port 14330");
println!(" iridium-server --pool-min 2 --pool-max 100 --pool-idle-timeout 60");
println!(" iridium-server -u sa -P Test@12345 # With auth");
println!(" iridium-server --playground # Playground mode with sample data");
println!(" iridium-server --playground --no-tls # Playground without TLS");
println!();
println!("PLAYGROUND MODE:");
println!(" When --playground is enabled, the server starts with pre-loaded");
println!(" sample tables (Customers, Products, Orders, etc.) and views for");
println!(" testing SQL Server clients without manual setup.");
println!();
println!("NOTES:");
println!(" - TLS is enabled by default");
println!(" - Storage defaults to ProgramData on Windows and ./iridium_sql_data elsewhere");
println!(" - Use --data-dir to override the persistent storage path");
println!(" - Use --tls-gen to generate a self-signed certificate for testing");
}