246 lines
6.7 KiB
Rust
246 lines
6.7 KiB
Rust
use crate::backup::Manager as BackupManager;
|
|
use crate::server::ServerProcess;
|
|
use clap::ValueEnum;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::fmt;
|
|
use std::fs::File;
|
|
use std::io::Write;
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::{Command, Stdio};
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize)]
|
|
pub enum ServerType {
|
|
Unknown,
|
|
Paper,
|
|
Forge,
|
|
Vanilla,
|
|
}
|
|
|
|
impl fmt::Display for ServerType {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
let s = match self {
|
|
ServerType::Unknown => "Unknown",
|
|
ServerType::Paper => "PaperMC",
|
|
ServerType::Forge => "Forge",
|
|
ServerType::Vanilla => "Vanilla",
|
|
};
|
|
|
|
write!(f, "{}", s)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Serialize, Deserialize)]
|
|
pub struct Metadata {
|
|
pub server_type: ServerType,
|
|
pub server_version: String,
|
|
}
|
|
|
|
pub struct ServerCommand {
|
|
type_: ServerType,
|
|
version: String,
|
|
java: String,
|
|
jar: PathBuf,
|
|
config_dir: PathBuf,
|
|
world_dir: PathBuf,
|
|
backup_dir: PathBuf,
|
|
xms: u64,
|
|
xmx: u64,
|
|
chain_len: u64,
|
|
chains_to_keep: u64,
|
|
}
|
|
|
|
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,
|
|
chain_len: 4,
|
|
chains_to_keep: 7,
|
|
}
|
|
}
|
|
|
|
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 chain_len(mut self, v: u64) -> Self {
|
|
self.chain_len = v;
|
|
self
|
|
}
|
|
|
|
pub fn chains_to_keep(mut self, v: u64) -> Self {
|
|
self.chains_to_keep = v;
|
|
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 mut manager = BackupManager::new(
|
|
self.backup_dir.clone(),
|
|
self.config_dir.clone(),
|
|
self.world_dir.clone(),
|
|
metadata,
|
|
self.chain_len,
|
|
self.chains_to_keep,
|
|
);
|
|
manager.load()?;
|
|
|
|
let mut cmd = self.create_cmd();
|
|
self.accept_eula()?;
|
|
let child = cmd.spawn()?;
|
|
|
|
Ok(ServerProcess::new(manager, 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(())
|
|
}
|
|
}
|