mod auth; mod config; mod db; mod docker; mod models; mod mods; mod properties; mod rcon; mod routes; mod sanitize; use tracing_subscriber::EnvFilter; use crate::db::Database; use crate::docker::DockerManager; use crate::routes::AppState; #[tokio::main] async fn main() { tracing_subscriber::fmt() .with_env_filter(EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into())) .init(); if !std::path::Path::new("config.toml").exists() { let default = include_str!("../config.toml"); std::fs::write("config.toml", default).expect("Failed to create config.toml"); eprintln!("Created config.toml with default values. Please edit it and restart."); std::process::exit(0); } let config = config::Config::load().unwrap_or_else(|e| { eprintln!("Configuration error: {e}"); std::process::exit(1); }); if !config.server_directory.exists() { eprintln!( "Server directory does not exist: {}", config.server_directory.display() ); eprintln!("Please create it or update config.toml."); std::process::exit(1); } let db_path = config.server_directory.join("mcui.db"); let db = Database::open(&db_path).unwrap_or_else(|e| { eprintln!("Failed to open database: {e}"); std::process::exit(1); }); bootstrap_superuser(&db, &config); let docker = DockerManager::new(config.clone()).unwrap_or_else(|e| { eprintln!("Failed to connect to Docker: {e}"); eprintln!("Is the Docker daemon running and accessible?"); std::process::exit(1); }); let state = AppState { config: config.clone(), db: db.clone(), docker, }; let router = routes::build_router(state); let addr = format!("127.0.0.1:{}", config.webui_port); tracing::info!("Starting Minecraft UI on http://{addr}"); let listener = tokio::net::TcpListener::bind(&addr) .await .unwrap_or_else(|e| { eprintln!("Failed to bind to {addr}: {e}"); std::process::exit(1); }); axum::serve(listener, router).await.unwrap_or_else(|e| { eprintln!("Server error: {e}"); }); } fn bootstrap_superuser(db: &Database, config: &config::Config) { let conn = db.conn(); let count: i32 = conn .query_row("SELECT COUNT(*) FROM accounts", [], |row| row.get(0)) .unwrap_or(0); drop(conn); if count == 0 { let hash = auth::hash_password(&config.initial_superuser_password) .expect("Failed to hash initial password"); let conn = db.conn(); conn.execute( "INSERT INTO accounts (username, password, is_superuser, password_change_required) VALUES (?1, ?2, 1, 1)", rusqlite::params![config.initial_superuser, hash], ) .expect("Failed to create initial superuser"); drop(conn); tracing::info!( "Created initial superuser '{}' (password change required on first login)", config.initial_superuser ); } }