mirror of
https://github.com/timvisee/lazymc.git
synced 2025-07-26 09:42:03 -07:00
Add simple SIGINT handling
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -174,6 +174,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures",
|
"futures",
|
||||||
|
"libc",
|
||||||
"minecraft-protocol",
|
"minecraft-protocol",
|
||||||
"rand 0.8.4",
|
"rand 0.8.4",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@@ -21,6 +21,7 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
bytes = "1.1"
|
bytes = "1.1"
|
||||||
futures = { version = "0.3", default-features = false }
|
futures = { version = "0.3", default-features = false }
|
||||||
|
libc = "0.2"
|
||||||
minecraft-protocol = { git = "https://github.com/timvisee/minecraft-protocol", rev = "c578492" }
|
minecraft-protocol = { git = "https://github.com/timvisee/minecraft-protocol", rev = "c578492" }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "macros", "time", "process"] }
|
tokio = { version = "1", default-features = false, features = ["rt", "rt-multi-thread", "io-util", "net", "macros", "time", "process", "signal"] }
|
||||||
|
@@ -8,7 +8,7 @@ pub(crate) const ADDRESS_PUBLIC: &str = "127.0.0.1:9090";
|
|||||||
pub(crate) const ADDRESS_PROXY: &str = "127.0.0.1:9091";
|
pub(crate) const ADDRESS_PROXY: &str = "127.0.0.1:9091";
|
||||||
|
|
||||||
/// Server description shown when server is starting.
|
/// Server description shown when server is starting.
|
||||||
pub(crate) const LABEL_SERVER_SLEEPING: &str = "☠ Server is sleeping...\n§2☻ Join to start it up";
|
pub(crate) const LABEL_SERVER_SLEEPING: &str = "☠ Server is sleeping\n§2☻ Join to start it up";
|
||||||
|
|
||||||
/// Server description shown when server is starting.
|
/// Server description shown when server is starting.
|
||||||
pub(crate) const LABEL_SERVER_STARTING: &str = "§2☻ Server is starting...\n§7⌛ Please wait...";
|
pub(crate) const LABEL_SERVER_STARTING: &str = "§2☻ Server is starting...\n§7⌛ Please wait...";
|
||||||
|
50
src/main.rs
50
src/main.rs
@@ -1,7 +1,8 @@
|
|||||||
pub mod config;
|
pub(crate) mod config;
|
||||||
pub mod monitor;
|
pub(crate) mod monitor;
|
||||||
pub mod protocol;
|
pub(crate) mod protocol;
|
||||||
pub mod types;
|
pub(crate) mod server;
|
||||||
|
pub(crate) mod types;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -19,11 +20,10 @@ use tokio::io::AsyncReadExt;
|
|||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::net::tcp::ReadHalf;
|
use tokio::net::tcp::ReadHalf;
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio::process::Command;
|
|
||||||
|
|
||||||
use config::*;
|
use config::*;
|
||||||
use monitor::ServerState;
|
|
||||||
use protocol::{Client, ClientState, RawPacket};
|
use protocol::{Client, ClientState, RawPacket};
|
||||||
|
use server::ServerState;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), ()> {
|
async fn main() -> Result<(), ()> {
|
||||||
@@ -32,7 +32,7 @@ async fn main() -> Result<(), ()> {
|
|||||||
ADDRESS_PUBLIC, ADDRESS_PROXY,
|
ADDRESS_PUBLIC, ADDRESS_PROXY,
|
||||||
);
|
);
|
||||||
|
|
||||||
let server_state = monitor::ServerState::default().shared();
|
let server_state = Arc::new(ServerState::default());
|
||||||
|
|
||||||
// Listen for new connections
|
// Listen for new connections
|
||||||
// TODO: do not drop error here
|
// TODO: do not drop error here
|
||||||
@@ -42,6 +42,17 @@ async fn main() -> Result<(), ()> {
|
|||||||
let addr = ADDRESS_PROXY.parse().expect("invalid server IP");
|
let addr = ADDRESS_PROXY.parse().expect("invalid server IP");
|
||||||
tokio::spawn(monitor::monitor_server(addr, server_state.clone()));
|
tokio::spawn(monitor::monitor_server(addr, server_state.clone()));
|
||||||
|
|
||||||
|
let sub = server_state.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
tokio::signal::ctrl_c().await.unwrap();
|
||||||
|
if !sub.kill_server() {
|
||||||
|
// TODO: gracefully kill itself instead
|
||||||
|
std::process::exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Proxy all incomming connections
|
// Proxy all incomming connections
|
||||||
while let Ok((inbound, _)) = listener.accept().await {
|
while let Ok((inbound, _)) = listener.accept().await {
|
||||||
let client = Client::default();
|
let client = Client::default();
|
||||||
@@ -164,7 +175,7 @@ async fn status_server(
|
|||||||
// Start server if not starting yet
|
// Start server if not starting yet
|
||||||
if !server.starting() {
|
if !server.starting() {
|
||||||
server.set_starting(true);
|
server.set_starting(true);
|
||||||
tokio::spawn(start_server(server).map(|_| ()));
|
tokio::spawn(server::start(server).map(|_| ()));
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -223,10 +234,10 @@ async fn status_server(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show unhandled packet warning
|
// // Show unhandled packet warning
|
||||||
eprintln!("Received unhandled packet:");
|
// eprintln!("Received unhandled packet:");
|
||||||
eprintln!("- State: {:?}", client.state());
|
// eprintln!("- State: {:?}", client.state());
|
||||||
eprintln!("- Packet ID: {}", packet.id);
|
// eprintln!("- Packet ID: {}", packet.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gracefully close connection
|
// Gracefully close connection
|
||||||
@@ -259,18 +270,3 @@ async fn proxy(mut inbound: TcpStream, addr_target: String) -> Result<(), ()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start Minecraft server.
|
|
||||||
async fn start_server(state: Arc<ServerState>) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let mut cmd = Command::new(SERVER_CMD);
|
|
||||||
|
|
||||||
let status = cmd.status().await?;
|
|
||||||
|
|
||||||
println!("Server exited, status: {}", status);
|
|
||||||
|
|
||||||
// Reset online and starting state
|
|
||||||
state.set_online(false);
|
|
||||||
state.set_starting(false);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
// TODO: remove all unwraps/expects here!
|
// TODO: remove all unwraps/expects here!
|
||||||
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -15,6 +14,7 @@ use tokio::io::AsyncWriteExt;
|
|||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
use crate::protocol::{self, ClientState, RawPacket};
|
use crate::protocol::{self, ClientState, RawPacket};
|
||||||
|
use crate::server::ServerState;
|
||||||
|
|
||||||
/// Minecraft protocol version used when polling server status.
|
/// Minecraft protocol version used when polling server status.
|
||||||
const PROTOCOL_VERSION: i32 = 754;
|
const PROTOCOL_VERSION: i32 = 754;
|
||||||
@@ -25,43 +25,6 @@ const MONITOR_PING_INTERVAL: u64 = 2;
|
|||||||
/// Ping timeout in seconds.
|
/// Ping timeout in seconds.
|
||||||
const PING_TIMEOUT: u64 = 8;
|
const PING_TIMEOUT: u64 = 8;
|
||||||
|
|
||||||
/// Shared server state.
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct ServerState {
|
|
||||||
/// Whether the server is online.
|
|
||||||
online: AtomicBool,
|
|
||||||
|
|
||||||
/// Whether the server is starting.
|
|
||||||
starting: AtomicBool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ServerState {
|
|
||||||
/// Transform into shared instance.
|
|
||||||
pub fn shared(self) -> Arc<Self> {
|
|
||||||
Arc::new(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the server is online.
|
|
||||||
pub fn online(&self) -> bool {
|
|
||||||
self.online.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set whether the server is online.
|
|
||||||
pub fn set_online(&self, online: bool) {
|
|
||||||
self.online.store(online, Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the server is starting.
|
|
||||||
pub fn starting(&self) -> bool {
|
|
||||||
self.starting.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set whether the server is starting.
|
|
||||||
pub fn set_starting(&self, starting: bool) {
|
|
||||||
self.starting.store(starting, Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Poll server state.
|
/// Poll server state.
|
||||||
///
|
///
|
||||||
/// Returns `true` if a ping succeeded.
|
/// Returns `true` if a ping succeeded.
|
||||||
|
@@ -46,10 +46,10 @@ impl ClientState {
|
|||||||
/// From state ID.
|
/// From state ID.
|
||||||
pub fn from_id(id: i32) -> Option<Self> {
|
pub fn from_id(id: i32) -> Option<Self> {
|
||||||
match id {
|
match id {
|
||||||
// 0 => Self::Handshake,
|
0 => Some(Self::Handshake),
|
||||||
1 => Some(Self::Status),
|
1 => Some(Self::Status),
|
||||||
2 => Some(Self::Login),
|
2 => Some(Self::Login),
|
||||||
// 2 => Self::Play,
|
3 => Some(Self::Play),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,10 +57,10 @@ impl ClientState {
|
|||||||
/// Get state ID.
|
/// Get state ID.
|
||||||
pub fn to_id(self) -> i32 {
|
pub fn to_id(self) -> i32 {
|
||||||
match self {
|
match self {
|
||||||
Self::Handshake => unimplemented!(),
|
Self::Handshake => 0,
|
||||||
Self::Status => 1,
|
Self::Status => 1,
|
||||||
Self::Login => 2,
|
Self::Login => 2,
|
||||||
Self::Play => unimplemented!(),
|
Self::Play => 3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
92
src/server.rs
Normal file
92
src/server.rs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
use crate::config::SERVER_CMD;
|
||||||
|
|
||||||
|
/// Shared server state.
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct ServerState {
|
||||||
|
/// Whether the server is online.
|
||||||
|
online: AtomicBool,
|
||||||
|
|
||||||
|
/// Whether the server is starting.
|
||||||
|
starting: AtomicBool,
|
||||||
|
|
||||||
|
/// Server PID.
|
||||||
|
pid: Mutex<Option<u32>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerState {
|
||||||
|
/// Whether the server is online.
|
||||||
|
pub fn online(&self) -> bool {
|
||||||
|
self.online.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether the server is online.
|
||||||
|
pub fn set_online(&self, online: bool) {
|
||||||
|
self.online.store(online, Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the server is starting.
|
||||||
|
pub fn starting(&self) -> bool {
|
||||||
|
self.starting.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether the server is starting.
|
||||||
|
pub fn set_starting(&self, starting: bool) {
|
||||||
|
self.starting.store(starting, Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Kill any running server.
|
||||||
|
pub fn kill_server(&self) -> bool {
|
||||||
|
if let Some(pid) = *self.pid.lock().unwrap() {
|
||||||
|
eprintln!("[lazymc] Sending kill signal to server");
|
||||||
|
kill_gracefully(pid);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set server PID.
|
||||||
|
pub fn set_pid(&self, pid: Option<u32>) {
|
||||||
|
*self.pid.lock().unwrap() = pid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start Minecraft server.
|
||||||
|
pub async fn start(state: Arc<ServerState>) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut cmd = Command::new(SERVER_CMD);
|
||||||
|
|
||||||
|
eprintln!("[lazymc] Starting server...");
|
||||||
|
let mut child = cmd.spawn()?;
|
||||||
|
|
||||||
|
state.set_pid(Some(child.id().expect("unknown server PID")));
|
||||||
|
|
||||||
|
let status = child.wait().await?;
|
||||||
|
eprintln!("[lazymc] Server stopped (status: {})\n", status);
|
||||||
|
|
||||||
|
// Reset online and starting state
|
||||||
|
// TODO: also set this when returning early due to error
|
||||||
|
state.set_pid(None);
|
||||||
|
state.set_online(false);
|
||||||
|
state.set_starting(false);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gracefully kill process.
|
||||||
|
fn kill_gracefully(pid: u32) {
|
||||||
|
#[cfg(unix)]
|
||||||
|
unsafe {
|
||||||
|
libc::kill(pid as i32, libc::SIGINT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
{
|
||||||
|
// TODO: implement this for Windows
|
||||||
|
unimplemented!();
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user