Compare commits
4 Commits
668870b92e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 6dea63f818 | |||
| 7782e71990 | |||
| 7e7bfbc576 | |||
| e2cbd16dc8 |
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1342,6 +1342,7 @@ checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
"serde_core",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -33,4 +33,4 @@ thiserror = { version = "2.0.17", optional = true }
|
|||||||
tokio = { version = "1.48.0", features = ["process", "rt-multi-thread", "macros", "io-std", "io-util"], optional = true }
|
tokio = { version = "1.48.0", features = ["process", "rt-multi-thread", "macros", "io-std", "io-util"], optional = true }
|
||||||
tokio-stream = { version = "0.1.17", features = ["full", "io-util", "signal", "tokio-util"], optional = true }
|
tokio-stream = { version = "0.1.17", features = ["full", "io-util", "signal", "tokio-util"], optional = true }
|
||||||
tokio-util = { version = "0.7.17", features = ["full"], optional = true }
|
tokio-util = { version = "0.7.17", features = ["full"], optional = true }
|
||||||
uuid = { version = "1.19.0", features = ["v4"], optional = true }
|
uuid = { version = "1.19.0", features = ["serde", "v4"], optional = true }
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
<picture>
|
<img src="assets/banner.svg" alt="Project Banner">
|
||||||
<source media="(prefers-color-scheme: dark)" srcset="assets/banner-dark.png">
|
|
||||||
<source media="(prefers-color-scheme: light)" srcset="assets/banner-light.png">
|
|
||||||
<img src="assets/banner-light.png" alt="Project Banner">
|
|
||||||
</picture>
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> MineGuard is currently in active development and breaking updates can be merged to the main branch any time
|
> MineGuard is currently in active development and breaking updates can be merged to the main branch any time
|
||||||
|
|||||||
7
assets/banner.svg
Normal file
7
assets/banner.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 960 288" version="1.1" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><style>
|
||||||
|
:root { color-scheme: light dark; }
|
||||||
|
#Candi2 { fill: #000000; }
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
#Candi2 { fill: #ffffff; }
|
||||||
|
}
|
||||||
|
</style><rect id="Artboard1" x="0" y="0" width="960" height="288" style="fill:none;" /><path id="Candi2" d="M572.408,105.247l-0,50.499c-0,27.084 -58.055,74.003 -92.408,84.254c-34.353,-10.25 -92.408,-57.17 -92.408,-84.254l-0,-107.746l38.689,0l45.705,45.705l45.705,-45.705l54.715,0l-0,43.383l-27.358,0l0,-16.026l-16.026,-0l-57.037,57.037l-57.037,-57.037l0,79.563c0.096,2.885 9.718,14.902 14.778,20.045c14.344,14.58 34.069,28.878 50.272,35.919c16.203,-7.041 35.928,-21.339 50.272,-35.919c5.06,-5.143 14.683,-17.16 14.778,-20.045l0,-22.526l-45.705,0l27.147,-27.147l45.915,0Z" /></svg>
|
||||||
|
After Width: | Height: | Size: 941 B |
@@ -1,6 +1,6 @@
|
|||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::{Uuid, timestamp};
|
||||||
|
|
||||||
use crate::instance::InstanceStatus;
|
use crate::instance::InstanceStatus;
|
||||||
|
|
||||||
@@ -28,6 +28,11 @@ pub struct InstanceEvent {
|
|||||||
pub payload: EventPayload,
|
pub payload: EventPayload,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum InternalEvent {
|
||||||
|
ServerStarted,
|
||||||
|
}
|
||||||
|
|
||||||
impl InstanceEvent {
|
impl InstanceEvent {
|
||||||
pub fn stdout<S: Into<String>>(line: S) -> Self {
|
pub fn stdout<S: Into<String>>(line: S) -> Self {
|
||||||
let line = line.into();
|
let line = line.into();
|
||||||
@@ -54,6 +59,16 @@ impl InstanceEvent {
|
|||||||
payload,
|
payload,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new(payload: EventPayload) -> Self {
|
||||||
|
let timestamp = chrono::Utc::now();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
id: Uuid::new_v4(),
|
||||||
|
timestamp,
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for InstanceEvent {
|
impl Display for InstanceEvent {
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ pub enum StreamSource {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub struct StreamLine {
|
pub struct StreamLine {
|
||||||
line: String,
|
pub line: String,
|
||||||
source: StreamSource,
|
pub source: StreamSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StreamLine {
|
impl StreamLine {
|
||||||
@@ -29,8 +29,6 @@ impl StreamLine {
|
|||||||
|
|
||||||
pub fn stdout<S: Into<String>>(line: S) -> Self {
|
pub fn stdout<S: Into<String>>(line: S) -> Self {
|
||||||
let line = line.into();
|
let line = line.into();
|
||||||
let re = Regex::new(r#"^\[[^\]]*\]\s*\[[^\]]*\]:\s*"#).unwrap();
|
|
||||||
let line = re.replace(&line, "").to_string();
|
|
||||||
Self {
|
Self {
|
||||||
line,
|
line,
|
||||||
source: StreamSource::Stdout,
|
source: StreamSource::Stdout,
|
||||||
@@ -39,8 +37,6 @@ impl StreamLine {
|
|||||||
|
|
||||||
pub fn stderr<S: Into<String>>(line: S) -> Self {
|
pub fn stderr<S: Into<String>>(line: S) -> Self {
|
||||||
let line = line.into();
|
let line = line.into();
|
||||||
let re = Regex::new(r#"^\[[^\]]*\]\s*\[[^\]]*\]:\s*"#).unwrap();
|
|
||||||
let line = re.replace(&line, "").to_string();
|
|
||||||
Self {
|
Self {
|
||||||
line,
|
line,
|
||||||
source: StreamSource::Stderr,
|
source: StreamSource::Stderr,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ pub struct LogMeta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "mc-vanilla")]
|
#[cfg(feature = "mc-vanilla")]
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum LogLevel {
|
pub enum LogLevel {
|
||||||
Info,
|
Info,
|
||||||
Warn,
|
Warn,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ mod line;
|
|||||||
#[cfg(feature = "mc-vanilla")]
|
#[cfg(feature = "mc-vanilla")]
|
||||||
mod log;
|
mod log;
|
||||||
|
|
||||||
|
pub use event::InternalEvent;
|
||||||
pub use event::{EventPayload, InstanceEvent};
|
pub use event::{EventPayload, InstanceEvent};
|
||||||
pub use line::{StreamLine, StreamSource};
|
pub use line::{StreamLine, StreamSource};
|
||||||
#[cfg(feature = "mc-vanilla")]
|
#[cfg(feature = "mc-vanilla")]
|
||||||
|
|||||||
@@ -3,18 +3,19 @@ use std::{
|
|||||||
str::FromStr,
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
|
|
||||||
use crate::error::VersionError;
|
use crate::error::VersionError;
|
||||||
|
|
||||||
/// Identifies the type of Minecraft distribution supported by the configuration.
|
/// Identifies the type of Minecraft distribution supported by the configuration.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub enum MinecraftType {
|
pub enum MinecraftType {
|
||||||
Vanilla,
|
Vanilla,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Semantic release version parsed from strings like `1.20.4`.
|
/// Semantic release version parsed from strings like `1.20.4`.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub struct Version {
|
pub struct Version {
|
||||||
pub major: u32,
|
pub major: u32,
|
||||||
pub minor: u32,
|
pub minor: u32,
|
||||||
@@ -22,7 +23,7 @@ pub struct Version {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Snapshot version parsed from strings like `23w13b`.
|
/// Snapshot version parsed from strings like `23w13b`.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub struct Snapshot {
|
pub struct Snapshot {
|
||||||
pub year: u32,
|
pub year: u32,
|
||||||
pub week: u32,
|
pub week: u32,
|
||||||
@@ -30,7 +31,7 @@ pub struct Snapshot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Enum covering both release and snapshot Minecraft version formats.
|
/// Enum covering both release and snapshot Minecraft version formats.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub enum MinecraftVersion {
|
pub enum MinecraftVersion {
|
||||||
Release(Version),
|
Release(Version),
|
||||||
Snapshot(Snapshot),
|
Snapshot(Snapshot),
|
||||||
|
|||||||
@@ -91,6 +91,14 @@ pub enum ServerError {
|
|||||||
|
|
||||||
#[error("Failed to write to stdin")]
|
#[error("Failed to write to stdin")]
|
||||||
StdinWriteFailed,
|
StdinWriteFailed,
|
||||||
|
|
||||||
|
#[error("Failed to open eula.txt")]
|
||||||
|
NoEULA,
|
||||||
|
#[error("Failed to write eula.txt")]
|
||||||
|
WriteEULAFailed,
|
||||||
|
|
||||||
|
#[error("File io error")]
|
||||||
|
FileIO,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "events")]
|
#[cfg(feature = "events")]
|
||||||
|
|||||||
@@ -15,8 +15,12 @@ use uuid::Uuid;
|
|||||||
#[cfg(feature = "events")]
|
#[cfg(feature = "events")]
|
||||||
use crate::config::stream::InstanceEvent;
|
use crate::config::stream::InstanceEvent;
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{MinecraftType, MinecraftVersion, StreamSource, stream::EventPayload},
|
config::{
|
||||||
|
MinecraftType, MinecraftVersion, StreamSource,
|
||||||
|
stream::{EventPayload, InternalEvent},
|
||||||
|
},
|
||||||
error::{HandleError, ServerError, SubscribeError},
|
error::{HandleError, ServerError, SubscribeError},
|
||||||
|
server::domain::MineGuardConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{InstanceData, InstanceStatus};
|
use super::{InstanceData, InstanceStatus};
|
||||||
@@ -30,16 +34,25 @@ pub struct InstanceHandle {
|
|||||||
#[cfg(feature = "events")]
|
#[cfg(feature = "events")]
|
||||||
events_tx: broadcast::Sender<InstanceEvent>,
|
events_tx: broadcast::Sender<InstanceEvent>,
|
||||||
#[cfg(feature = "events")]
|
#[cfg(feature = "events")]
|
||||||
internal_tx: mpsc::Sender<InstanceEvent>,
|
internal_events_tx: mpsc::Sender<InstanceEvent>,
|
||||||
#[cfg(feature = "events")]
|
#[cfg(feature = "events")]
|
||||||
internal_rx: Option<mpsc::Receiver<InstanceEvent>>,
|
internal_events_rx: Option<mpsc::Receiver<InstanceEvent>>,
|
||||||
stdin_tx: mpsc::Sender<String>,
|
stdin_tx: mpsc::Sender<String>,
|
||||||
stdin_rx: Option<mpsc::Receiver<String>>,
|
stdin_rx: Option<mpsc::Receiver<String>>,
|
||||||
child: Option<Arc<RwLock<Child>>>,
|
child: Option<Arc<RwLock<Child>>>,
|
||||||
shutdown: CancellationToken,
|
shutdown: CancellationToken,
|
||||||
|
internal_bus_tx: broadcast::Sender<InternalEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InstanceHandle {
|
impl InstanceHandle {
|
||||||
|
pub fn new_with_config(config: MineGuardConfig) -> Result<Self, HandleError> {
|
||||||
|
InstanceHandle::new_with_params(
|
||||||
|
config.server_dir,
|
||||||
|
config.jar_path,
|
||||||
|
config.mc_version,
|
||||||
|
config.mc_type,
|
||||||
|
)
|
||||||
|
}
|
||||||
pub fn new_with_params(
|
pub fn new_with_params(
|
||||||
root_dir: PathBuf,
|
root_dir: PathBuf,
|
||||||
jar_path: PathBuf,
|
jar_path: PathBuf,
|
||||||
@@ -82,13 +95,14 @@ impl InstanceHandle {
|
|||||||
#[cfg(feature = "events")]
|
#[cfg(feature = "events")]
|
||||||
events_tx: broadcast::Sender::new(2048),
|
events_tx: broadcast::Sender::new(2048),
|
||||||
#[cfg(feature = "events")]
|
#[cfg(feature = "events")]
|
||||||
internal_tx,
|
internal_events_tx: internal_tx,
|
||||||
#[cfg(feature = "events")]
|
#[cfg(feature = "events")]
|
||||||
internal_rx: Some(internal_rx),
|
internal_events_rx: Some(internal_rx),
|
||||||
stdin_tx,
|
stdin_tx,
|
||||||
stdin_rx: Some(stdin_rx),
|
stdin_rx: Some(stdin_rx),
|
||||||
child: None,
|
child: None,
|
||||||
shutdown: CancellationToken::new(),
|
shutdown: CancellationToken::new(),
|
||||||
|
internal_bus_tx: broadcast::Sender::new(2048),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +122,7 @@ impl InstanceHandle {
|
|||||||
|
|
||||||
pub async fn start(&mut self) -> Result<(), ServerError> {
|
pub async fn start(&mut self) -> Result<(), ServerError> {
|
||||||
self.validate_start_parameters().await?;
|
self.validate_start_parameters().await?;
|
||||||
|
self.setup_loopback()?;
|
||||||
|
|
||||||
self.transition_status(InstanceStatus::Starting).await;
|
self.transition_status(InstanceStatus::Starting).await;
|
||||||
|
|
||||||
@@ -116,10 +131,23 @@ impl InstanceHandle {
|
|||||||
|
|
||||||
self.setup_stream_pumps(child)?;
|
self.setup_stream_pumps(child)?;
|
||||||
|
|
||||||
self.transition_status(InstanceStatus::Running).await;
|
|
||||||
|
|
||||||
self.setup_parser()?;
|
self.setup_parser()?;
|
||||||
|
|
||||||
|
let mut rx = self.internal_bus_tx.subscribe();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match rx.recv().await {
|
||||||
|
Ok(event) => {
|
||||||
|
if event == InternalEvent::ServerStarted {
|
||||||
|
self.transition_status(InstanceStatus::Running).await;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +178,7 @@ impl InstanceHandle {
|
|||||||
payload: EventPayload::StateChange { old, new },
|
payload: EventPayload::StateChange { old, new },
|
||||||
};
|
};
|
||||||
|
|
||||||
_ = self.internal_tx.send(event).await;
|
_ = self.internal_events_tx.send(event).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_start_command(&self) -> process::Command {
|
fn build_start_command(&self) -> process::Command {
|
||||||
@@ -187,8 +215,8 @@ impl InstanceHandle {
|
|||||||
|
|
||||||
let stdout_status = self.status.clone();
|
let stdout_status = self.status.clone();
|
||||||
let stderr_status = self.status.clone();
|
let stderr_status = self.status.clone();
|
||||||
let internal_tx1 = self.internal_tx.clone();
|
let internal_tx1 = self.internal_events_tx.clone();
|
||||||
let internal_tx2 = self.internal_tx.clone();
|
let internal_tx2 = self.internal_events_tx.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut stdout_reader = BufReader::new(stdout).lines();
|
let mut stdout_reader = BufReader::new(stdout).lines();
|
||||||
@@ -286,17 +314,14 @@ impl InstanceHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "events", any(feature = "mc-vanilla")))]
|
#[cfg(all(feature = "events", any(feature = "mc-vanilla")))]
|
||||||
fn setup_parser(&mut self) -> Result<(), ServerError> {
|
fn setup_loopback(&mut self) -> Result<(), ServerError> {
|
||||||
let stdout_stream = self
|
|
||||||
.subscribe(StreamSource::Stdout)
|
|
||||||
.map_err(|_| ServerError::NoStdoutPipe)?;
|
|
||||||
let shutdown1 = self.shutdown.clone();
|
let shutdown1 = self.shutdown.clone();
|
||||||
let shutdown2 = self.shutdown.clone();
|
|
||||||
let event_tx = self.events_tx.clone();
|
|
||||||
|
|
||||||
if let Some(mut internal_rx) = self.internal_rx.take() {
|
let event_tx1 = self.events_tx.clone();
|
||||||
|
//internal mpsc to broadcast loopback
|
||||||
|
if let Some(mut internal_rx) = self.internal_events_rx.take() {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let tx = event_tx;
|
let tx = event_tx1;
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = shutdown1.cancelled() => {
|
_ = shutdown1.cancelled() => {
|
||||||
@@ -312,20 +337,50 @@ impl InstanceHandle {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "events", any(feature = "mc-vanilla")))]
|
||||||
|
fn setup_parser(&mut self) -> Result<(), ServerError> {
|
||||||
|
use crate::config::LogMeta;
|
||||||
|
|
||||||
|
let stdout_stream = self
|
||||||
|
.subscribe(StreamSource::Stdout)
|
||||||
|
.map_err(|_| ServerError::NoStdoutPipe)?;
|
||||||
|
let shutdown2 = self.shutdown.clone();
|
||||||
|
let bus_tx = self.internal_bus_tx.clone();
|
||||||
|
|
||||||
#[cfg(feature = "mc-vanilla")]
|
#[cfg(feature = "mc-vanilla")]
|
||||||
if self.data.mc_type == MinecraftType::Vanilla {
|
if self.data.mc_type == MinecraftType::Vanilla {
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut rx = stdout_stream;
|
let mut rx = stdout_stream;
|
||||||
|
let tx = bus_tx;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
tokio::select! {
|
tokio::select! {
|
||||||
_ = shutdown2.cancelled() => {
|
_ = shutdown2.cancelled() => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
line = rx.next() => {
|
next_line = rx.next() => {
|
||||||
if let Some(Ok(val)) = line {
|
if let Some(Ok(val)) = next_line {
|
||||||
// TODO: Call parser
|
let event_line = match val.payload {
|
||||||
|
EventPayload::StdLine{line} => {
|
||||||
|
line
|
||||||
|
},
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let meta = match LogMeta::new(event_line.line) {
|
||||||
|
Ok(Some(log_meta)) => {
|
||||||
|
log_meta
|
||||||
|
},
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
match meta.parse_event() {
|
||||||
|
Ok(Some(event)) => _ = tx.send(event),
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ pub mod config;
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod instance;
|
pub mod instance;
|
||||||
pub mod manifests;
|
pub mod manifests;
|
||||||
|
pub mod parser;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|||||||
26
src/parser/mod.rs
Normal file
26
src/parser/mod.rs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::{
|
||||||
|
LogMeta,
|
||||||
|
stream::{EventPayload, InstanceEvent, InternalEvent, LogLevel},
|
||||||
|
},
|
||||||
|
error::ParserError,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl LogMeta {
|
||||||
|
pub fn parse_event(&self) -> Result<Option<InternalEvent>, ParserError> {
|
||||||
|
if self.thread == "Server thread" && self.level == LogLevel::Info {
|
||||||
|
return self.parse_server_thread_info_lv2();
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_server_thread_info_lv2(&self) -> Result<Option<InternalEvent>, ParserError> {
|
||||||
|
let re = Regex::new(r"Done \([0-9.]+s\)!").unwrap();
|
||||||
|
if re.is_match(&self.msg) {
|
||||||
|
return Ok(Some(InternalEvent::ServerStarted));
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,28 +1,33 @@
|
|||||||
use std::{ops::RangeInclusive, path::PathBuf, str::FromStr};
|
use std::{ops::RangeInclusive, path::PathBuf, str::FromStr};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::json;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
fs::{File, create_dir},
|
fs::{File, create_dir, read, read_dir},
|
||||||
io::{self, AsyncWriteExt},
|
io::{self, AsyncWriteExt},
|
||||||
sync::RwLock,
|
sync::{RwLock, watch},
|
||||||
};
|
};
|
||||||
|
use tokio_stream::wrappers::BroadcastStream;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{MinecraftType, MinecraftVersion, Version},
|
config::{self, MinecraftType, MinecraftVersion, StreamSource, Version, stream::InstanceEvent},
|
||||||
error::CreationError,
|
error::{CreationError, ServerError, SubscribeError},
|
||||||
instance::InstanceHandle,
|
instance::InstanceHandle,
|
||||||
manifests::vanilla::{VanillaManifestV2, VanillaManifestV2Version, VanillaReleaseManifest},
|
manifests::vanilla::{VanillaManifestV2, VanillaManifestV2Version, VanillaReleaseManifest},
|
||||||
server,
|
server,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct MineGuardConfig {
|
pub struct MineGuardConfig {
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
server_dir: PathBuf,
|
pub server_dir: PathBuf,
|
||||||
jar_path: PathBuf,
|
pub jar_path: PathBuf,
|
||||||
mc_version: MinecraftVersion,
|
pub mc_version: MinecraftVersion,
|
||||||
mc_type: MinecraftType,
|
pub mc_type: MinecraftType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct MineGuardServer {
|
pub struct MineGuardServer {
|
||||||
pub handle: RwLock<InstanceHandle>,
|
pub handle: RwLock<InstanceHandle>,
|
||||||
pub config: RwLock<MineGuardConfig>,
|
pub config: RwLock<MineGuardConfig>,
|
||||||
@@ -41,6 +46,15 @@ impl MineGuardConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MineGuardServer {
|
impl MineGuardServer {
|
||||||
|
async fn load_cfg_handle(
|
||||||
|
config: MineGuardConfig,
|
||||||
|
handle: InstanceHandle,
|
||||||
|
) -> Result<Self, CreationError> {
|
||||||
|
Ok(Self {
|
||||||
|
config: RwLock::new(config),
|
||||||
|
handle: RwLock::new(handle),
|
||||||
|
})
|
||||||
|
}
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
mc_version: MinecraftVersion,
|
mc_version: MinecraftVersion,
|
||||||
mc_type: MinecraftType,
|
mc_type: MinecraftType,
|
||||||
@@ -61,6 +75,11 @@ impl MineGuardServer {
|
|||||||
.await
|
.await
|
||||||
.map_err(|_| CreationError::DirectoryError)?;
|
.map_err(|_| CreationError::DirectoryError)?;
|
||||||
|
|
||||||
|
let internal_dir = server_root.join(".mineguard");
|
||||||
|
create_dir(internal_dir)
|
||||||
|
.await
|
||||||
|
.map_err(|_| CreationError::DirectoryError)?;
|
||||||
|
|
||||||
let mut url = String::new();
|
let mut url = String::new();
|
||||||
|
|
||||||
if mc_type == MinecraftType::Vanilla {
|
if mc_type == MinecraftType::Vanilla {
|
||||||
@@ -120,4 +139,110 @@ impl MineGuardServer {
|
|||||||
|
|
||||||
Ok(server)
|
Ok(server)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn start(&self) -> Result<(), ServerError> {
|
||||||
|
let mut handle_w = self.handle.write().await;
|
||||||
|
let res = handle_w.start().await;
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn kill(&self) -> Result<(), ServerError> {
|
||||||
|
let mut handle_w = self.handle.write().await;
|
||||||
|
let res = handle_w.kill().await;
|
||||||
|
res
|
||||||
|
}
|
||||||
|
pub async fn stop(&self) -> Result<(), ServerError> {
|
||||||
|
let mut handle_w = self.handle.write().await;
|
||||||
|
let res = handle_w.stop().await;
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn subscribe(
|
||||||
|
&self,
|
||||||
|
stream: StreamSource,
|
||||||
|
) -> Result<BroadcastStream<InstanceEvent>, SubscribeError> {
|
||||||
|
let handle_r = self.handle.read().await;
|
||||||
|
let res = handle_r.subscribe(stream);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn accept_eula(&self) -> Result<(), ServerError> {
|
||||||
|
let config_r = self.config.read().await;
|
||||||
|
let eula_path = config_r.server_dir.join("eula.txt");
|
||||||
|
|
||||||
|
let mut out = File::create(eula_path)
|
||||||
|
.await
|
||||||
|
.map_err(|_| ServerError::NoEULA)?;
|
||||||
|
|
||||||
|
out.write_all(b"#Generated by MineGuard\neula=true\n")
|
||||||
|
.await
|
||||||
|
.map_err(|_| ServerError::WriteEULAFailed)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn write_config(&self) -> Result<(), ServerError> {
|
||||||
|
let config_r = self.config.read().await;
|
||||||
|
let root_path = config_r.server_dir.clone();
|
||||||
|
let config_clone = config_r.clone();
|
||||||
|
drop(config_r);
|
||||||
|
|
||||||
|
let config_path = root_path.join(".mineguard/config.json");
|
||||||
|
|
||||||
|
let json = serde_json::to_vec_pretty(&config_clone).map_err(|_| ServerError::FileIO)?;
|
||||||
|
|
||||||
|
File::create(config_path)
|
||||||
|
.await
|
||||||
|
.map_err(|_| ServerError::FileIO)?
|
||||||
|
.write_all(&json)
|
||||||
|
.await
|
||||||
|
.map_err(|_| ServerError::FileIO)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn load(path: &PathBuf) -> Result<Self, CreationError> {
|
||||||
|
let config_path = path.join(".mineguard/config.json");
|
||||||
|
|
||||||
|
let data = read(config_path)
|
||||||
|
.await
|
||||||
|
.map_err(|_| CreationError::DirectoryError)?;
|
||||||
|
|
||||||
|
let config: MineGuardConfig =
|
||||||
|
serde_json::from_slice(&data).map_err(|_| CreationError::CreationError)?;
|
||||||
|
let handle = InstanceHandle::new_with_config(config.clone())
|
||||||
|
.map_err(|_| CreationError::CreationError)?;
|
||||||
|
|
||||||
|
MineGuardServer::load_cfg_handle(config, handle).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn load_all(path: PathBuf) -> Result<Vec<Self>, CreationError> {
|
||||||
|
let mut dirs = Vec::new();
|
||||||
|
let mut entries = read_dir(path)
|
||||||
|
.await
|
||||||
|
.map_err(|_| CreationError::DirectoryError)?;
|
||||||
|
|
||||||
|
while let Some(entry) = entries
|
||||||
|
.next_entry()
|
||||||
|
.await
|
||||||
|
.map_err(|_| CreationError::DirectoryError)?
|
||||||
|
{
|
||||||
|
let meta = entry
|
||||||
|
.metadata()
|
||||||
|
.await
|
||||||
|
.map_err(|_| CreationError::DirectoryError)?;
|
||||||
|
if meta.is_dir() {
|
||||||
|
dirs.push(entry.path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut servers: Vec<Self> = Vec::new();
|
||||||
|
|
||||||
|
for v in dirs {
|
||||||
|
println!("{}", v.to_str().unwrap());
|
||||||
|
servers.push(Self::load(&v).await?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(servers)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user