1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
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
);
}
}
|