112 lines
3.6 KiB
Rust
112 lines
3.6 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum ServerState {
|
|
Installing,
|
|
Stopped,
|
|
Starting,
|
|
Running,
|
|
Stopping,
|
|
Error,
|
|
}
|
|
|
|
impl std::fmt::Display for ServerState {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::Installing => write!(f, "installing"),
|
|
Self::Stopped => write!(f, "stopped"),
|
|
Self::Starting => write!(f, "starting"),
|
|
Self::Running => write!(f, "running"),
|
|
Self::Stopping => write!(f, "stopping"),
|
|
Self::Error => write!(f, "error"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct PortMap {
|
|
pub host_port: u16,
|
|
pub container_port: u16,
|
|
pub protocol: String, // "tcp" or "udp"
|
|
}
|
|
|
|
/// Per-game runtime knobs supplied by the panel. Mirrored into Docker labels so
|
|
/// they survive a daemon restart (see `docker::container`).
|
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
pub struct ServerRuntime {
|
|
/// Mount point of the data directory inside the container. `None` means
|
|
/// "derive it from the image".
|
|
pub data_mount_path: Option<String>,
|
|
/// In-game command that shuts the server down cleanly (e.g. `stop`, `quit`).
|
|
pub stop_command: Option<String>,
|
|
/// Total budget for a graceful shutdown before the container gets killed.
|
|
pub stop_timeout_seconds: Option<i64>,
|
|
}
|
|
|
|
impl ServerRuntime {
|
|
pub fn from_request(
|
|
data_mount_path: String,
|
|
stop_command: String,
|
|
stop_timeout_seconds: i32,
|
|
) -> Self {
|
|
Self {
|
|
data_mount_path: non_empty(data_mount_path),
|
|
stop_command: non_empty(stop_command),
|
|
stop_timeout_seconds: if stop_timeout_seconds > 0 {
|
|
Some(stop_timeout_seconds as i64)
|
|
} else {
|
|
None
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
fn non_empty(value: String) -> Option<String> {
|
|
let trimmed = value.trim();
|
|
if trimmed.is_empty() {
|
|
None
|
|
} else {
|
|
Some(trimmed.to_string())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ServerSpec {
|
|
pub uuid: String,
|
|
pub docker_image: String,
|
|
pub memory_limit: i64, // bytes
|
|
pub disk_limit: i64, // bytes
|
|
pub cpu_limit: i32, // percentage (100 = 1 core)
|
|
pub startup_command: String,
|
|
pub environment: HashMap<String, String>,
|
|
pub ports: Vec<PortMap>,
|
|
pub data_path: PathBuf,
|
|
pub state: ServerState,
|
|
pub container_id: Option<String>,
|
|
#[serde(default)]
|
|
pub runtime: ServerRuntime,
|
|
}
|
|
|
|
impl ServerSpec {
|
|
/// Check if the server can transition to the requested state.
|
|
pub fn can_transition_to(&self, target: &ServerState) -> bool {
|
|
matches!(
|
|
(&self.state, target),
|
|
(ServerState::Installing, ServerState::Stopped)
|
|
| (ServerState::Installing, ServerState::Error)
|
|
| (ServerState::Stopped, ServerState::Starting)
|
|
| (ServerState::Starting, ServerState::Running)
|
|
| (ServerState::Starting, ServerState::Error)
|
|
| (ServerState::Running, ServerState::Stopping)
|
|
| (ServerState::Running, ServerState::Error)
|
|
| (ServerState::Stopping, ServerState::Stopped)
|
|
| (ServerState::Stopping, ServerState::Error)
|
|
| (ServerState::Error, ServerState::Starting)
|
|
| (ServerState::Error, ServerState::Stopped)
|
|
)
|
|
}
|
|
}
|