mc-gate 0.1.1

Async proxy for Minecraft and HTTP traffic with automated server wake-up.
Documentation
use clap::{Parser, ValueEnum};
use mc_gate::{Config, WakeupCondition, run};
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use tokio::process::Command;

#[derive(ValueEnum, Clone, Debug, PartialEq)]
pub enum WakeupArg {
    Motd,
    Join,
    Disabled,
}

#[derive(Parser)]
struct Cli {
    #[arg(short, long, default_value = "0.0.0.0:25565")]
    listen: String,
    #[arg(short, long)]
    web: Option<String>,
    #[arg(short, long, default_value = "127.0.0.1:25567")]
    mc: String,
    #[arg(short, long)]
    on_wakeup: Option<String>,
    #[arg(long, value_enum, default_value = "motd")]
    wakeup_on: WakeupArg,
    #[arg(short, long, default_value_t = false)]
    debug: bool,

    #[arg(
        long,
        default_value = "§c§l⚡ §eServer currently waking up...\n§7Please wait a moment."
    )]
    msg_motd: String,

    #[arg(
        long,
        default_value = "§6§l⚡ §eServer still starting...\n\n§7Please wait a moment while the world loads.\n\n§8[§eNote§8] §eIf the ping bar stays §9blue/idle§e, please\n§etry to re-join manually in §c2 minutes§e."
    )]
    msg_starting: String,

    #[arg(
        long,
        default_value = "§6§l⚡ §eServer still starting...\n\n§c§lNext attempt will put you in a waitlist.\n§7(We will notify you when the server is ready)"
    )]
    msg_waitlist: String,

    #[arg(
        long,
        default_value = "§6Server §a§lONLINE§r§6!\n\n§6§lTry to join the server normally."
    )]
    msg_online: String,

    #[arg(
        long,
        default_value = "§c§l⚡ §eWaitlist timeout...\n\n§7The server is taking too long to start.\n§ePlease try again in a few minutes."
    )]
    msg_timeout: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args = Cli::parse();

    let cond = match args.wakeup_on {
        WakeupArg::Motd => WakeupCondition::Motd,
        WakeupArg::Join => WakeupCondition::Join,
        WakeupArg::Disabled => WakeupCondition::Disabled,
    };

    let callback = args.on_wakeup.map(|cmd_str| {
        let debug = args.debug;
        let cb: mc_gate::WakeupCallback = Arc::new(move || {
            let cmd_to_run = cmd_str.clone();
            Box::pin(async move {
                if debug {
                    println!("Executing wakeup command...");
                }
                let mut cmd = if cfg!(target_os = "windows") {
                    let mut c = Command::new("cmd");
                    c.args(["/C", &cmd_to_run]);
                    c
                } else {
                    let mut c = Command::new("sh");
                    c.args(["-c", &cmd_to_run]);
                    c
                };
                cmd.kill_on_drop(true);
                match cmd.status().await {
                    Ok(s) if s.success() => {
                        if debug {
                            println!("Wakeup command executed successfully");
                        }
                    }
                    Ok(s) => eprintln!("Wakeup command failed: {}", s),
                    Err(e) => eprintln!("Failed to execute: {}", e),
                }
            })
        });
        cb
    });

    let cfg = Arc::new(Config {
        listen: args.listen,
        web: args.web,
        mc: args.mc,
        wakeup_on: cond,
        debug: args.debug,
        on_wakeup: callback,
        is_waking: AtomicBool::new(false),

        msg_motd: args.msg_motd,
        msg_starting: args.msg_starting,
        msg_waitlist: args.msg_waitlist,
        msg_online: args.msg_online,
        msg_timeout: args.msg_timeout,
    });

    tokio::spawn(async move {
        tokio::signal::ctrl_c().await.ok();
        std::process::exit(0);
    });

    run(cfg).await
}