mirror of
https://gitlab.com/crafty-controller/crafty-4.git
synced 2024-08-30 18:23:09 +00:00
scheduler, version change, database work, controller init servers, etc etc
This commit is contained in:
parent
25affba2e6
commit
ad541347af
138
app/classes/minecraft/controller.py
Normal file
138
app/classes/minecraft/controller.py
Normal file
@ -0,0 +1,138 @@
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
from app.classes.shared.models import db_helper
|
||||
|
||||
from app.classes.minecraft.server import Server
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Controller:
|
||||
|
||||
def __init__(self):
|
||||
self.servers_list = []
|
||||
|
||||
def init_all_servers(self):
|
||||
|
||||
# if we have servers defined, let's destroy it and start over
|
||||
if len(self.servers_list) > 0:
|
||||
self.servers_list = []
|
||||
|
||||
servers = db_helper.get_all_defined_servers()
|
||||
|
||||
for s in servers:
|
||||
settings_file = os.path.join(s['path'], 'server.properties')
|
||||
settings = ServerProps(settings_file)
|
||||
|
||||
temp_server_dict = {
|
||||
'server_id': s.get('server_id'),
|
||||
'server_data_obj': s,
|
||||
'server_obj': Server(),
|
||||
'server_settings': settings.props
|
||||
}
|
||||
|
||||
# setup the server, do the auto start and all that jazz
|
||||
temp_server_dict['server_obj'].do_server_setup(s)
|
||||
|
||||
# add this temp object to the list of init servers
|
||||
self.servers_list.append(temp_server_dict)
|
||||
|
||||
console.info("Loaded Server: ID {} | Name: {} | Autostart: {} | Delay: {} ".format(
|
||||
s['server_id'],
|
||||
s['server_name'],
|
||||
s['auto_start'],
|
||||
s['auto_start_delay']
|
||||
))
|
||||
|
||||
def get_server_obj(self, server_id):
|
||||
|
||||
for s in self.servers_list:
|
||||
if int(s['server_id']) == int(server_id):
|
||||
return s['server_obj']
|
||||
|
||||
logger.warning("Unable to find server object for server id {}".format(server_id))
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def list_defined_servers():
|
||||
servers = db_helper.get_all_defined_servers()
|
||||
|
||||
def list_running_servers(self):
|
||||
running_servers = []
|
||||
|
||||
if len(self.servers_list) > 0:
|
||||
|
||||
# for each server
|
||||
for s in self.servers_list:
|
||||
|
||||
# is the server running?
|
||||
srv_obj = s['server_obj']
|
||||
running = srv_obj.check_running()
|
||||
# if so, let's add a dictionary to the list of running servers
|
||||
if running:
|
||||
running_servers.append({
|
||||
'id': srv_obj.server_id,
|
||||
'name': srv_obj.name
|
||||
})
|
||||
|
||||
return running_servers
|
||||
|
||||
def stop_all_servers(self):
|
||||
servers = self.list_running_servers()
|
||||
logger.info("Found {} running server(s)".format(len(servers)))
|
||||
console.info("Found {} running server(s)".format(len(servers)))
|
||||
|
||||
logger.info("Stopping All Servers")
|
||||
console.info("Stopping All Servers")
|
||||
|
||||
for s in servers:
|
||||
logger.info("Stopping Server ID {} - {}".format(s['id'], s['name']))
|
||||
console.info("Stopping Server ID {} - {}".format(s['id'], s['name']))
|
||||
|
||||
# get object
|
||||
svr_obj = self.get_server_obj(s['id'])
|
||||
running = svr_obj.check_running(True)
|
||||
|
||||
# issue the stop command
|
||||
svr_obj.stop_threaded_server()
|
||||
|
||||
# while it's running, we wait
|
||||
x = 0
|
||||
while running:
|
||||
logger.info("Server {} is still running - waiting 2s to see if it stops".format(s['name']))
|
||||
console.info("Server {} is still running - waiting 2s to see if it stops".format(s['name']))
|
||||
running = svr_obj.check_running()
|
||||
|
||||
# let's keep track of how long this is going on...
|
||||
x = x + 1
|
||||
|
||||
# if we have been waiting more than 120 seconds. let's just kill the pid
|
||||
if x >= 60:
|
||||
logger.error("Server {} is taking way too long to stop. Killing this process".format(s['name']))
|
||||
console.error("Server {} is taking way too long to stop. Killing this process".format(s['name']))
|
||||
|
||||
svr_obj.killpid(svr_obj.PID)
|
||||
running = False
|
||||
|
||||
# if we killed the server, let's clean up the object
|
||||
if not running:
|
||||
svr_obj.cleanup_server_object()
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# let's wait 2 seconds to let everything flush out
|
||||
time.sleep(2)
|
||||
|
||||
logger.info("All Servers Stopped")
|
||||
console.info("All Servers Stopped")
|
||||
|
||||
|
||||
controller = Controller()
|
270
app/classes/minecraft/server.py
Normal file
270
app/classes/minecraft/server.py
Normal file
@ -0,0 +1,270 @@
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import time
|
||||
import psutil
|
||||
import pexpect
|
||||
import datetime
|
||||
import threading
|
||||
import schedule
|
||||
import logging.config
|
||||
|
||||
from pexpect.popen_spawn import PopenSpawn
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Server:
|
||||
|
||||
def __init__(self):
|
||||
# holders for our process
|
||||
self.process = None
|
||||
self.line = False
|
||||
self.PID = None
|
||||
self.start_time = None
|
||||
self.server_command = None
|
||||
self.server_path = None
|
||||
self.server_thread = None
|
||||
self.settings = None
|
||||
self.updating = False
|
||||
self.server_id = None
|
||||
self.name = None
|
||||
self.is_crashed = False
|
||||
self.restart_count = 0
|
||||
|
||||
def do_server_setup(self, server_data_obj):
|
||||
logger.info('Creating Server object: {} | Server Name: {} | Auto Start: {}'.format(
|
||||
server_data_obj['server_id'],
|
||||
server_data_obj['server_name'],
|
||||
server_data_obj['auto_start']
|
||||
))
|
||||
self.server_id = server_data_obj['server_id']
|
||||
self.name = server_data_obj['server_name']
|
||||
self.settings = server_data_obj
|
||||
|
||||
# build our server run command
|
||||
self.setup_server_run_command()
|
||||
|
||||
if server_data_obj['auto_start']:
|
||||
delay = int(self.settings['auto_start_delay'])
|
||||
|
||||
logger.info("Scheduling server {} to start in {} seconds".format(self.name, delay))
|
||||
console.info("Scheduling server {} to start in {} seconds".format(self.name, delay))
|
||||
|
||||
schedule.every(delay).seconds.do(self.run_scheduled_server)
|
||||
|
||||
def run_scheduled_server(self):
|
||||
console.info("Starting Minecraft server ID: {} - {}".format(self.server_id, self.name))
|
||||
logger.info("Starting Minecraft server {}".format(self.server_id, self.name))
|
||||
self.run_threaded_server()
|
||||
|
||||
# remove the scheduled job since it's ran
|
||||
return schedule.CancelJob
|
||||
|
||||
def run_threaded_server(self):
|
||||
# start the server
|
||||
self.server_thread = threading.Thread(target=self.start_server, daemon=True)
|
||||
self.server_thread.start()
|
||||
|
||||
def setup_server_run_command(self):
|
||||
# configure the server
|
||||
server_exec_path = self.settings['executable']
|
||||
self.server_command = self.settings['execution_command']
|
||||
self.server_path = self.settings['path']
|
||||
|
||||
# let's do some quick checking to make sure things actually exists
|
||||
full_path = os.path.join(self.server_path, server_exec_path)
|
||||
if not helper.check_file_exists(full_path):
|
||||
logger.critical("Server executable path: {} does not seem to exist".format(full_path))
|
||||
console.critical("Server executable path: {} does not seem to exist".format(full_path))
|
||||
helper.do_exit()
|
||||
|
||||
if not helper.check_path_exits(self.server_path):
|
||||
logger.critical("Server path: {} does not seem to exits".format(self.server_path))
|
||||
console.critical("Server path: {} does not seem to exits".format(self.server_path))
|
||||
helper.do_exit()
|
||||
|
||||
if not helper.check_writeable(self.server_path):
|
||||
logger.critical("Unable to write/access {}".format(self.server_path))
|
||||
console.warning("Unable to write/access {}".format(self.server_path))
|
||||
helper.do_exit()
|
||||
|
||||
def start_server(self):
|
||||
# fail safe in case we try to start something already running
|
||||
if self.check_running():
|
||||
logger.error("Server is already running - Cancelling Startup")
|
||||
console.error("Server is already running - Cancelling Startup")
|
||||
return False
|
||||
|
||||
logger.info("Launching Server {} with command {}".format(self.name, self.server_command))
|
||||
console.info("Launching Server {} with command {}".format(self.name, self.server_command))
|
||||
|
||||
if os.name == "nt":
|
||||
logger.info("Windows Detected - launching cmd")
|
||||
self.server_command = self.server_command.replace('\\', '/')
|
||||
logging.info("Opening CMD prompt")
|
||||
self.process = pexpect.popen_spawn.PopenSpawn('cmd \r\n', timeout=None, encoding=None)
|
||||
|
||||
drive_letter = self.server_path[:1]
|
||||
|
||||
if drive_letter.lower() != "c":
|
||||
logger.info("Server is not on the C drive, changing drive letter to {}:".format(drive_letter))
|
||||
self.process.send("{}:\r\n".format(drive_letter))
|
||||
|
||||
logging.info("changing directories to {}".format(self.server_path.replace('\\', '/')))
|
||||
self.process.send('cd {} \r\n'.format(self.server_path.replace('\\', '/')))
|
||||
logging.info("Sending command {} to CMD".format(self.server_command))
|
||||
self.process.send(self.server_command + "\r\n")
|
||||
|
||||
self.is_crashed = False
|
||||
else:
|
||||
logger.info("Linux Detected - launching Bash")
|
||||
self.process = pexpect.popen_spawn.PopenSpawn('/bin/bash \n', timeout=None, encoding=None)
|
||||
|
||||
logger.info("Changing directory to {}".format(self.server_path))
|
||||
self.process.send('cd {} \n'.format(self.server_path))
|
||||
|
||||
logger.info("Sending server start command: {} to shell".format(self.server_command))
|
||||
self.process.send(self.server_command + '\n')
|
||||
self.is_crashed = False
|
||||
|
||||
ts = time.time()
|
||||
self.start_time = str(datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S'))
|
||||
|
||||
if psutil.pid_exists(self.process.pid):
|
||||
parent = psutil.Process(self.process.pid)
|
||||
time.sleep(.5)
|
||||
children = parent.children(recursive=True)
|
||||
for c in children:
|
||||
self.PID = c.pid
|
||||
logger.info("Server {} running with PID {}".format(self.name, self.PID))
|
||||
console.info("Server {} running with PID {}".format(self.name, self.PID))
|
||||
self.is_crashed = False
|
||||
else:
|
||||
logger.warning("Server PID {} died right after starting - is this a server config issue?".format(self.PID))
|
||||
console.warning("Server PID {} died right after starting - is this a server config issue?".format(self.PID))
|
||||
|
||||
if self.settings['crash_detection']:
|
||||
logger.info("Server {} has crash detection enabled - starting watcher task".format(self.name))
|
||||
console.info("Server {} has crash detection enabled - starting watcher task".format(self.name))
|
||||
|
||||
# TODO: create crash detection watcher and such
|
||||
# schedule.every(30).seconds.do(self.check_running).tag(self.name)
|
||||
|
||||
def stop_threaded_server(self):
|
||||
self.stop_server()
|
||||
|
||||
if self.server_thread:
|
||||
self.server_thread.join()
|
||||
|
||||
def stop_server(self):
|
||||
if self.settings['stop_command_needed']:
|
||||
self.send_command(self.settings['stop_command'])
|
||||
else:
|
||||
self.killpid(self.PID)
|
||||
|
||||
def cleanup_server_object(self):
|
||||
self.process = None
|
||||
self.PID = None
|
||||
self.start_time = None
|
||||
self.name = None
|
||||
|
||||
def check_running(self, shutting_down=False):
|
||||
# if process is None, we never tried to start
|
||||
if self.PID is None:
|
||||
return False
|
||||
|
||||
try:
|
||||
running = psutil.pid_exists(self.PID)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Unable to find if server PID exists: {}".format(self.PID))
|
||||
running = False
|
||||
pass
|
||||
|
||||
if not running:
|
||||
|
||||
# did the server crash?
|
||||
if not shutting_down:
|
||||
|
||||
# do we have crash detection turned on?
|
||||
if self.settings['crash_detection']:
|
||||
|
||||
# if we haven't tried to restart more 3 or more times
|
||||
if self.restart_count <= 3:
|
||||
|
||||
# start the server if needed
|
||||
server_restarted = self.crash_detected(self.name)
|
||||
|
||||
if server_restarted:
|
||||
# add to the restart count
|
||||
self.restart_count = self.restart_count + 1
|
||||
return False
|
||||
|
||||
# we have tried to restart 4 times...
|
||||
elif self.restart_count == 4:
|
||||
logger.warning("Server {} has been restarted {} times. It has crashed, not restarting.".format(
|
||||
self.name, self.restart_count))
|
||||
|
||||
# set to 99 restart attempts so this elif is skipped next time. (no double logging)
|
||||
self.restart_count = 99
|
||||
self.is_crashed = True
|
||||
return False
|
||||
else:
|
||||
self.is_crashed = True
|
||||
return False
|
||||
|
||||
self.process = None
|
||||
self.PID = None
|
||||
self.name = None
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def send_command(self, command):
|
||||
|
||||
if not self.check_running() and command.lower() != 'start':
|
||||
logger.warning("Server not running, unable to send command \"{}\"".format(command))
|
||||
return False
|
||||
|
||||
logger.debug("Sending command {} to server via pexpect".format(command))
|
||||
|
||||
# send it
|
||||
self.process.send(command + '\n')
|
||||
|
||||
def crash_detected(self, name):
|
||||
|
||||
# the server crashed, or isn't found - so let's reset things.
|
||||
logger.warning("The server {} seems to have vanished unexpectedly, did it crash?".format(name))
|
||||
|
||||
if self.settings['crash_detection']:
|
||||
logger.info("The server {} has crashed and will be restarted. Restarting server".format(name))
|
||||
self.run_threaded_server()
|
||||
return True
|
||||
else:
|
||||
logger.info("The server {} has crashed, crash detection is disabled and it will not be restarted".format(name))
|
||||
return False
|
||||
|
||||
def killpid(self, pid):
|
||||
logger.info("Terminating PID {} and all child processes".format(pid))
|
||||
process = psutil.Process(pid)
|
||||
|
||||
# for every sub process...
|
||||
for proc in process.children(recursive=True):
|
||||
# kill all the child processes - it sounds too wrong saying kill all the children (kevdagoat: lol!)
|
||||
logger.info("Sending SIGKILL to PID {}".format(proc.name))
|
||||
proc.kill()
|
||||
# kill the main process we are after
|
||||
logger.info('Sending SIGKILL to parent')
|
||||
process.kill()
|
||||
|
||||
def get_start_time(self):
|
||||
if self.check_running():
|
||||
return self.start_time
|
||||
else:
|
||||
return False
|
||||
|
||||
|
66
app/classes/minecraft/server_props.py
Normal file
66
app/classes/minecraft/server_props.py
Normal file
@ -0,0 +1,66 @@
|
||||
import pprint
|
||||
import os
|
||||
|
||||
class ServerProps:
|
||||
|
||||
def __init__(self, filepath):
|
||||
self.filepath = filepath
|
||||
self.props = self._parse()
|
||||
|
||||
def _parse(self):
|
||||
"""Loads and parses the file speified in self.filepath"""
|
||||
with open(self.filepath) as fp:
|
||||
line = fp.readline()
|
||||
d = {}
|
||||
if os.path.exists(".header"):
|
||||
os.remove(".header")
|
||||
while line:
|
||||
if '#' != line[0]:
|
||||
s = line
|
||||
s1 = s[:s.find('=')]
|
||||
if '\n' in s:
|
||||
s2 = s[s.find('=')+1:s.find('\\')]
|
||||
else:
|
||||
s2 = s[s.find('=')+1:]
|
||||
d[s1] = s2
|
||||
else:
|
||||
with open(".header", "a+") as h:
|
||||
h.write(line)
|
||||
line = fp.readline()
|
||||
return d
|
||||
|
||||
def print(self):
|
||||
"""Prints the properties dictionary (using pprint)"""
|
||||
pprint.pprint(self.props)
|
||||
|
||||
def get(self):
|
||||
"""Returns the properties dictionary"""
|
||||
return self.props
|
||||
|
||||
def update(self, key, val):
|
||||
"""Updates property in the properties dictionary [ update("pvp", "true") ] and returns boolean condition"""
|
||||
if key in self.props.keys():
|
||||
self.props[key] = val
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def save(self):
|
||||
"""Writes to the new file"""
|
||||
with open(self.filepath, "a+") as f:
|
||||
f.truncate(0)
|
||||
with open(".header") as header:
|
||||
line = header.readline()
|
||||
while line:
|
||||
f.write(line)
|
||||
line = header.readline()
|
||||
header.close()
|
||||
for key, value in self.props.items():
|
||||
f.write(key + "=" + value + "\n")
|
||||
if os.path.exists(".header"):
|
||||
os.remove(".header")
|
||||
|
||||
@staticmethod
|
||||
def cleanup():
|
||||
if os.path.exists(".header"):
|
||||
os.remove(".header")
|
@ -32,7 +32,7 @@ class Helpers:
|
||||
self.session_file = os.path.join(self.root_dir, 'session.lock')
|
||||
self.settings_file = os.path.join(self.root_dir, 'config.ini')
|
||||
self.webroot = os.path.join(self.root_dir, 'app', 'frontend')
|
||||
self.db_path = os.path.join(self.root_dir, 'commander.sqlite')
|
||||
self.db_path = os.path.join(self.root_dir, 'crafty.sqlite')
|
||||
self.passhasher = PasswordHasher()
|
||||
self.exiting = False
|
||||
|
||||
@ -74,6 +74,15 @@ class Helpers:
|
||||
|
||||
return version_data
|
||||
|
||||
def get_version_string(self):
|
||||
|
||||
version_data = self.get_version()
|
||||
# set some defaults if we don't get version_data from our helper
|
||||
version = "{}.{}.{}".format(version_data.get('major', '?'),
|
||||
version_data.get('minor', '?'),
|
||||
version_data.get('sub', '?'))
|
||||
return str(version)
|
||||
|
||||
def do_exit(self):
|
||||
exit_file = os.path.join(self.root_dir, 'exit.txt')
|
||||
try:
|
||||
@ -111,6 +120,7 @@ class Helpers:
|
||||
|
||||
def ensure_logging_setup(self):
|
||||
log_file = os.path.join(os.path.curdir, 'logs', 'commander.log')
|
||||
session_log_file = os.path.join(os.path.curdir, 'logs', 'session.log')
|
||||
|
||||
logger.info("Checking app directory writable")
|
||||
|
||||
@ -136,7 +146,7 @@ class Helpers:
|
||||
|
||||
# del any old session.lock file as this is a new session
|
||||
try:
|
||||
os.remove(self.session_file)
|
||||
os.remove(session_log_file)
|
||||
except:
|
||||
pass
|
||||
|
||||
@ -326,5 +336,11 @@ class Helpers:
|
||||
"""
|
||||
return ''.join(random.choice(chars) for x in range(size))
|
||||
|
||||
@staticmethod
|
||||
def is_os_windows():
|
||||
if os.name == 'nt':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
helper = Helpers()
|
||||
|
@ -1,15 +1,18 @@
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.minecraft.server_props import ServerProps
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from peewee import *
|
||||
from playhouse.shortcuts import model_to_dict
|
||||
import yaml
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
@ -59,6 +62,23 @@ class Host_Stats(BaseModel):
|
||||
table_name = "host_stats"
|
||||
|
||||
|
||||
class Servers(BaseModel):
|
||||
server_id = AutoField()
|
||||
created = DateTimeField(default=datetime.datetime.now)
|
||||
server_name = CharField(default="Server")
|
||||
path = CharField(default="")
|
||||
executable = CharField(default="")
|
||||
log_path = CharField(default="")
|
||||
execution_command = CharField(default="")
|
||||
auto_start = BooleanField(default=0)
|
||||
auto_start_delay = IntegerField(default=10)
|
||||
crash_detection = BooleanField(default=0)
|
||||
stop_command = CharField(default="stop")
|
||||
|
||||
class Meta:
|
||||
table_name = "servers"
|
||||
|
||||
|
||||
class Webhooks(BaseModel):
|
||||
id = AutoField()
|
||||
name = CharField(max_length=64, unique=True)
|
||||
@ -80,6 +100,7 @@ class Backups(BaseModel):
|
||||
class Meta:
|
||||
table_name = 'backups'
|
||||
|
||||
|
||||
class db_builder:
|
||||
|
||||
@staticmethod
|
||||
@ -89,7 +110,8 @@ class db_builder:
|
||||
Backups,
|
||||
Users,
|
||||
Host_Stats,
|
||||
Webhooks
|
||||
Webhooks,
|
||||
Servers
|
||||
])
|
||||
|
||||
@staticmethod
|
||||
@ -105,7 +127,25 @@ class db_builder:
|
||||
def is_fresh_install():
|
||||
if helper.check_file_exists(helper.db_path):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class db_shortcuts:
|
||||
|
||||
def return_rows(self, query):
|
||||
rows = []
|
||||
|
||||
if query:
|
||||
for s in query:
|
||||
rows.append(model_to_dict(s))
|
||||
else:
|
||||
rows.append({})
|
||||
return rows
|
||||
|
||||
def get_all_defined_servers(self):
|
||||
query = Servers.select()
|
||||
return self.return_rows(query)
|
||||
|
||||
|
||||
installer = db_builder()
|
||||
db_helper = db_shortcuts()
|
@ -5,13 +5,20 @@ import time
|
||||
import logging
|
||||
import threading
|
||||
|
||||
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.web.tornado import webserver
|
||||
from app.classes.minecraft import server_props
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import schedule
|
||||
|
||||
except ModuleNotFoundError as e:
|
||||
logger.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
console.critical("Import Error: Unable to load {} module".format(e, e.name))
|
||||
sys.exit(1)
|
||||
|
||||
class TasksManager:
|
||||
|
||||
@ -22,6 +29,8 @@ class TasksManager:
|
||||
self.main_kill_switch_thread = threading.Thread(target=self.main_kill_switch, daemon=True, name="main_loop")
|
||||
self.main_thread_exiting = False
|
||||
|
||||
self.schedule_thread = threading.Thread(target=self.scheduler_thread, daemon=True, name="scheduler")
|
||||
|
||||
def get_main_thread_run_status(self):
|
||||
return self.main_thread_exiting
|
||||
|
||||
@ -39,9 +48,12 @@ class TasksManager:
|
||||
# commander.stop_all_servers()
|
||||
logger.info("***** Crafty Shutting Down *****\n\n")
|
||||
console.info("***** Crafty Shutting Down *****\n\n")
|
||||
os.remove(helper.session_file)
|
||||
os.remove(os.path.join(helper.root_dir, 'exit.txt'))
|
||||
# ServerProps.cleanup()
|
||||
try:
|
||||
os.remove(helper.session_file)
|
||||
os.remove(os.path.join(helper.root_dir, 'exit.txt'))
|
||||
os.remove(os.path.join(helper.root_dir, '.header'))
|
||||
except:
|
||||
pass
|
||||
self.main_thread_exiting = True
|
||||
|
||||
def start_webserver(self):
|
||||
@ -57,6 +69,16 @@ class TasksManager:
|
||||
def stop_webserver(self):
|
||||
self.tornado.stop_web_server()
|
||||
|
||||
def start_scheduler(self):
|
||||
logger.info("Launching Scheduler Thread...")
|
||||
console.info("Launching Scheduler Thread...")
|
||||
self.schedule_thread.start()
|
||||
|
||||
@staticmethod
|
||||
def scheduler_thread():
|
||||
while True:
|
||||
schedule.run_pending()
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
|
||||
|
@ -5,12 +5,13 @@ import tornado.escape
|
||||
import bleach
|
||||
|
||||
from app.classes.shared.console import console
|
||||
from app.classes.shared.models import Users
|
||||
from app.classes.shared.models import Users, installer
|
||||
from app.classes.web.base_handler import BaseHandler
|
||||
|
||||
from app.classes.minecraft.controller import controller
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PanelHandler(BaseHandler):
|
||||
|
||||
@tornado.web.authenticated
|
||||
@ -25,6 +26,8 @@ class PanelHandler(BaseHandler):
|
||||
'user_data': user_data
|
||||
}
|
||||
|
||||
servers = controller.list_defined_servers()
|
||||
|
||||
if page == 'unauthorized':
|
||||
template = "panel/denied.html"
|
||||
|
||||
|
@ -40,7 +40,9 @@ class PublicHandler(BaseHandler):
|
||||
self.clear_cookie("user")
|
||||
self.clear_cookie("user_data")
|
||||
|
||||
# print(page)
|
||||
page_data = {
|
||||
'version': helper.get_version_string()
|
||||
}
|
||||
|
||||
error = bleach.clean(self.get_argument('error', ""))
|
||||
|
||||
@ -51,19 +53,14 @@ class PublicHandler(BaseHandler):
|
||||
|
||||
# sensible defaults
|
||||
template = "public/404.html"
|
||||
page_data = "{}"
|
||||
|
||||
# if we have no page, let's go to login
|
||||
if page is None:
|
||||
self.redirect("public/login")
|
||||
|
||||
if page == "login":
|
||||
template = "public/login.html"
|
||||
page_data = {'error': error_msg}
|
||||
page_data['error'] = error_msg
|
||||
|
||||
# our default 404 template
|
||||
# if we have no page, let's go to login
|
||||
else:
|
||||
page_data = {'error': error_msg}
|
||||
self.redirect("public/login")
|
||||
|
||||
self.render(template, data=page_data)
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"major": 0.1,
|
||||
"minor": 1,
|
||||
"major": 4,
|
||||
"minor": 0,
|
||||
"sub": "Alpha"
|
||||
}
|
51
app/frontend/templates/blank_base.html
Normal file
51
app/frontend/templates/blank_base.html
Normal file
@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>{% block title %}{{ _('Default') }}{% end %}</title>
|
||||
<!-- plugins:css -->
|
||||
<link rel="stylesheet" href="/static/assets/vendors/mdi/css/materialdesignicons.min.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/flag-icon-css/css/flag-icon.min.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/ti-icons/css/themify-icons.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/typicons/typicons.css">
|
||||
<link rel="stylesheet" href="/static/assets/vendors/css/vendor.bundle.base.css">
|
||||
<!-- endinject -->
|
||||
<!-- Plugin css for this page -->
|
||||
<!-- End Plugin css for this page -->
|
||||
<!-- Layout styles -->
|
||||
<link rel="stylesheet" href="/static/assets/css/dark/style.css">
|
||||
<!-- End Layout styles -->
|
||||
<link rel="shortcut icon" href="/static/assets/images/favicon.png" />
|
||||
</head>
|
||||
<body class="dark-theme">
|
||||
<div class="container-scroller">
|
||||
<div class="container-fluid page-body-wrapper full-page-wrapper">
|
||||
<div class="content-wrapper d-flex align-items-center auth auth-bg-1 theme-one">
|
||||
<div class="row w-100">
|
||||
<div class="col-lg-4 mx-auto">
|
||||
|
||||
{% block content %}
|
||||
{% end %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- content-wrapper ends -->
|
||||
</div>
|
||||
<!-- page-body-wrapper ends -->
|
||||
</div>
|
||||
<!-- container-scroller -->
|
||||
<!-- plugins:js -->
|
||||
<script src="/static/assets/vendors/js/vendor.bundle.base.js"></script>
|
||||
<!-- endinject -->
|
||||
<!-- inject:js -->
|
||||
<script src="/static/assets/js/shared/off-canvas.js"></script>
|
||||
<script src="/static/assets/js/shared/hoverable-collapse.js"></script>
|
||||
<script src="/static/assets/js/shared/misc.js"></script>
|
||||
<script src="/static/assets/js/shared/settings.js"></script>
|
||||
<script src="/static/assets/js/shared/todolist.js"></script>
|
||||
<!-- endinject -->
|
||||
</body>
|
||||
</html>
|
@ -6,28 +6,13 @@
|
||||
|
||||
<li class="nav-item nav-category" style="margin-top:10px;">Main Menu</li>
|
||||
|
||||
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="collapse" href="#dashboard-dropdown" aria-expanded="false" aria-controls="dashboard-dropdown">
|
||||
<i class="menu-icon fas fa-chart-network"></i>
|
||||
<span class="menu-title">Nodes</span>
|
||||
<i class="menu-arrow"></i>
|
||||
<a class="nav-link" href="/panel/dashboard">
|
||||
<i class="fas fa-chart-network"></i>
|
||||
<span class="menu-title">Dashboard</span>
|
||||
</a>
|
||||
<div class="collapse" id="dashboard-dropdown">
|
||||
<ul class="nav flex-column sub-menu">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/pro/node_names">Nodes Claims</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="index.html">Buntu-Server</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="pages/dashboards/dashboard-2.html">RMDC-Stone</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="collapse" href="#page-layouts" aria-expanded="false" aria-controls="page-layouts">
|
||||
<i class="menu-icon typcn typcn-archive"></i>
|
||||
|
@ -25,14 +25,14 @@
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="d-flex">
|
||||
<div class="wrapper">
|
||||
<h5 class="mb-1 font-weight-medium text-primary"> Nodes</h5>
|
||||
<h3 class="mb-0 font-weight-semibold">2 </h3>
|
||||
<h5 class="mb-1 font-weight-medium text-primary"> Host</h5>
|
||||
<h3 class="mb-0 font-weight-semibold"> <i class="fas fa-chart-line"></i></h3>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="wrapper my-auto ml-auto ml-lg-4">
|
||||
<p class="mb-0 text-success">1 Online</p>
|
||||
<p class="mb-0 text-danger">1 Offline</p>
|
||||
<p class="mb-0 text-success">3.5% CPU</p>
|
||||
<p class="mb-0 text-danger">80% Memory</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -55,15 +55,7 @@
|
||||
</div>
|
||||
|
||||
<div class="text-block text-center my-3">
|
||||
<span class="text-small font-weight-semibold">Not a member ?</span>
|
||||
<a href="/public/register" class="text-small">Closed During Alpha Testing</a>
|
||||
</div>
|
||||
<div class="text-block text-center my-3">
|
||||
<span class="text-small font-weight-semibold">Want to know more?</span>
|
||||
<a href="/public/timeline" class="text-small">See the release timeline</a>
|
||||
</div>
|
||||
<div class="text-block text-center my-3">
|
||||
<span class="text-small font-weight-semibold">Copyright © 2020 Rent My Data Center. All rights reserved.</span>
|
||||
<span class="text-small font-weight-semibold"><a href="https://craftycontrol.com/">Crafty Control {{data['version'] }}</a> </span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
66
app/frontend/templates/setup/setup1.html
Executable file
66
app/frontend/templates/setup/setup1.html
Executable file
@ -0,0 +1,66 @@
|
||||
{% extends ../blank_base.html %}
|
||||
|
||||
{% block title %}Crafty Controller - Setup 1{% end %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="auto-form-wrapper">
|
||||
<div class="text-center">
|
||||
<!-- <img src="/static/assets/images/logo_long.jpg">-->
|
||||
{{ _('Configure Your Existing Server') }}<br /><br />
|
||||
</div>
|
||||
<form action="/public/login" method="post">
|
||||
{% raw xsrf_form_html() %}
|
||||
|
||||
<div class="form-group">
|
||||
<label class="label">
|
||||
{{ _('Server Name') }} - <small>{{ _('Example Survival Server') }}</small>
|
||||
</label>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="{{ _('Server Name') }}" name="server_name" value="{{_('MyFirstServer') }}" maxlength="55">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
{% if data['is_windows'] %}
|
||||
<label class="label">
|
||||
{{ _('Server Path') }} - <small>{{ _('Example c:\minecraft\server') }}</small>
|
||||
</label>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="{{ _('Server Path') }}" name="server_path"
|
||||
value="c:\windows\minecraft" maxlength="255">
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
|
||||
<label class="label">
|
||||
{{ _('Server Path') }} - <small>{{ _("Example: /var/opt/minecraft/server") }}</small>
|
||||
</label>
|
||||
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="{{ _('Server Path') }}" name="server_path"
|
||||
value="c:\windows\minecraft" maxlength="255">
|
||||
</div>
|
||||
{% end %}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="label">
|
||||
{{ _('Server Jar') }} - <small>{{ _('Example paper.jar') }}</small>
|
||||
</label>
|
||||
|
||||
<input type="text" class="form-control" placeholder="{{ _('Server Jar') }}" name="server_jar" value="paper.jar" maxlength="255">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<button class="btn btn-primary submit-btn btn-block">Save</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
{% end %}
|
24
main.py
24
main.py
@ -10,21 +10,16 @@ from app.classes.shared.console import console
|
||||
from app.classes.shared.helpers import helper
|
||||
from app.classes.shared.models import installer
|
||||
from app.classes.shared.tasks import tasks_manager
|
||||
from app.classes.minecraft.controller import controller
|
||||
|
||||
def do_intro():
|
||||
logger.info("***** Commander Agent Launched *****")
|
||||
logger.info("***** Crafty Controller Started *****")
|
||||
|
||||
version_data = helper.get_version()
|
||||
|
||||
# set some defaults if we don't get version_data from our helper
|
||||
version = "{}.{}.{}".format(version_data.get('major', '?'),
|
||||
version_data.get('minor', '?'),
|
||||
version_data.get('sub', '?'))
|
||||
version = helper.get_version_string()
|
||||
|
||||
intro = """
|
||||
{lines}
|
||||
#\t\tWelcome to Crafty Controller - v.{version}\t\t #
|
||||
#\t\t\t Codename: Commander \t\t\t\t #
|
||||
{lines}
|
||||
# \tServer Manager / Web Portal for your Minecraft server \t #
|
||||
# \t\tHomepage: www.craftycontrol.com\t\t\t #
|
||||
@ -85,6 +80,10 @@ if __name__ == '__main__':
|
||||
helper.create_session_file(ignore=args.ignore)
|
||||
|
||||
tasks_manager.start_webserver()
|
||||
tasks_manager.start_scheduler()
|
||||
|
||||
# slowing down reporting just for a 1/2 second so messages look cleaner
|
||||
time.sleep(.5)
|
||||
|
||||
# this should always be last
|
||||
tasks_manager.start_main_kill_switch_watcher()
|
||||
@ -94,7 +93,14 @@ if __name__ == '__main__':
|
||||
installer.create_tables()
|
||||
installer.default_settings()
|
||||
|
||||
# our main loop
|
||||
# init servers
|
||||
logger.info("Initializing all servers defined")
|
||||
console.info("Initializing all servers defined")
|
||||
|
||||
controller.init_all_servers()
|
||||
servers = controller.list_defined_servers()
|
||||
|
||||
# our main loop - eventually a shell
|
||||
while True:
|
||||
if tasks_manager.get_main_thread_run_status():
|
||||
sys.exit(0)
|
||||
|
Loading…
Reference in New Issue
Block a user