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

View File

@ -1,32 +1,52 @@
use crate::server::ServerProcess;
use flate2::write::GzEncoder;
use flate2::Compression;
use std::fs::File;
use std::io;
use std::io::Write;
use std::path::{Path, PathBuf};
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 {
type_: ServerType,
version: String,
java: String,
jar: PathBuf,
config_dir: PathBuf,
world_dir: PathBuf,
backup_dir: PathBuf,
xms: u32,
xmx: u32,
child: Option<Child>,
}
impl ServerCommand {
pub fn new(jar: &str, config_dir: &str, world_dir: &str) -> Self {
pub fn new(type_: ServerType, version: &str) -> Self {
ServerCommand {
type_,
version: String::from(version),
java: String::from("java"),
jar: Path::new(jar).canonicalize().unwrap(),
config_dir: Path::new(config_dir).canonicalize().unwrap(),
world_dir: Path::new(world_dir).canonicalize().unwrap(),
jar: PathBuf::from("server.jar"),
config_dir: PathBuf::from("config"),
world_dir: PathBuf::from("worlds"),
backup_dir: PathBuf::from("backups"),
xms: 1024,
xmx: 2048,
child: None,
}
}
@ -36,6 +56,27 @@ impl ServerCommand {
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 {
self.xms = xms;
self.xmx = xmx;
@ -52,19 +93,32 @@ impl ServerCommand {
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()?;
let child = Command::new(&self.java)
.current_dir(&self.config_dir)
.current_dir(&config_dir)
.arg("-jar")
.arg(&self.jar)
.arg(&jar)
.arg("--universe")
.arg(&self.world_dir)
.arg(&world_dir)
.arg("--nogui")
.stdin(Stdio::piped())
.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 process;
pub use command::ServerCommand;
pub use command::{ServerCommand, ServerType};
pub use process::ServerProcess;

View File

@ -1,3 +1,4 @@
use crate::server::ServerType;
use flate2::write::GzEncoder;
use flate2::Compression;
use std::fs::File;
@ -8,50 +9,91 @@ use std::process::Child;
use std::process::{Command, Stdio};
pub struct ServerProcess {
type_: ServerType,
version: String,
config_dir: PathBuf,
world_dir: PathBuf,
backup_dir: PathBuf,
child: Child,
}
impl ServerProcess {
pub fn new(child: Child) -> ServerProcess {
ServerProcess { child }
}
pub fn send_command(&mut self, cmd: &str) {
match cmd.trim() {
"stop" | "exit" => self.stop(),
"backup" => self.backup(),
s => self.custom(s),
pub fn new(
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,
}
}
fn custom(&mut self, cmd: &str) {
let mut stdin = self.child.stdin.take().unwrap();
stdin
.write_all(format!("{}\n", cmd.trim()).as_bytes())
.unwrap();
stdin.flush().unwrap();
pub fn send_command(&mut self, cmd: &str) -> std::io::Result<()> {
match cmd.trim() {
"stop" | "exit" => self.stop()?,
"backup" => self.backup()?,
s => self.custom(s)?,
}
Ok(())
}
pub fn stop(&mut self) {
self.custom("stop");
self.child.wait();
fn custom(&mut self, cmd: &str) -> std::io::Result<()> {
let mut stdin = self.child.stdin.as_ref().unwrap();
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
self.custom("save-off");
self.custom("save-all");
self.custom("save-off")?;
self.custom("save-all")?;
// Create a gzip-compressed tarball of the worlds folder
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 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")
}
}