alex/src/server/command.rs

211 lines
6.0 KiB
Rust

use crate::backup::ManagerConfig;
use crate::backup::MetaManager;
use crate::server::{Metadata, ServerProcess, ServerType};
use std::fmt;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
pub struct ServerCommand {
type_: ServerType,
version: String,
java: String,
jar: PathBuf,
config_dir: PathBuf,
world_dir: PathBuf,
backup_dir: PathBuf,
xms: u64,
xmx: u64,
managers: Vec<ManagerConfig>,
}
impl ServerCommand {
pub fn new(type_: ServerType, version: &str) -> Self {
ServerCommand {
type_,
version: String::from(version),
java: String::from("java"),
jar: PathBuf::from("server.jar"),
config_dir: PathBuf::from("config"),
world_dir: PathBuf::from("worlds"),
backup_dir: PathBuf::from("backups"),
xms: 1024,
xmx: 2048,
managers: Vec::new(),
}
}
pub fn java(mut self, java: &str) -> Self {
self.java = String::from(java);
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 xms(mut self, v: u64) -> Self {
self.xms = v;
self
}
pub fn xmx(mut self, v: u64) -> Self {
self.xmx = v;
self
}
pub fn managers(mut self, configs: Vec<ManagerConfig>) -> Self {
self.managers = configs;
self
}
fn accept_eula(&self) -> std::io::Result<()> {
let mut eula_path = self.config_dir.clone();
eula_path.push("eula.txt");
let mut eula_file = File::create(eula_path)?;
eula_file.write_all(b"eula=true")?;
Ok(())
}
/// Canonicalize all paths to absolute paths. Without this command, all paths will be
/// interpreted relatively from the config directory.
pub fn canonicalize(&mut self) -> std::io::Result<()> {
// To avoid any issues, we use absolute paths for everything when spawning the process
self.jar = self.jar.canonicalize()?;
self.config_dir = self.config_dir.canonicalize()?;
self.world_dir = self.world_dir.canonicalize()?;
self.backup_dir = self.backup_dir.canonicalize()?;
Ok(())
}
fn create_cmd(&self) -> std::process::Command {
let mut cmd = Command::new(&self.java);
// Apply JVM optimisation flags
// https://aikar.co/2018/07/02/tuning-the-jvm-g1gc-garbage-collector-flags-for-minecraft/
cmd.arg(format!("-Xms{}M", self.xms))
.arg(format!("-Xmx{}M", self.xmx))
.args([
"-XX:+UseG1GC",
"-XX:+ParallelRefProcEnabled",
"-XX:MaxGCPauseMillis=200",
"-XX:+UnlockExperimentalVMOptions",
"-XX:+DisableExplicitGC",
"-XX:+AlwaysPreTouch",
]);
if self.xms > 12 * 1024 {
cmd.args([
"-XX:G1NewSizePercent=40",
"-XX:G1MaxNewSizePercent=50",
"-XX:G1HeapRegionSize=16M",
"-XX:G1ReservePercent=15",
]);
} else {
cmd.args([
"-XX:G1NewSizePercent=30",
"-XX:G1MaxNewSizePercent=40",
"-XX:G1HeapRegionSize=8M",
"-XX:G1ReservePercent=20",
]);
}
cmd.args(["-XX:G1HeapWastePercent=5", "-XX:G1MixedGCCountTarget=4"]);
if self.xms > 12 * 1024 {
cmd.args(["-XX:InitiatingHeapOccupancyPercent=20"]);
} else {
cmd.args(["-XX:InitiatingHeapOccupancyPercent=15"]);
}
cmd.args([
"-XX:G1MixedGCLiveThresholdPercent=90",
"-XX:G1RSetUpdatingPauseTimePercent=5",
"-XX:SurvivorRatio=32",
"-XX:+PerfDisableSharedMem",
"-XX:MaxTenuringThreshold=1",
"-Dusing.aikars.flags=https://mcflags.emc.gs",
"-Daikars.new.flags=true",
]);
cmd.current_dir(&self.config_dir)
.arg("-jar")
.arg(&self.jar)
.arg("--universe")
.arg(&self.world_dir)
.arg("--nogui")
.stdin(Stdio::piped());
cmd
}
pub fn spawn(&mut self) -> std::io::Result<ServerProcess> {
let metadata = Metadata {
server_type: self.type_,
server_version: self.version.clone(),
};
let dirs = vec![
(PathBuf::from("config"), self.config_dir.clone()),
(PathBuf::from("worlds"), self.world_dir.clone()),
];
let mut meta = MetaManager::new(self.backup_dir.clone(), dirs, metadata);
meta.add_all(&self.managers)?;
let mut cmd = self.create_cmd();
self.accept_eula()?;
let child = cmd.spawn()?;
Ok(ServerProcess::new(meta, child))
}
}
impl fmt::Display for ServerCommand {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let cmd = self.create_cmd();
writeln!(f, "Command: {}", self.java)?;
writeln!(f, "Working dir: {}", self.config_dir.as_path().display())?;
// Print command env vars
writeln!(f, "Environment:")?;
for (key, val) in cmd.get_envs().filter(|(_, v)| v.is_some()) {
let val = val.unwrap();
writeln!(f, " {}={}", key.to_string_lossy(), val.to_string_lossy())?;
}
// Print command arguments
writeln!(f, "Arguments:")?;
for arg in cmd.get_args() {
writeln!(f, " {}", arg.to_string_lossy())?;
}
Ok(())
}
}