diff --git a/api_sender.html b/api_sender.html index a24a7bc..bc6255c 100644 --- a/api_sender.html +++ b/api_sender.html @@ -51,7 +51,7 @@ document.addEventListener('DOMContentLoaded', () => { buttons.forEach(button => { button.addEventListener('click', async event => { const action = button.dataset.action; - const token = "kPbH7QbrOzTrNcpqTnZGDJfSj3E3"; + const token = "MpkbDMOO8PQddQgB5VgBQdTMWF53"; const framework = document.getElementById('serverFramework').value; const subdomain = document.getElementById('subdomain').value; const email = document.getElementById('accountEmail').value; diff --git a/app.py b/app.py index d801760..b0ce3e5 100644 --- a/app.py +++ b/app.py @@ -1,6 +1,7 @@ import http import inspect from typing import Union +from apscheduler.schedulers.background import BackgroundScheduler from flask import (Blueprint, Flask, Response, jsonify, request) from flask_cors import CORS @@ -125,4 +126,7 @@ app.register_blueprint(apiBP) if __name__ == '__main__': atexit.register(exit_safety) + scheduler = BackgroundScheduler() + scheduler.add_job(generic_executor.scheduled_actions, 'interval', minutes=1) + scheduler.start() app.run(host='0.0.0.0', port=3000, debug=False) diff --git a/firebase_manager.py b/firebase_manager.py index 2c076ad..463e72b 100644 --- a/firebase_manager.py +++ b/firebase_manager.py @@ -1,3 +1,4 @@ +import time from datetime import datetime from typing import Union @@ -7,6 +8,7 @@ from firebase_admin import auth, credentials, firestore from google.api_core.exceptions import Aborted, DataLoss, NotFound, OutOfRange, PermissionDenied, ResourceExhausted import file_manager +from generic_executor import mc_manager cred = credentials.Certificate('secrets/servii.json') app = firebase_admin.initialize_app(cred) @@ -185,5 +187,22 @@ def log_exception_to_firestore(exception: Exception = None, user_id: str = None, print(f"Failed to add log entry: {e}") +def close_idle_server(user_id: Union[str, None], name: Union[str, None], port: int, + server_stamp: Union[float, None]) -> None: + if any(var is None for var in (user_id, name, server_stamp)): + return + try: + mc_manager.stop_server(port) + update_server_running_state(user_id, name, False) + now: float = time.time() + elapsed_seconds = now - server_stamp + hours = int(elapsed_seconds // 3600) + minutes = int((elapsed_seconds % 3600) // 60) + seconds = round(elapsed_seconds % 60, 2) + file_manager.log_action(user_id, name, "ServerStop (idle)", + f"Suspended inactive server activities. Uptime : {hours}h {minutes}m {seconds}s") + except Exception as e: + print(e, user_id, name, server_stamp) + if __name__ == "__main__": pass diff --git a/generic_executor.py b/generic_executor.py index fd69c1f..1e309d2 100644 --- a/generic_executor.py +++ b/generic_executor.py @@ -1,7 +1,6 @@ -import json import os from http import HTTPStatus -from typing import Union, Tuple, Dict +from typing import Union from cloudflare.types.dns import SRVRecord from firebase_admin.auth import UserRecord @@ -218,7 +217,7 @@ def server_run(user: UserRecord, name: str) -> tuple[HTTPStatus, Union[str, None mc_manager.set_cooldown(user_id=user_id) try: port: int = firebase_manager.get_server_port(user_id) - server_id = mc_manager.start_server(f"users/{user_id}/{name}", port) + server_id = mc_manager.start_server(f"users/{user_id}/{name}", port, user_id, name) if server_id is None: return HTTPStatus.OK, f"You cannot run multiples instances at this time." mc_manager.servers[server_id]['port'] = int(port) @@ -302,5 +301,8 @@ def run_command(user: UserRecord, command: str, name: str) -> tuple[HTTPStatus, return HTTPStatus.INTERNAL_SERVER_ERROR, f"Error executing command: {command} || {str(e)}" +def scheduled_actions() -> None: + mc_manager.check_servers_idle() + if __name__ == "__main__": pass diff --git a/requirements.txt b/requirements.txt index e4ef2cc..6d13e35 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ Flask-Cors~=4.0.1 mcipc~=2.4.2 firebase-admin~=6.5.0 protobuf~=4.25.3 -typing_extensions~=4.9.0 \ No newline at end of file +typing_extensions~=4.9.0 +APScheduler~=3.10.4 \ No newline at end of file diff --git a/server_mc_manager.py b/server_mc_manager.py index df94b76..7e7421c 100644 --- a/server_mc_manager.py +++ b/server_mc_manager.py @@ -5,6 +5,9 @@ import mcipc.query import mcipc.query.client from typing import Union +import firebase_manager +import generic_executor + class MinecraftServerManager: allowed_properties: list[str] = ["difficulty", "gamemode", "force-gamemode", "hardcore", "generate-structures", @@ -14,8 +17,9 @@ class MinecraftServerManager: self.servers: dict = {} self.servers_count: int = 0 self.cooldowns = {} + self.offline_ports: list[int] = [] - def start_server(self, server_directory: str, port: int, + def start_server(self, server_directory: str, port: int, user_id: str, server_name: str, java_executable='java', jar_file='server.jar', memory_size='2048M') -> Union[int, None]: if port in self.servers: @@ -38,7 +42,10 @@ class MinecraftServerManager: self.servers[port] = { 'process': process, 'directory': server_directory, - 'port': port + 'port': port, + 'user_id': user_id, + 'name': server_name, + 'time': time.time(), } return port @@ -56,6 +63,8 @@ class MinecraftServerManager: process = self.servers[port]['process'] process.communicate(input=b"stop\n") del self.servers[port] + if port in self.offline_ports: + self.offline_ports.remove(port) return True def stop_server_forcefully(self, port) -> bool: @@ -98,6 +107,34 @@ class MinecraftServerManager: del self.cooldowns[user_id] return False + def check_servers_idle(self) -> None: + servers: dict = self.servers.copy() + + for port, server_info in servers.items(): + online_players = self.get_online_players(port) + + if online_players == 0: + if port not in self.offline_ports: + self.offline_ports.append(port) + else: + print(f"Closing {port} server.") + user_id = server_info.get("user_id", None) + name = server_info.get("name", None) + server_stamp = server_info.get("time", None) + print(f"{user_id}, {name}, {server_stamp}") + firebase_manager.close_idle_server( + user_id=user_id, + name=name, + server_stamp=server_stamp, + port=port, + ) + else: + if port in self.offline_ports: + self.offline_ports.remove(port) + + print(f"Offline_servers : {self.offline_ports}") + return + if __name__ == "__main__": pass diff --git a/unit_test.py b/unit_test.py index 11617b7..52a57d9 100644 --- a/unit_test.py +++ b/unit_test.py @@ -1,3 +1,6 @@ +import time +from os import remove + import file_manager import firebase_manager @@ -14,4 +17,7 @@ if __name__ == '__main__': #ban_user("MpkbDMOO8PQddQgB5VgBQdTMWF53") #file_manager.log_action("gqZN3eCHF3V2er3Py3rlgk8u2t83", "test", "DeleteServer") #firebase_manager.set_servers_not_running() + test: list[int] = [0, 987] + test.remove(85) + print(test) pass