Compare commits
No commits in common. "a008bc27fab257cfe6eba7cda494838feed0c0d3" and "eb92d2d36f21650a5ff24b96628a28611c5d5709" have entirely different histories.
a008bc27fa
...
eb92d2d36f
11 changed files with 53 additions and 53 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
__pycache__/
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,13 +1,6 @@
|
|||
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
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ 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)
|
||||
|
|
|
|||
|
|
@ -1,56 +1,70 @@
|
|||
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, Literal
|
||||
from typing import Generator
|
||||
|
||||
from mcrcon import MCRcon
|
||||
from mcstatus import JavaServer
|
||||
from psutil import NoSuchProcess, Process
|
||||
|
||||
from .classes import ProcessStatus, ServerStatus
|
||||
from .classes import 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: Popen | None = None
|
||||
self.last_status: Literal[ProcessStatus.STOPPED, ProcessStatus.CRASHED] = (
|
||||
ProcessStatus.STOPPED
|
||||
)
|
||||
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()
|
||||
|
||||
def start(self) -> None:
|
||||
"Start the process."
|
||||
if self.status() == ProcessStatus.RUNNING:
|
||||
"Starts the process."
|
||||
if self.is_started():
|
||||
return
|
||||
|
||||
self.process = Popen(
|
||||
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 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 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 kill(self) -> None:
|
||||
"Kill the process."
|
||||
if self.process:
|
||||
self.process.terminate()
|
||||
self.process = None
|
||||
self.last_status = ProcessStatus.STOPPED
|
||||
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
|
||||
|
||||
|
||||
class ServerController:
|
||||
|
|
@ -88,13 +102,10 @@ class ServerController:
|
|||
}
|
||||
|
||||
def command(self, command: str) -> str:
|
||||
try:
|
||||
self.rcon.connect()
|
||||
output = self.rcon.command(command)
|
||||
self.rcon.disconnect()
|
||||
return output
|
||||
except Exception:
|
||||
return ""
|
||||
self.rcon.connect()
|
||||
output = self.rcon.command(command)
|
||||
self.rcon.disconnect()
|
||||
return output
|
||||
|
||||
|
||||
class MaintainanceController:
|
||||
|
|
|
|||
18
main.py
18
main.py
|
|
@ -4,7 +4,6 @@ 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
|
||||
|
|
@ -17,7 +16,7 @@ app = FastAPI()
|
|||
async def start() -> Responses.StartResponse:
|
||||
"Starts the Server process."
|
||||
|
||||
if Controllers.process.status() == ProcessStatus.RUNNING:
|
||||
if Controllers.process.is_started():
|
||||
return {"status": "running"}
|
||||
Controllers.process.start()
|
||||
return {"status": "started"}
|
||||
|
|
@ -27,10 +26,9 @@ async def start() -> Responses.StartResponse:
|
|||
async def status() -> Responses.StatusResponse:
|
||||
"Checks whether the Server is running and returns its information."
|
||||
|
||||
process_status = Controllers.process.status()
|
||||
if process_status != ProcessStatus.RUNNING:
|
||||
if not Controllers.process.is_started():
|
||||
# Crashed
|
||||
if process_status == ProcessStatus.CRASHED:
|
||||
if Controllers.process.is_crashed():
|
||||
return {"status": "crashed"}
|
||||
# Maintainance
|
||||
if Controllers.maintainance.is_set():
|
||||
|
|
@ -38,18 +36,18 @@ async def status() -> Responses.StatusResponse:
|
|||
# Offline
|
||||
return {"status": "offline"}
|
||||
|
||||
server_status = Controllers.server.status()
|
||||
status = Controllers.server.status()
|
||||
|
||||
# Starting
|
||||
if not server_status["online"]:
|
||||
if not status["online"]:
|
||||
return {"status": "starting"}
|
||||
|
||||
# Online
|
||||
return {
|
||||
"status": "online",
|
||||
"motd": server_status.get("motd", ""),
|
||||
"icon": server_status.get("icon", None),
|
||||
"players": server_status.get(
|
||||
"motd": status.get("motd", ""),
|
||||
"icon": status.get("icon", None),
|
||||
"players": status.get(
|
||||
"players",
|
||||
{
|
||||
"online": 0,
|
||||
|
|
|
|||
8
util.py
8
util.py
|
|
@ -3,8 +3,6 @@ from typing import Callable
|
|||
|
||||
from fastapi import HTTPException
|
||||
|
||||
from minecraftd.classes import ProcessStatus
|
||||
|
||||
from .config import Config
|
||||
from .controllers import Controllers
|
||||
|
||||
|
|
@ -30,12 +28,12 @@ async def stop_server(
|
|||
await sleep(1)
|
||||
countdown -= 1
|
||||
|
||||
Controllers.server.command("stop")
|
||||
while timeout > 0 and Controllers.process.status() == ProcessStatus.RUNNING:
|
||||
# Controllers.server.command("stop")
|
||||
while timeout > 0 and Controllers.process.is_started():
|
||||
await sleep(1)
|
||||
timeout -= 1
|
||||
|
||||
if Controllers.process.status() == ProcessStatus.RUNNING:
|
||||
if Controllers.process.is_started():
|
||||
Controllers.process.kill()
|
||||
|
||||
if then:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue