From a008bc27fab257cfe6eba7cda494838feed0c0d3 Mon Sep 17 00:00:00 2001 From: Malasaur Date: Mon, 1 Dec 2025 22:43:15 +0100 Subject: [PATCH] Changed ProcessController to handle Server as child subprocess --- classes.py | 7 +++++ config.py | 1 - controllers.py | 71 +++++++++++++++++++++----------------------------- main.py | 18 +++++++------ util.py | 8 +++--- 5 files changed, 52 insertions(+), 53 deletions(-) diff --git a/classes.py b/classes.py index ca48faf..ceca19f 100644 --- a/classes.py +++ b/classes.py @@ -1,6 +1,13 @@ +from enum import Enum from typing import NotRequired, TypedDict +class ProcessStatus(Enum): + RUNNING = None + STOPPED = 0 + CRASHED = 1 + + class ServerPlayersList(TypedDict): online: int max: int diff --git a/config.py b/config.py index f90de32..7d57f4e 100644 --- a/config.py +++ b/config.py @@ -4,7 +4,6 @@ from dotenv import dotenv_values class Config: data = dotenv_values() MINECRAFTD_PASSWORD: str | None = data.get("MINECRAFTD_PASSWORD") - PID_FILE: str = data.get("PID_FILE") or "server.pid" START_COMMAND: str = data.get("START_COMMAND") or "python proc.py" SERVER_HOST: str = data.get("SERVER_HOST") or "localhost" SERVER_PORT: int = int(data.get("SERVER_PORT") or 25565) diff --git a/controllers.py b/controllers.py index 24ddf33..ac87ddb 100644 --- a/controllers.py +++ b/controllers.py @@ -1,70 +1,56 @@ import shlex from collections import deque -from os import setsid from pathlib import Path from subprocess import PIPE, Popen from time import sleep -from typing import Generator +from typing import Generator, Literal from mcrcon import MCRcon from mcstatus import JavaServer -from psutil import NoSuchProcess, Process -from .classes import ServerStatus +from .classes import ProcessStatus, ServerStatus from .config import Config class ProcessController: def __init__(self): - self.pid_file: Path = Path(Config.PID_FILE) self.start_command: list[str] = shlex.split(Config.START_COMMAND) - self.process: Process | None = None - - if self.pid_file.is_file(): - pid = int(self.pid_file.read_text()) - if self._is_pid_alive(pid): - self.process = Process(pid) - else: - self.pid_file.unlink() + self.process: Popen | None = None + self.last_status: Literal[ProcessStatus.STOPPED, ProcessStatus.CRASHED] = ( + ProcessStatus.STOPPED + ) def start(self) -> None: - "Starts the process." - if self.is_started(): + "Start the process." + if self.status() == ProcessStatus.RUNNING: return - process = Popen( + self.process = Popen( self.start_command, stdout=PIPE, stderr=PIPE, - preexec_fn=setsid, ) - self.process = Process(process.pid) - self.pid_file.write_text(str(self.process.pid)) - def is_started(self) -> bool: - "Check if the process is running." - if self.process: - return self.process.is_running() - return False - - def is_crashed(self) -> bool: - "Check if the process has crashed." - return False # TODO + def status(self) -> ProcessStatus: + "Check the process' status." + if not self.process: + return self.last_status + match self.process.poll(): + case None: + return ProcessStatus.RUNNING + case 0: + self.last_status = ProcessStatus.STOPPED + return ProcessStatus.STOPPED + case _: + self.last_status = ProcessStatus.CRASHED + return ProcessStatus.CRASHED def kill(self) -> None: "Kill the process." if self.process: self.process.terminate() self.process = None - self.pid_file.unlink() - - def _is_pid_alive(self, pid: int) -> bool: - "Check if a process with the given PID is alive." - try: - p = Process(pid) - return p.is_running() - except NoSuchProcess: - return False + self.last_status = ProcessStatus.STOPPED class ServerController: @@ -102,10 +88,13 @@ class ServerController: } def command(self, command: str) -> str: - self.rcon.connect() - output = self.rcon.command(command) - self.rcon.disconnect() - return output + try: + self.rcon.connect() + output = self.rcon.command(command) + self.rcon.disconnect() + return output + except Exception: + return "" class MaintainanceController: diff --git a/main.py b/main.py index 5f74ce4..e8faa57 100644 --- a/main.py +++ b/main.py @@ -4,6 +4,7 @@ from typing import Annotated from fastapi import FastAPI, Header from fastapi.responses import StreamingResponse +from .classes import ProcessStatus from .controllers import Controllers from .models import Models from .responses import Responses @@ -16,7 +17,7 @@ app = FastAPI() async def start() -> Responses.StartResponse: "Starts the Server process." - if Controllers.process.is_started(): + if Controllers.process.status() == ProcessStatus.RUNNING: return {"status": "running"} Controllers.process.start() return {"status": "started"} @@ -26,9 +27,10 @@ async def start() -> Responses.StartResponse: async def status() -> Responses.StatusResponse: "Checks whether the Server is running and returns its information." - if not Controllers.process.is_started(): + process_status = Controllers.process.status() + if process_status != ProcessStatus.RUNNING: # Crashed - if Controllers.process.is_crashed(): + if process_status == ProcessStatus.CRASHED: return {"status": "crashed"} # Maintainance if Controllers.maintainance.is_set(): @@ -36,18 +38,18 @@ async def status() -> Responses.StatusResponse: # Offline return {"status": "offline"} - status = Controllers.server.status() + server_status = Controllers.server.status() # Starting - if not status["online"]: + if not server_status["online"]: return {"status": "starting"} # Online return { "status": "online", - "motd": status.get("motd", ""), - "icon": status.get("icon", None), - "players": status.get( + "motd": server_status.get("motd", ""), + "icon": server_status.get("icon", None), + "players": server_status.get( "players", { "online": 0, diff --git a/util.py b/util.py index 5c7d38a..7a1588f 100644 --- a/util.py +++ b/util.py @@ -3,6 +3,8 @@ from typing import Callable from fastapi import HTTPException +from minecraftd.classes import ProcessStatus + from .config import Config from .controllers import Controllers @@ -28,12 +30,12 @@ async def stop_server( await sleep(1) countdown -= 1 - # Controllers.server.command("stop") - while timeout > 0 and Controllers.process.is_started(): + Controllers.server.command("stop") + while timeout > 0 and Controllers.process.status() == ProcessStatus.RUNNING: await sleep(1) timeout -= 1 - if Controllers.process.is_started(): + if Controllers.process.status() == ProcessStatus.RUNNING: Controllers.process.kill() if then: