summaryrefslogtreecommitdiffstats
path: root/src/main.rs
blob: 28579f3b20642cbff1928d8eba78f40fc30f0f79 (plain) (blame)
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!("0.0.0.0:{}", 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
        );
    }
}