feat: some improvements

signal-handling
Jef Roosens 2023-06-03 14:18:39 +02:00
parent d176fac420
commit 826565dbae
Signed by: Jef Roosens
GPG Key ID: B75D4F293C7052DB
4 changed files with 150 additions and 48 deletions

View File

@ -1,10 +1,14 @@
use std::io;
mod server; mod server;
use server::{ServerCommand, ServerType};
use std::io;
fn main() { fn main() {
let mut server = let mut server = server::ServerCommand::new(ServerType::Paper, "1.19.4-545")
server::ServerCommand::new("paper-1.19.4-545.jar", "data/config", "data/worlds") .jar("paper-1.19.4-545.jar")
.config("data/config")
.world("data/worlds")
.backup("data/backups")
.mem(512, 1024) .mem(512, 1024)
.spawn() .spawn()
.unwrap(); .unwrap();
@ -16,7 +20,9 @@ fn main() {
input.clear(); input.clear();
stdin.read_line(input); stdin.read_line(input);
println!("input: {}", input.trim()); println!("input: {}", input.trim());
server.send_command(input); if let Err(e) = server.send_command(input) {
println!("{}", e);
};
if input.trim() == "stop" { if input.trim() == "stop" {
break; break;

View File

@ -1,32 +1,52 @@
use crate::server::ServerProcess; use crate::server::ServerProcess;
use flate2::write::GzEncoder;
use flate2::Compression;
use std::fs::File; use std::fs::File;
use std::io;
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio}; use std::process::{Child, Command, Stdio};
use std::fmt;
pub enum ServerType {
Paper,
Forge,
Vanilla,
}
impl fmt::Display for ServerType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
ServerType::Paper => "PaperMC",
ServerType::Forge => "Forge",
ServerType::Vanilla => "Vanilla",
};
write!(f, "{}", s)
}
}
pub struct ServerCommand { pub struct ServerCommand {
type_: ServerType,
version: String,
java: String, java: String,
jar: PathBuf, jar: PathBuf,
config_dir: PathBuf, config_dir: PathBuf,
world_dir: PathBuf, world_dir: PathBuf,
backup_dir: PathBuf,
xms: u32, xms: u32,
xmx: u32, xmx: u32,
child: Option<Child>,
} }
impl ServerCommand { impl ServerCommand {
pub fn new(jar: &str, config_dir: &str, world_dir: &str) -> Self { pub fn new(type_: ServerType, version: &str) -> Self {
ServerCommand { ServerCommand {
type_,
version: String::from(version),
java: String::from("java"), java: String::from("java"),
jar: Path::new(jar).canonicalize().unwrap(), jar: PathBuf::from("server.jar"),
config_dir: Path::new(config_dir).canonicalize().unwrap(), config_dir: PathBuf::from("config"),
world_dir: Path::new(world_dir).canonicalize().unwrap(), world_dir: PathBuf::from("worlds"),
backup_dir: PathBuf::from("backups"),
xms: 1024, xms: 1024,
xmx: 2048, xmx: 2048,
child: None,
} }
} }
@ -36,6 +56,27 @@ impl ServerCommand {
self self
} }
pub fn jar<T: AsRef<Path>>(mut self, path: T) -> Self {
self.jar = PathBuf::from(path.as_ref());
self
}
pub fn config<T: AsRef<Path>>(mut self, path: T) -> Self {
self.config_dir = PathBuf::from(path.as_ref());
self
}
pub fn world<T: AsRef<Path>>(mut self, path: T) -> Self {
self.world_dir = PathBuf::from(path.as_ref());
self
}
pub fn backup<T: AsRef<Path>>(mut self, path: T) -> Self {
self.backup_dir = PathBuf::from(path.as_ref());
self
}
pub fn mem(mut self, xms: u32, xmx: u32) -> Self { pub fn mem(mut self, xms: u32, xmx: u32) -> Self {
self.xms = xms; self.xms = xms;
self.xmx = xmx; self.xmx = xmx;
@ -52,19 +93,32 @@ impl ServerCommand {
Ok(()) Ok(())
} }
pub fn spawn(&mut self) -> std::io::Result<ServerProcess> { pub fn spawn(self) -> std::io::Result<ServerProcess> {
// To avoid any issues, we use absolute paths for everything when spawning the process
let jar = self.jar.canonicalize()?;
let config_dir = self.config_dir.canonicalize()?;
let world_dir = self.world_dir.canonicalize()?;
let backup_dir = self.backup_dir.canonicalize()?;
self.accept_eula()?; self.accept_eula()?;
let child = Command::new(&self.java) let child = Command::new(&self.java)
.current_dir(&self.config_dir) .current_dir(&config_dir)
.arg("-jar") .arg("-jar")
.arg(&self.jar) .arg(&jar)
.arg("--universe") .arg("--universe")
.arg(&self.world_dir) .arg(&world_dir)
.arg("--nogui") .arg("--nogui")
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.spawn()?; .spawn()?;
Ok(ServerProcess::new(child)) Ok(ServerProcess::new(
self.type_,
self.version,
config_dir,
world_dir,
backup_dir,
child,
))
} }
} }

View File

@ -1,5 +1,5 @@
mod command; mod command;
mod process; mod process;
pub use command::ServerCommand; pub use command::{ServerCommand, ServerType};
pub use process::ServerProcess; pub use process::ServerProcess;

View File

@ -1,3 +1,4 @@
use crate::server::ServerType;
use flate2::write::GzEncoder; use flate2::write::GzEncoder;
use flate2::Compression; use flate2::Compression;
use std::fs::File; use std::fs::File;
@ -8,50 +9,91 @@ use std::process::Child;
use std::process::{Command, Stdio}; use std::process::{Command, Stdio};
pub struct ServerProcess { pub struct ServerProcess {
type_: ServerType,
version: String,
config_dir: PathBuf,
world_dir: PathBuf,
backup_dir: PathBuf,
child: Child, child: Child,
} }
impl ServerProcess { impl ServerProcess {
pub fn new(child: Child) -> ServerProcess { pub fn new(
ServerProcess { child } type_: ServerType,
version: String,
config_dir: PathBuf,
world_dir: PathBuf,
backup_dir: PathBuf,
child: Child,
) -> ServerProcess {
ServerProcess {
type_,
version,
config_dir,
world_dir,
backup_dir,
child,
}
} }
pub fn send_command(&mut self, cmd: &str) { pub fn send_command(&mut self, cmd: &str) -> std::io::Result<()> {
match cmd.trim() { match cmd.trim() {
"stop" | "exit" => self.stop(), "stop" | "exit" => self.stop()?,
"backup" => self.backup(), "backup" => self.backup()?,
s => self.custom(s), s => self.custom(s)?,
}
} }
fn custom(&mut self, cmd: &str) { Ok(())
let mut stdin = self.child.stdin.take().unwrap();
stdin
.write_all(format!("{}\n", cmd.trim()).as_bytes())
.unwrap();
stdin.flush().unwrap();
} }
pub fn stop(&mut self) { fn custom(&mut self, cmd: &str) -> std::io::Result<()> {
self.custom("stop"); let mut stdin = self.child.stdin.as_ref().unwrap();
self.child.wait(); stdin.write_all(format!("{}\n", cmd.trim()).as_bytes())?;
stdin.flush()?;
Ok(())
} }
pub fn backup(&mut self) { pub fn stop(&mut self) -> std::io::Result<()> {
self.custom("stop")?;
self.child.wait()?;
Ok(())
}
pub fn backup(&mut self) -> std::io::Result<()> {
// Make sure the server isn't modifying the files during the backup // Make sure the server isn't modifying the files during the backup
self.custom("save-off"); self.custom("save-off")?;
self.custom("save-all"); self.custom("save-all")?;
// Create a gzip-compressed tarball of the worlds folder // Create a gzip-compressed tarball of the worlds folder
let filename = format!( let filename = format!(
"{}", "{}",
chrono::offset::Local::now().format("backups/%Y-%m-%d_%H-%M-%S.tar.gz") chrono::offset::Local::now().format("%Y-%m-%d_%H-%M-%S.tar.gz")
); );
let tar_gz = std::fs::File::create(filename).unwrap(); let path = self.backup_dir.join(&filename);
let tar_gz = std::fs::File::create(path)?;
let enc = GzEncoder::new(tar_gz, Compression::default()); let enc = GzEncoder::new(tar_gz, Compression::default());
let mut tar = tar::Builder::new(enc); let mut tar = tar::Builder::new(enc);
tar.append_dir_all("worlds", "worlds").unwrap();
self.custom("save-on"); tar.append_dir_all("worlds", &self.world_dir)?;
// We don't store all files in the config, as this would include caches
tar.append_path_with_name(self.config_dir.join("server.properties"), "config/server.properties")?;
// We add a file to the backup describing for what version it was made
let info = format!("{} {}", self.type_, self.version);
let info_bytes = info.as_bytes();
let mut header = tar::Header::new_gnu();
header.set_size(info_bytes.len().try_into().unwrap());
tar.append_data(&mut header, "info.txt", info_bytes)?;
// tar.append_dir_all("config", &self.config_dir)?;
//
// Backup file gets finalized in the drop
self.custom("save-on")
} }
} }