Add option to automatically rewrite server.properties file
This tries to fiend the server.properties file of the Minecraft server, and sets the correct IP and port as configured within lazymc. This is enabled by default.
This commit is contained in:
@@ -38,3 +38,7 @@ motd_starting = "§2☻ Server is starting...\n§7⌛ Please wait..."
|
||||
|
||||
# Login (kick) message when server is starting.
|
||||
login_starting = "Server is starting... §c♥§r\n\nThis may take some time.\n\nPlease try to reconnect in a minute."
|
||||
|
||||
[advanced]
|
||||
# Automatically set internal IP and port in Minecraft server.properties file.
|
||||
rewrite_server_properties = true
|
||||
|
@@ -1,8 +1,10 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::ArgMatches;
|
||||
|
||||
use crate::config;
|
||||
use crate::config::{self, Config};
|
||||
use crate::mc::server_properties;
|
||||
use crate::service;
|
||||
|
||||
/// Start lazymc.
|
||||
@@ -10,7 +12,37 @@ pub async fn invoke(matches: &ArgMatches) -> Result<(), ()> {
|
||||
// Load config
|
||||
let config = Arc::new(config::load(matches));
|
||||
|
||||
// Rewrite server server.properties file
|
||||
rewrite_server_properties(&config);
|
||||
|
||||
// Start server service
|
||||
// TODO: start tokio runtime here?
|
||||
service::server::service(config).await
|
||||
}
|
||||
|
||||
/// Rewrite server server.properties file with correct internal IP and port.
|
||||
fn rewrite_server_properties(config: &Config) {
|
||||
// Rewrite must be enabled
|
||||
if !config.advanced.rewrite_server_properties {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure server directory is set, it must exist
|
||||
let dir = match &config.server.directory {
|
||||
Some(dir) => dir,
|
||||
None => {
|
||||
warn!(target: "lazymc", "Not rewriting {} file, server directory not configured (server.directory)", server_properties::FILE);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Build list of changes
|
||||
let changes = HashMap::from([
|
||||
("server-ip", config.server.address.ip().to_string()),
|
||||
("server-port", config.server.address.port().to_string()),
|
||||
("query.port", config.server.address.port().to_string()),
|
||||
]);
|
||||
|
||||
// Rewrite file
|
||||
server_properties::rewrite_dir(dir, changes)
|
||||
}
|
||||
|
@@ -68,6 +68,9 @@ pub struct Config {
|
||||
|
||||
/// Messages, shown to the user.
|
||||
pub messages: Messages,
|
||||
|
||||
/// Advanced configuration.
|
||||
pub advanced: Advanced,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -101,6 +104,7 @@ pub struct Server {
|
||||
pub address: SocketAddr,
|
||||
|
||||
/// Immediately wake server when starting lazymc.
|
||||
#[serde(default)]
|
||||
pub wake_on_start: bool,
|
||||
}
|
||||
|
||||
@@ -111,12 +115,11 @@ pub struct Time {
|
||||
pub sleep_after: u32,
|
||||
|
||||
/// Minimum time in seconds to stay online when server is started.
|
||||
// TODO: implement this
|
||||
#[serde(default, alias = "minimum_online_time")]
|
||||
pub min_online_time: u32,
|
||||
}
|
||||
|
||||
/// Messages.
|
||||
/// Message configuration.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Messages {
|
||||
/// MOTD when server is sleeping.
|
||||
@@ -128,3 +131,10 @@ pub struct Messages {
|
||||
/// Login message when server is starting.
|
||||
pub login_starting: String,
|
||||
}
|
||||
|
||||
/// Advanced configuration.
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Advanced {
|
||||
/// Rewrite server.properties.
|
||||
pub rewrite_server_properties: bool,
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ extern crate log;
|
||||
pub(crate) mod action;
|
||||
pub(crate) mod cli;
|
||||
pub(crate) mod config;
|
||||
pub(crate) mod mc;
|
||||
pub(crate) mod monitor;
|
||||
pub(crate) mod proto;
|
||||
pub(crate) mod proxy;
|
||||
|
1
src/mc/mod.rs
Normal file
1
src/mc/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod server_properties;
|
148
src/mc/server_properties.rs
Normal file
148
src/mc/server_properties.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// File name.
|
||||
pub const FILE: &str = "server.properties";
|
||||
|
||||
/// EOL in server.properties file.
|
||||
const EOL: &str = "\r\n";
|
||||
|
||||
/// Try to rewrite changes in server.properties file in dir.
|
||||
///
|
||||
/// Prints an error and stops on failure.
|
||||
pub fn rewrite_dir(dir: &Path, changes: HashMap<&str, String>) {
|
||||
if changes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure directory exists
|
||||
if !dir.is_dir() {
|
||||
warn!(target: "lazymc",
|
||||
"Not rewriting {} file, configured server directory doesn't exist: {}",
|
||||
FILE,
|
||||
dir.to_str().unwrap_or("?")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Rewrite file
|
||||
rewrite_file(&dir.join(FILE), changes)
|
||||
}
|
||||
|
||||
/// Try to rewrite changes in server.properties file.
|
||||
///
|
||||
/// Prints an error and stops on failure.
|
||||
pub fn rewrite_file(file: &Path, changes: HashMap<&str, String>) {
|
||||
if changes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// File must exist
|
||||
if !file.is_file() {
|
||||
warn!(target: "lazymc",
|
||||
"Not writing {} file, not found at: {}",
|
||||
FILE,
|
||||
file.to_str().unwrap_or("?"),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Read contents
|
||||
let contents = match fs::read_to_string(&file) {
|
||||
Ok(contents) => contents,
|
||||
Err(err) => {
|
||||
error!(target: "lazymc",
|
||||
"Failed to rewrite {} file, could not load: {}",
|
||||
FILE,
|
||||
err,
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Rewrite file contents, return if nothing changed
|
||||
let contents = match rewrite_contents(contents, changes) {
|
||||
Some(contents) => contents,
|
||||
None => {
|
||||
debug!(target: "lazymc",
|
||||
"Not rewriting {} file, no changes to apply",
|
||||
FILE,
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Write changes
|
||||
match fs::write(file, contents) {
|
||||
Ok(_) => {
|
||||
info!(target: "lazymc",
|
||||
"Rewritten {} file with correct IP and port",
|
||||
FILE,
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
error!(target: "lazymc",
|
||||
"Failed to rewrite {} file, could not save changes: {}",
|
||||
FILE,
|
||||
err,
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Rewrite file contents with new properties.
|
||||
///
|
||||
/// Returns new file contents if anything has changed.
|
||||
fn rewrite_contents(contents: String, mut changes: HashMap<&str, String>) -> Option<String> {
|
||||
if changes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut changed = false;
|
||||
|
||||
// Build new file
|
||||
let mut new_contents: String = contents
|
||||
.lines()
|
||||
.map(|line| {
|
||||
let mut line = line.to_owned();
|
||||
|
||||
// Skip comments or empty lines
|
||||
let trim = line.trim();
|
||||
if trim.starts_with("#") || trim.is_empty() {
|
||||
return line;
|
||||
}
|
||||
|
||||
// Try to split property
|
||||
let (key, value) = match line.split_once("=") {
|
||||
Some(result) => result,
|
||||
None => return line,
|
||||
};
|
||||
|
||||
// Take any new value, and update it
|
||||
if let Some((_, new)) = changes.remove_entry(key.trim().to_lowercase().as_str()) {
|
||||
if value != new {
|
||||
line = format!("{}={}", key, new);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
line
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(EOL);
|
||||
|
||||
// Append any missed changes
|
||||
for (key, value) in changes {
|
||||
new_contents += &format!("{}{}={}", EOL, key, value);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Return new contents if changed
|
||||
if changed {
|
||||
Some(new_contents)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user