module server_actions; import std.stdio; import std.process; import std.format; import std.algorithm; import std.array; import std.string; import std.file; import std.datetime; import mcrcd; import shared_utils.discord; import shared_utils.server_status; import server_metadata; import config; ServerStatus determineStatus(in ServerMetaData server) { const bool online = isServiceActive(server.serviceName); int playersOnline = 0; string[] playerNames; if (online) { try { MCRconResponse response = executeRconCommand(server, "list"); string playersList; int tmp; response.text.formattedRead!"There are %d of a max of %d players online: %s"( playersOnline, tmp, playersList ); playerNames = playersList.strip.split(",") .filter!(s => s !is null && s.strip.length > 0) .map!(s => s.strip) .array; } catch (Exception e) { stderr.writefln!"Failed to get players from server: %s"(e.msg); } } return ServerStatus( server.name, server.displayName, server.description, online, playersOnline, server.maxPlayers, playerNames.idup ); } void startServer(in ServerMetaData server, in AgentConfig config) { writeln("Starting server " ~ server.name); Pid pid = spawnProcess(["sudo", "systemctl", "start", server.serviceName]); int result = wait(pid); if (result != 0) { string msg = format!"Starting server %s failed with code %d."(server.name, result); stderr.writeln(msg); throw new Exception(msg); } sendDiscordMessage( config.discordWebhookUrl, format!"Started server %s as a result of a user request."(server.name) ); } void stopServer(in ServerMetaData server, in AgentConfig config) { writeln("Shutting down server " ~ server.name ~ " after a period of inactivity."); Pid pid = spawnProcess(["sudo", "systemctl", "stop", server.serviceName]); int result = wait(pid); if (result == 0) { removeIdleTrackerFileIfPresent(server); sendDiscordMessage( config.discordWebhookUrl, format!"Shut down server %s after inactivity for more than %d minutes."( server.name, config.serverInactivityTimeoutMinutes ) ); } else { stderr.writefln!"Failed to stop server %s. systemctl stop exited with code %d."(server.name, result); } } Duration getOrCreateIdleTrackerFileAndGetAge(in ServerMetaData server) { string filename = getIdleTrackerFilename(server); if (exists(filename)) { SysTime timestamp = std.file.timeLastModified(filename); return Clock.currTime - timestamp; } else { File f = File(filename, "w"); f.close(); writeln("Created idle tracker for server " ~ server.name ~ "."); return seconds(0); } } void removeIdleTrackerFileIfPresent(in ServerMetaData server) { string trackerFile = getIdleTrackerFilename(server); if (exists(trackerFile)) { std.file.remove(trackerFile); writeln("Removed idle tracker for server " ~ server.name); } } private MCRconResponse executeRconCommand(in ServerMetaData server, string command) { MCRcon rcon = new MCRcon(); rcon.connect("127.0.0.1", server.rconPort); scope(exit) { rcon.disconnect(); } rcon.login(server.rconPassword); return rcon.command(command); } private bool isServiceActive(string serviceName) { import std.process; Pid pid = spawnProcess(["systemctl", "is-active", "--quiet", serviceName]); int result = wait(pid); return result == 0; } private string getIdleTrackerFilename(in ServerMetaData server) { return "agent-idle-tracker__" ~ server.name ~ ".txt"; }