293 lines
9.3 KiB
Python
293 lines
9.3 KiB
Python
from typing import Annotated, Optional
|
|
|
|
import uvicorn
|
|
from classes import ProcessStatus
|
|
from controllers import Controllers
|
|
from fastapi import BackgroundTasks, FastAPI, Header
|
|
from fastapi.responses import StreamingResponse
|
|
from logs import logger
|
|
from models import Models
|
|
from responses import Responses
|
|
from util import check_password, stop_server
|
|
|
|
app = FastAPI()
|
|
|
|
|
|
@app.get("/start")
|
|
async def start(tasks: BackgroundTasks) -> Responses.StartResponse:
|
|
"""Starts the Server's process if it is not already running.
|
|
Returns:
|
|
status: "started" or "running".
|
|
message: The Server's response.
|
|
"""
|
|
|
|
logger.debug("/start")
|
|
|
|
if Controllers.process.status() == ProcessStatus.RUNNING:
|
|
logger.debug("/start - The Server was already running")
|
|
return {
|
|
"status": "running",
|
|
"message": "The Server was already running.",
|
|
}
|
|
|
|
if Controllers.maintainance.is_set():
|
|
Controllers.maintainance.unset()
|
|
logger.debug("/start - Unset maintainance")
|
|
|
|
tasks.add_task(Controllers.process.start)
|
|
logger.info("/start - Starting server")
|
|
return {
|
|
"status": "starting",
|
|
"message": "The Server is starting.",
|
|
}
|
|
|
|
|
|
@app.get("/status")
|
|
async def status() -> Responses.StatusResponse:
|
|
"""Checks whether the Server is running and returns its information.
|
|
Returns:
|
|
status: One of "online", "offline", "crashed", "maintainance", or "starting".
|
|
message: The Server's response.
|
|
reason: if status is "maintainance", contains the reason for the Server's maintainance state.
|
|
motd: if status is "online", contains the Server's MOTD as an HTML string.
|
|
icon: if status is "online", contains the Server's icon as a base64 string.
|
|
players: if status is "online", contains:
|
|
online: the number of online players.
|
|
max: the number of max allowed players.
|
|
list: the list of online players' usernames as strings.
|
|
"""
|
|
|
|
logger.debug("/status")
|
|
|
|
process_status = Controllers.process.status()
|
|
if process_status != ProcessStatus.RUNNING:
|
|
# Crashed
|
|
if process_status == ProcessStatus.CRASHED:
|
|
logger.debug("/status - The Server has crashed")
|
|
return {
|
|
"status": "crashed",
|
|
"message": "The Server has crashed.",
|
|
}
|
|
# Maintainance
|
|
if Controllers.maintainance.is_set():
|
|
reason = Controllers.maintainance.get()
|
|
logger.debug(
|
|
'/status - The Server is offline due to maintainance ("%s")', reason
|
|
)
|
|
return {
|
|
"status": "maintainance",
|
|
"message": "The Server is offline due to maintainance.",
|
|
"reason": reason,
|
|
}
|
|
# Offline
|
|
logger.debug("/status - The Server is offline")
|
|
return {
|
|
"status": "offline",
|
|
"message": "The Server is offline.",
|
|
}
|
|
|
|
server_status = Controllers.server.status()
|
|
|
|
# Starting
|
|
if not server_status["online"]:
|
|
logger.debug("/status - The Server is starting")
|
|
return {
|
|
"status": "starting",
|
|
"message": "The Server is starting.",
|
|
}
|
|
|
|
# Online
|
|
logger.debug("/status - The Server is online")
|
|
return {
|
|
"status": "online",
|
|
"message": "The Server is online.",
|
|
"motd": server_status.get("motd", ""),
|
|
"icon": server_status.get("icon", None),
|
|
"players": server_status.get(
|
|
"players",
|
|
{
|
|
"online": 0,
|
|
"max": 20,
|
|
"list": [],
|
|
},
|
|
),
|
|
}
|
|
|
|
|
|
@app.get("/stop")
|
|
async def stop(
|
|
data: Models.StopModel,
|
|
authorization: Annotated[str, Header()],
|
|
tasks: BackgroundTasks,
|
|
) -> Responses.StopResponse:
|
|
"""Stops the Server.
|
|
It waits for `countdown` seconds, then runs `/stop` on the Server, and kills it after `timeout` seconds if it's still alive.
|
|
Args:
|
|
countdown: the time in seconds to give the players to leave the Server before initiating the shutdown.
|
|
if set to 0, the shutdown phase is started immediately. Defaults to 60.
|
|
reason: a brief message explaining why the Server is shutting down. Only shown if countdown is non-zero. Defaults to "".
|
|
timeout: the time in seconds to wait before killing the Server if its process hasn't stopped. Defaults to 10.
|
|
Headers:
|
|
Authorization: the Authorization token.
|
|
Returns:
|
|
status: "stopping"
|
|
message: The Server's response.
|
|
"""
|
|
|
|
logger.debug("/stop")
|
|
|
|
check_password(authorization)
|
|
tasks.add_task(stop_server, "STOPPING", data.countdown, data.reason, data.timeout)
|
|
|
|
logger.info("/stop - The Server is stopping")
|
|
return {
|
|
"status": "stopping",
|
|
"message": "The Server is stopping.",
|
|
}
|
|
|
|
|
|
@app.get("/restart")
|
|
async def restart(
|
|
data: Models.RestartModel,
|
|
authorization: Annotated[str, Header()],
|
|
tasks: BackgroundTasks,
|
|
) -> Responses.RestartResponse:
|
|
"""Restarts the Server.
|
|
It waits for `countdown` seconds, then runs `/stop` on the Server, and kills it after `timeout` seconds if it's still alive.
|
|
Then, it starts the Server again.
|
|
Args:
|
|
countdown: the time in seconds to give the players to leave the Server before initiating the shutdown.
|
|
if set to 0, the shutdown phase is started immediately. Defaults to 60.
|
|
reason: a brief message explaining why the Server is restarting. Only shown if countdown is non-zero. Defaults to "".
|
|
timeout: the time in seconds to wait before killing the Server if its process hasn't stopped. Defaults to 10.
|
|
Headers:
|
|
Authorization: the Authorization token.
|
|
Returns:
|
|
status: "restarting"
|
|
message: The Server's response.
|
|
"""
|
|
|
|
check_password(authorization)
|
|
|
|
logger.debug("/restart")
|
|
|
|
tasks.add_task(
|
|
stop_server,
|
|
"RESTARTING",
|
|
data.countdown,
|
|
data.reason,
|
|
data.timeout,
|
|
Controllers.process.start,
|
|
)
|
|
|
|
logger.info("/restart - The Server is restarting")
|
|
|
|
return {
|
|
"status": "restarting",
|
|
"message": "The Server is restarting.",
|
|
}
|
|
|
|
|
|
@app.get("/maintainance")
|
|
async def maintainance(
|
|
data: Models.MaintainanceModel,
|
|
authorization: Annotated[str, Header()],
|
|
tasks: BackgroundTasks,
|
|
) -> Responses.MaintainanceResponse:
|
|
"""Stops the Server and sets it to maintainance status.
|
|
It waits for `countdown` seconds, then runs `/stop` on the Server, and kills it after `timeout` seconds if it's still alive.
|
|
Args:
|
|
countdown: the time in seconds to give the players to leave the Server before initiating the shutdown.
|
|
if set to 0, the shutdown phase is started immediately. Defaults to 60.
|
|
reason: a brief message explaining why the Server is entering maintanance mode. Only shown if countdown is non-zero. Defaults to "".
|
|
timeout: the time in seconds to wait before killing the Server if its process hasn't stopped. Defaults to 10.
|
|
Headers:
|
|
Authorization: the Authorization token.
|
|
Returns:
|
|
status: "stopping"
|
|
message: The Server's response.
|
|
"""
|
|
|
|
check_password(authorization)
|
|
|
|
logger.debug("/maintainance")
|
|
|
|
tasks.add_task(
|
|
stop_server,
|
|
"STOPPING FOR MAINTAINANCE",
|
|
data.countdown,
|
|
data.reason,
|
|
data.timeout,
|
|
Controllers.maintainance.set,
|
|
data.reason,
|
|
)
|
|
|
|
logger.info("/maintainance - The Server is stopping for maintainance")
|
|
|
|
return {
|
|
"status": "stopping",
|
|
"message": "The Server is stopping for maintainance.",
|
|
}
|
|
|
|
|
|
@app.get("/command")
|
|
async def command(
|
|
data: Models.CommandModel, authorization: Annotated[str, Header()]
|
|
) -> Responses.CommandResponse:
|
|
"""
|
|
Executes a command on the Server and returns its output.
|
|
Args:
|
|
command: The command to execute.
|
|
Headers:
|
|
Authorization: the Authorization token.
|
|
Returns:
|
|
status: "executed"
|
|
message: The Server's response.
|
|
output: The command's output.
|
|
"""
|
|
|
|
logger.debug('/command {command: "%s"}', data.command)
|
|
check_password(authorization)
|
|
|
|
output = Controllers.server.command(data.command)
|
|
return {
|
|
"status": "executed",
|
|
"message": "The command was executed.",
|
|
"output": output,
|
|
}
|
|
|
|
|
|
@app.get("/logs")
|
|
@app.get("/logs/stream")
|
|
async def logs_stream(authorization: Annotated[str, Header()]) -> StreamingResponse:
|
|
"""Streams Server logs in real-time using SSE.
|
|
Headers:
|
|
Authorization: the Authorization token.
|
|
Returns: text/event-stream
|
|
"""
|
|
|
|
logger.debug("/logs/stream")
|
|
check_password(authorization)
|
|
return StreamingResponse(Controllers.logs.stream(), media_type="text/event-stream")
|
|
|
|
|
|
@app.get("/logs/tail")
|
|
async def logs_tail(
|
|
authorization: Annotated[str, Header()],
|
|
data: Optional[Models.LogsTailModel] = None,
|
|
) -> StreamingResponse:
|
|
"""Streams the last few lines of the Server's logs.
|
|
Args:
|
|
back: The number of lines to stream.
|
|
Headers:
|
|
Authorization: the Authorization token.
|
|
Returns: text/event-stream
|
|
"""
|
|
|
|
logger.debug("/logs/tail")
|
|
check_password(authorization)
|
|
return StreamingResponse(Controllers.logs.tail(data.back if data else 10))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
uvicorn.run("main:app", host="0.0.0.0", port=42101)
|