2020-08-16 22:47:53 -04:00
import os
2020-08-24 19:11:17 -04:00
import sys
2020-08-16 22:47:53 -04:00
import re
import json
import time
import datetime
import threading
import logging . config
2021-03-21 23:02:18 -05:00
import zipfile
2021-07-25 11:05:16 -04:00
from threading import Thread
2021-08-13 23:28:41 -05:00
import shutil
2021-09-25 14:29:28 -05:00
import subprocess
2021-08-13 23:28:41 -05:00
import zlib
2021-08-10 23:17:56 +03:00
import html
2022-01-11 18:30:06 -05:00
import apscheduler
from apscheduler . schedulers . background import BackgroundScheduler
2022-01-14 16:06:02 -05:00
#TZLocal is set as a hidden import on win pipeline
2022-01-14 14:59:06 -05:00
from tzlocal import get_localzone
2020-08-16 22:47:53 -04:00
from app . classes . shared . helpers import helper
from app . classes . shared . console import console
2022-01-09 13:14:59 -05:00
from app . classes . models . servers import Servers , helper_servers , servers_helper
2021-09-09 00:01:10 +02:00
from app . classes . models . management import management_helper
2021-07-25 17:04:10 -04:00
from app . classes . web . websocket_helper import websocket_helper
2021-09-13 15:37:59 -04:00
from app . classes . shared . translation import translation
2021-11-27 17:10:43 -05:00
from app . classes . models . users import users_helper
2020-08-24 19:11:17 -04:00
2020-08-16 22:47:53 -04:00
logger = logging . getLogger ( __name__ )
2020-08-24 19:11:17 -04:00
try :
2021-09-25 14:29:28 -05:00
import psutil
#import pexpect
2020-08-24 19:11:17 -04:00
except ModuleNotFoundError as e :
2021-04-17 23:34:13 +03:00
logger . critical ( " Import Error: Unable to load {} module " . format ( e . name ) , exc_info = True )
console . critical ( " Import Error: Unable to load {} module " . format ( e . name ) )
2020-08-24 19:11:17 -04:00
sys . exit ( 1 )
2021-08-10 23:17:56 +03:00
class ServerOutBuf :
lines = { }
2021-08-18 12:50:13 -04:00
2021-09-25 14:29:28 -05:00
def __init__ ( self , proc , server_id ) :
self . proc = proc
2021-08-10 23:17:56 +03:00
self . server_id = str ( server_id )
# Buffers text for virtual_terminal_lines config number of lines
self . max_lines = helper . get_setting ( ' virtual_terminal_lines ' )
self . line_buffer = ' '
ServerOutBuf . lines [ self . server_id ] = [ ]
2021-11-14 18:17:23 -06:00
self . lsi = 0
2021-08-10 23:17:56 +03:00
2021-11-06 12:06:26 -05:00
def process_byte ( self , char ) :
2021-11-14 18:17:23 -06:00
if char == os . linesep [ self . lsi ] :
self . lsi + = 1
else :
self . lsi = 0
self . line_buffer + = char
if self . lsi > = len ( os . linesep ) :
self . lsi = 0
2021-11-06 12:06:26 -05:00
ServerOutBuf . lines [ self . server_id ] . append ( self . line_buffer )
self . new_line_handler ( self . line_buffer )
self . line_buffer = ' '
# Limit list length to self.max_lines:
if len ( ServerOutBuf . lines [ self . server_id ] ) > self . max_lines :
ServerOutBuf . lines [ self . server_id ] . pop ( 0 )
2021-08-10 23:17:56 +03:00
def check ( self ) :
2021-11-06 12:06:26 -05:00
while True :
if self . proc . poll ( ) is None :
2021-12-13 20:44:22 -06:00
char = self . proc . stdout . read ( 1 ) . decode ( ' utf-8 ' , ' ignore ' )
2021-11-06 12:06:26 -05:00
# TODO: we may want to benchmark reading in blocks and userspace processing it later, reads are kind of expensive as a syscall
self . process_byte ( char )
2021-08-10 23:17:56 +03:00
else :
2021-12-13 20:44:22 -06:00
flush = self . proc . stdout . read ( ) . decode ( ' utf-8 ' , ' ignore ' )
2021-11-06 12:06:26 -05:00
for char in flush :
self . process_byte ( char )
2021-11-19 18:14:32 -06:00
break
2021-08-10 23:17:56 +03:00
def new_line_handler ( self , new_line ) :
2021-08-11 23:29:31 +03:00
new_line = re . sub ( ' ( \033 \\ [(0;)?[0-9]*[A-z]?(;[0-9])?m?)|(> ) ' , ' ' , new_line )
new_line = re . sub ( ' [A-z] {2} \b \b ' , ' ' , new_line )
2021-08-10 23:17:56 +03:00
highlighted = helper . log_colors ( html . escape ( new_line ) )
2021-08-11 23:29:31 +03:00
logger . debug ( ' Broadcasting new virtual terminal line ' )
2021-08-10 23:17:56 +03:00
# TODO: Do not send data to clients who do not have permission to view this server's console
websocket_helper . broadcast_page_params (
' /panel/server_detail ' ,
{
' id ' : self . server_id
} ,
' vterm_new_line ' ,
{
2021-08-11 23:29:31 +03:00
' line ' : highlighted + ' <br /> '
2021-08-10 23:17:56 +03:00
}
)
2020-08-24 19:11:17 -04:00
2020-08-16 22:47:53 -04:00
class Server :
2021-03-21 23:02:18 -05:00
def __init__ ( self , stats ) :
2020-08-16 22:47:53 -04:00
# holders for our process
self . process = None
self . line = False
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
2021-07-24 21:09:39 -04:00
self . jar_update_url = None
2020-08-16 22:47:53 -04:00
self . name = None
self . is_crashed = False
self . restart_count = 0
2020-12-11 09:52:36 -05:00
self . crash_watcher_schedule = None
2021-03-21 23:02:18 -05:00
self . stats = stats
2022-01-14 19:52:51 -05:00
tz = get_localzone ( )
self . server_scheduler = BackgroundScheduler ( timezone = str ( tz ) )
2021-11-19 18:14:32 -06:00
self . backup_thread = threading . Thread ( target = self . a_backup_server , daemon = True , name = f " backup_ { self . name } " )
2022-01-12 01:24:03 -05:00
self . is_backingup = False
2020-08-16 22:47:53 -04:00
2020-10-07 09:19:47 -04:00
def reload_server_settings ( self ) :
2021-09-09 00:01:10 +02:00
server_data = servers_helper . get_server_data_by_id ( self . server_id )
2020-10-07 09:19:47 -04:00
self . settings = server_data
2020-08-16 22:47:53 -04:00
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
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 ) )
2022-01-11 18:30:06 -05:00
self . server_scheduler . add_job ( self . run_scheduled_server , ' interval ' , seconds = delay , id = str ( self . server_id ) )
self . server_scheduler . start ( )
2020-08-16 22:47:53 -04:00
def run_scheduled_server ( self ) :
2020-08-31 13:53:46 -04:00
console . info ( " Starting server ID: {} - {} " . format ( self . server_id , self . name ) )
logger . info ( " Starting server {} " . format ( self . server_id , self . name ) )
2022-01-14 02:35:26 -05:00
#Sets waiting start to false since we're attempting to start the server.
servers_helper . set_waiting_start ( self . server_id , False )
2021-11-27 17:10:43 -05:00
self . run_threaded_server ( None )
2020-08-16 22:47:53 -04:00
# remove the scheduled job since it's ran
2022-01-11 18:30:06 -05:00
return self . server_scheduler . remove_job ( str ( self . server_id ) )
2020-08-16 22:47:53 -04:00
2021-11-27 17:10:43 -05:00
def run_threaded_server ( self , user_id ) :
2020-08-16 22:47:53 -04:00
# start the server
2021-11-27 17:10:43 -05:00
self . server_thread = threading . Thread ( target = self . start_server , daemon = True , args = ( user_id , ) , name = ' {} _server_thread ' . format ( self . server_id ) )
2020-08-16 22:47:53 -04:00
self . server_thread . start ( )
def setup_server_run_command ( self ) :
# configure the server
2021-11-21 11:52:29 +01:00
server_exec_path = helper . get_os_understandable_path ( self . settings [ ' executable ' ] )
2021-09-25 14:29:28 -05:00
self . server_command = helper . cmdparse ( self . settings [ ' execution_command ' ] )
2021-11-21 11:52:29 +01:00
self . server_path = helper . get_os_understandable_path ( self . settings [ ' path ' ] )
2020-08-16 22:47:53 -04:00
# 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 ) )
2021-01-17 19:20:28 +02:00
if not helper . check_path_exists ( self . server_path ) :
2020-08-16 22:47:53 -04:00
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 ) )
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 ) )
2021-11-27 17:10:43 -05:00
def start_server ( self , user_id ) :
if not user_id :
user_lang = helper . get_setting ( ' language ' )
else :
user_lang = users_helper . get_user_lang_by_id ( user_id )
2020-08-24 19:11:17 -04:00
2021-07-25 11:28:09 -04:00
logger . info ( " Start command detected. Reloading settings from DB for server {} " . format ( self . name ) )
self . setup_server_run_command ( )
2020-08-16 22:47:53 -04:00
# 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
2021-07-26 12:11:46 -04:00
if self . check_update ( ) :
logger . error ( " Server is updating. Terminating startup. " )
2021-07-25 17:04:10 -04:00
return False
2020-08-16 22:47:53 -04:00
logger . info ( " Launching Server {} with command {} " . format ( self . name , self . server_command ) )
console . info ( " Launching Server {} with command {} " . format ( self . name , self . server_command ) )
2022-01-09 13:14:59 -05:00
#Checks for eula. Creates one if none detected.
#If EULA is detected and not set to one of these true vaiants we offer to set it true.
2021-11-23 16:11:23 -05:00
if helper . check_file_exists ( os . path . join ( self . settings [ ' path ' ] , ' eula.txt ' ) ) :
f = open ( os . path . join ( self . settings [ ' path ' ] , ' eula.txt ' ) , ' r ' )
line = f . readline ( ) . lower ( )
if line == ' eula=true ' :
e_flag = True
elif line == ' eula = true ' :
e_flag = True
elif line == ' eula= true ' :
e_flag = True
elif line == ' eula =true ' :
e_flag = True
else :
e_flag = False
else :
e_flag = False
if e_flag == False :
2021-11-27 18:43:14 -05:00
if user_id :
websocket_helper . broadcast_user ( user_id , ' send_eula_bootbox ' , {
' id ' : self . server_id
} )
else :
logger . error ( " Autostart failed due to EULA being false. Agree not sent due to auto start. " )
return False
2021-11-23 16:11:23 -05:00
return False
f . close ( )
2021-11-21 11:52:29 +01:00
if helper . is_os_windows ( ) :
2021-02-13 05:47:30 +00:00
logger . info ( " Windows Detected " )
2021-09-25 14:29:28 -05:00
creationflags = subprocess . CREATE_NEW_CONSOLE
2020-08-16 22:47:53 -04:00
else :
2021-09-25 14:29:28 -05:00
logger . info ( " Unix Detected " )
creationflags = None
2020-08-16 22:47:53 -04:00
2021-02-13 05:47:30 +00:00
logger . info ( " Starting server in {p} with command: {c} " . format ( p = self . server_path , c = self . server_command ) )
2022-01-09 13:14:59 -05:00
2021-07-30 10:30:58 -04:00
try :
2021-09-25 14:29:28 -05:00
self . process = subprocess . Popen ( self . server_command , cwd = self . server_path , stdin = subprocess . PIPE , stdout = subprocess . PIPE , stderr = subprocess . STDOUT )
2021-07-30 10:30:58 -04:00
except Exception as ex :
2021-11-29 21:22:46 +00:00
#Checks for java on initial fail
if os . system ( " java -version " ) == 32512 :
msg = " Server {} failed to start with error code: {} " . format ( self . name , " Java not found. Please install Java then try again. " )
if user_id :
websocket_helper . broadcast_user ( user_id , ' send_start_error ' , {
' error ' : translation . translate ( ' error ' , ' noJava ' , user_lang ) . format ( self . name )
} )
return False
else :
msg = " Server {} failed to start with error code: {} " . format ( self . name , ex )
2021-08-07 13:15:53 -04:00
logger . error ( msg )
2021-11-27 17:10:43 -05:00
if user_id :
websocket_helper . broadcast_user ( user_id , ' send_start_error ' , {
' error ' : translation . translate ( ' error ' , ' start-error ' , user_lang ) . format ( self . name , ex )
} )
2021-07-30 10:30:58 -04:00
return False
2022-01-09 13:14:59 -05:00
2021-08-10 23:17:56 +03:00
out_buf = ServerOutBuf ( self . process , self . server_id )
2021-08-11 23:29:31 +03:00
logger . debug ( ' Starting virtual terminal listener for server {} ' . format ( self . name ) )
threading . Thread ( target = out_buf . check , daemon = True , name = ' {} _virtual_terminal ' . format ( self . server_id ) ) . start ( )
2021-08-18 12:50:13 -04:00
2021-02-13 05:47:30 +00:00
self . is_crashed = False
2020-08-16 22:47:53 -04:00
2021-04-18 00:20:23 +03:00
self . start_time = str ( datetime . datetime . utcnow ( ) . strftime ( ' % Y- % m- %d % H: % M: % S ' ) )
2020-08-16 22:47:53 -04:00
2021-09-25 14:29:28 -05:00
if self . process . poll ( ) is None :
logger . info ( " Server {} running with PID {} " . format ( self . name , self . process . pid ) )
console . info ( " Server {} running with PID {} " . format ( self . name , self . process . pid ) )
2021-02-13 05:47:30 +00:00
self . is_crashed = False
2021-03-21 23:48:06 -05:00
self . stats . record_stats ( )
2022-01-09 13:14:59 -05:00
check_internet_thread = threading . Thread ( target = self . check_internet_thread , daemon = True , args = ( user_id , user_lang , ) , name = " {self.name} _Internet " )
check_internet_thread . start ( )
#Checks if this is the servers first run.
2022-01-10 14:58:38 -05:00
if servers_helper . get_first_run ( self . server_id ) :
servers_helper . set_first_run ( self . server_id )
2022-01-09 13:14:59 -05:00
loc_server_port = servers_helper . get_server_stats_by_id ( self . server_id ) [ ' server_port ' ]
#Sends port reminder message.
websocket_helper . broadcast_user ( user_id , ' send_start_error ' , {
' error ' : translation . translate ( ' error ' , ' portReminder ' , user_lang ) . format ( self . name , loc_server_port )
} )
else :
websocket_helper . broadcast_user ( user_id , ' send_start_reload ' , {
} )
2020-08-16 22:47:53 -04:00
else :
2021-09-25 14:29:28 -05:00
logger . warning ( " Server PID {} died right after starting - is this a server config issue? " . format ( self . process . pid ) )
console . warning ( " Server PID {} died right after starting - is this a server config issue? " . format ( self . process . pid ) )
2020-08-16 22:47:53 -04:00
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 ) )
2022-01-14 19:52:51 -05:00
self . crash_watcher_schedule = self . server_scheduler . add_job ( self . detect_crash , ' interval ' , seconds = 30 , id = " crash_watcher " )
2022-01-09 13:14:59 -05:00
2021-12-10 04:52:37 +00:00
def check_internet_thread ( self , user_id , user_lang ) :
2021-12-09 17:58:36 -05:00
if user_id :
2022-01-09 13:14:59 -05:00
if not helper . check_internet ( ) :
2021-12-09 17:58:36 -05:00
websocket_helper . broadcast_user ( user_id , ' send_start_error ' , {
' error ' : translation . translate ( ' error ' , ' internet ' , user_lang )
} )
2021-12-10 04:52:37 +00:00
return
2020-08-16 22:47:53 -04:00
def stop_threaded_server ( self ) :
self . stop_server ( )
if self . server_thread :
self . server_thread . join ( )
def stop_server ( self ) :
2020-08-24 19:11:17 -04:00
if self . settings [ ' stop_command ' ] :
2020-08-16 22:47:53 -04:00
self . send_command ( self . settings [ ' stop_command ' ] )
2021-09-25 14:29:28 -05:00
else :
#windows will need to be handled separately for Ctrl+C
2021-11-19 18:14:32 -06:00
self . process . terminate ( )
2021-09-25 14:29:28 -05:00
running = self . check_running ( )
if not running :
logger . info ( " Can ' t stop server {} if it ' s not running " . format ( self . name ) )
console . info ( " Can ' t stop server {} if it ' s not running " . format ( self . name ) )
return
x = 0
2020-08-24 19:11:17 -04:00
2021-09-25 14:29:28 -05:00
# caching the name and pid number
server_name = self . name
server_pid = self . process . pid
2020-08-24 19:11:17 -04:00
2021-09-25 14:29:28 -05:00
while running :
x = x + 1
logstr = " Server {} is still running - waiting 2s to see if it stops ( {} seconds until force close) " . format ( server_name , int ( 60 - ( x * 2 ) ) )
logger . info ( logstr )
console . info ( logstr )
running = self . check_running ( )
time . sleep ( 2 )
2020-08-24 19:11:17 -04:00
2021-09-25 14:29:28 -05:00
# if we haven't closed in 60 seconds, let's just slam down on the PID
if x > = 30 :
logger . info ( " Server {} is still running - Forcing the process down " . format ( server_name ) )
console . info ( " Server {} is still running - Forcing the process down " . format ( server_name ) )
self . kill ( )
2020-08-24 19:11:17 -04:00
2021-09-25 14:29:28 -05:00
logger . info ( " Stopped Server {} with PID {} " . format ( server_name , server_pid ) )
console . info ( " Stopped Server {} with PID {} " . format ( server_name , server_pid ) )
2020-08-16 22:47:53 -04:00
2020-08-24 19:11:17 -04:00
# massive resetting of variables
self . cleanup_server_object ( )
2021-03-21 23:02:18 -05:00
self . stats . record_stats ( )
2020-08-24 19:11:17 -04:00
2021-11-27 17:10:43 -05:00
def restart_threaded_server ( self , user_id ) :
2020-08-24 19:11:17 -04:00
# if not already running, let's just start
if not self . check_running ( ) :
2021-11-27 17:10:43 -05:00
self . run_threaded_server ( user_id )
2020-08-24 19:11:17 -04:00
else :
self . stop_threaded_server ( )
time . sleep ( 2 )
2021-11-27 17:10:43 -05:00
self . run_threaded_server ( user_id )
2020-08-24 19:11:17 -04:00
2020-08-16 22:47:53 -04:00
def cleanup_server_object ( self ) :
self . start_time = None
2020-08-24 19:11:17 -04:00
self . restart_count = 0
self . is_crashed = False
self . updating = False
self . process = None
2020-08-16 22:47:53 -04:00
2020-12-11 09:52:36 -05:00
def check_running ( self ) :
2020-08-16 22:47:53 -04:00
# if process is None, we never tried to start
2021-09-25 14:29:28 -05:00
if self . process is None :
return False
poll = self . process . poll ( )
if poll is None :
return True
else :
self . last_rc = poll
return False
2020-08-16 22:47:53 -04:00
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
2022-01-18 13:59:53 -05:00
elif command . lower ( ) == self . settings [ ' stop_command ' ] :
logger . info ( " Stop command detected as terminal input - intercepting. Starting Crafty ' s stop process for server with id: {} . " . format ( self . server_id ) )
self . stop_threaded_server ( )
return
elif command . lower ( ) == ' restart ' :
logger . info ( " Restart command detected as terminal input - intercepting. Starting Crafty ' s restart process for server with id: {} " . format ( self . server_id ) )
self . restart_threaded_server ( )
return
console . info ( " COMMAND TIME: {} " . format ( command ) )
2021-09-25 14:29:28 -05:00
logger . debug ( " Sending command {} to server " . format ( command ) )
2020-08-16 22:47:53 -04:00
# send it
2021-09-25 14:29:28 -05:00
self . process . stdin . write ( " {} \n " . format ( command ) . encode ( ' utf-8 ' ) )
self . process . stdin . flush ( )
2020-08-16 22:47:53 -04:00
def crash_detected ( self , name ) :
2020-12-11 09:52:36 -05:00
# clear the old scheduled watcher task
self . remove_watcher_thread ( )
2020-08-16 22:47:53 -04:00
# 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 ' ] :
2020-12-11 09:52:36 -05:00
logger . warning ( " The server {} has crashed and will be restarted. Restarting server " . format ( name ) )
console . warning ( " The server {} has crashed and will be restarted. Restarting server " . format ( name ) )
2021-11-27 17:10:43 -05:00
self . run_threaded_server ( None )
2020-08-16 22:47:53 -04:00
return True
else :
2020-12-11 09:52:36 -05:00
logger . critical (
" The server {} has crashed, crash detection is disabled and it will not be restarted " . format ( name ) )
console . critical (
" The server {} has crashed, crash detection is disabled and it will not be restarted " . format ( name ) )
2020-08-16 22:47:53 -04:00
return False
2021-09-25 14:29:28 -05:00
def kill ( self ) :
logger . info ( " Terminating server {} and all child processes " . format ( self . server_id ) )
process = psutil . Process ( self . process . pid )
2020-08-16 22:47:53 -04:00
# 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!)
2021-09-25 14:29:28 -05:00
logger . info ( " Sending SIGKILL to server {} " . format ( proc . name ) )
2020-08-16 22:47:53 -04:00
proc . kill ( )
# kill the main process we are after
logger . info ( ' Sending SIGKILL to parent ' )
2021-09-25 14:29:28 -05:00
self . process . kill ( )
2020-08-16 22:47:53 -04:00
def get_start_time ( self ) :
if self . check_running ( ) :
return self . start_time
else :
return False
2021-09-13 15:03:47 -04:00
def get_pid ( self ) :
2021-09-25 14:29:28 -05:00
if self . process is not None :
return self . process . pid
else :
return None
2021-11-21 11:52:29 +01:00
2020-12-11 09:52:36 -05:00
def detect_crash ( self ) :
logger . info ( " Detecting possible crash for server: {} " . format ( self . name ) )
running = self . check_running ( )
# if all is okay, we just exit out
if running :
return
# 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
# we have tried to restart 4 times...
elif self . restart_count == 4 :
logger . critical ( " Server {} has been restarted {} times. It has crashed, not restarting. " . format (
self . name , self . restart_count ) )
console . critical ( " 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
# cancel the watcher task
self . remove_watcher_thread ( )
2020-08-16 22:47:53 -04:00
2020-12-11 09:52:36 -05:00
def remove_watcher_thread ( self ) :
logger . info ( " Removing old crash detection watcher thread " )
console . info ( " Removing old crash detection watcher thread " )
schedule . clear ( self . name )
2021-03-21 23:02:18 -05:00
2021-11-27 17:10:43 -05:00
def agree_eula ( self , user_id ) :
2021-11-23 16:11:23 -05:00
file = os . path . join ( self . server_path , ' eula.txt ' )
f = open ( file , ' w ' )
f . write ( ' eula=true ' )
f . close
2021-11-27 17:10:43 -05:00
self . run_threaded_server ( user_id )
2021-11-23 16:11:23 -05:00
2021-07-25 11:05:16 -04:00
def is_backup_running ( self ) :
if self . is_backingup :
return True
else :
return False
2021-03-21 23:02:18 -05:00
def backup_server ( self ) :
2021-11-19 18:14:32 -06:00
backup_thread = threading . Thread ( target = self . a_backup_server , daemon = True , name = f " backup_ { self . name } " )
2021-07-25 11:05:16 -04:00
logger . info ( " Starting Backup Thread for server {} . " . format ( self . settings [ ' server_name ' ] ) )
2021-11-19 01:07:53 -05:00
if self . server_path == None :
2021-11-21 11:52:29 +01:00
self . server_path = helper . get_os_understandable_path ( self . settings [ ' path ' ] )
2021-11-19 01:07:53 -05:00
logger . info ( " Backup Thread - Local server path not defined. Setting local server path variable. " )
2021-07-25 11:05:16 -04:00
#checks if the backup thread is currently alive for this server
if not self . is_backingup :
try :
backup_thread . start ( )
except Exception as ex :
logger . error ( " Failed to start backup: {} " . format ( ex ) )
return False
else :
logger . error ( " Backup is already being processed for server {} . Canceling backup request " . format ( self . settings [ ' server_name ' ] ) )
return False
logger . info ( " Backup Thread started for server {} . " . format ( self . settings [ ' server_name ' ] ) )
def a_backup_server ( self ) :
2021-08-12 00:33:18 -04:00
logger . info ( " Starting server {} (ID {} ) backup " . format ( self . name , self . server_id ) )
self . is_backingup = True
2021-09-09 00:01:10 +02:00
conf = management_helper . get_backup_config ( self . server_id )
2021-08-12 00:33:18 -04:00
try :
2021-08-14 00:00:09 -05:00
backup_filename = " {} / {} " . format ( self . settings [ ' backup_path ' ] , datetime . datetime . now ( ) . strftime ( ' % Y- % m- %d _ % H- % M- % S ' ) )
2021-08-12 00:33:18 -04:00
logger . info ( " Creating backup of server ' {} ' (ID# {} ) at ' {} ' " . format ( self . settings [ ' server_name ' ] , self . server_id , backup_filename ) )
2021-11-21 11:52:29 +01:00
shutil . make_archive ( helper . get_os_understandable_path ( backup_filename ) , ' zip ' , self . server_path )
2021-08-14 00:07:24 -05:00
while len ( self . list_backups ( ) ) > conf [ " max_backups " ] and conf [ " max_backups " ] > 0 :
2021-08-12 00:33:18 -04:00
backup_list = self . list_backups ( )
oldfile = backup_list [ 0 ]
2021-08-13 23:28:41 -05:00
oldfile_path = " {} / {} " . format ( conf [ ' backup_path ' ] , oldfile [ ' path ' ] )
logger . info ( " Removing old backup ' {} ' " . format ( oldfile [ ' path ' ] ) )
2021-11-21 11:52:29 +01:00
os . remove ( helper . get_os_understandable_path ( oldfile_path ) )
2021-08-12 00:33:18 -04:00
self . is_backingup = False
logger . info ( " Backup of server: {} completed " . format ( self . name ) )
return
except :
logger . exception ( " Failed to create backup of server {} (ID {} ) " . format ( self . name , self . server_id ) )
self . is_backingup = False
return
2021-03-21 23:02:18 -05:00
def list_backups ( self ) :
2021-11-27 19:28:53 -05:00
if self . settings [ ' backup_path ' ] :
if helper . check_path_exists ( helper . get_os_understandable_path ( self . settings [ ' backup_path ' ] ) ) :
files = helper . get_human_readable_files_sizes ( helper . list_dir_by_date ( helper . get_os_understandable_path ( self . settings [ ' backup_path ' ] ) ) )
2021-11-29 21:22:46 +00:00
return [ { " path " : os . path . relpath ( f [ ' path ' ] , start = helper . get_os_understandable_path ( self . settings [ ' backup_path ' ] ) ) , " size " : f [ " size " ] } for f in files ]
2021-11-27 19:28:53 -05:00
else :
return [ ]
2021-03-21 23:02:18 -05:00
else :
2021-11-29 21:22:46 +00:00
logger . info ( " Error putting backup file list for server with ID: {} " . format ( self . server_id ) )
2021-11-27 19:28:53 -05:00
return [ ]
2021-07-24 21:09:39 -04:00
def jar_update ( self ) :
2021-09-09 00:01:10 +02:00
servers_helper . set_update ( self . server_id , True )
2021-11-19 18:14:32 -06:00
update_thread = threading . Thread ( target = self . a_jar_update , daemon = True , name = f " exe_update_ { self . name } " )
2021-07-25 17:04:10 -04:00
update_thread . start ( )
2021-07-26 12:11:46 -04:00
def check_update ( self ) :
2022-01-10 14:58:38 -05:00
if servers_helper . get_update_status ( self . server_id ) :
2021-07-26 12:11:46 -04:00
return True
else :
return False
2021-07-25 17:04:10 -04:00
def a_jar_update ( self ) :
2021-08-11 23:33:18 -04:00
error = False
2021-07-25 11:05:16 -04:00
wasStarted = " -1 "
2021-07-26 21:14:36 -04:00
self . backup_server ( )
2021-07-24 21:09:39 -04:00
#checks if server is running. Calls shutdown if it is running.
if self . check_running ( ) :
2021-07-25 11:05:16 -04:00
wasStarted = True
2021-09-25 14:29:28 -05:00
logger . info ( " Server with PID {} is running. Sending shutdown command " . format ( self . process . pid ) )
2021-07-24 21:09:39 -04:00
self . stop_threaded_server ( )
2021-07-25 11:05:16 -04:00
else :
wasStarted = False
2021-07-26 21:45:33 -04:00
if len ( websocket_helper . clients ) > 0 :
# There are clients
self . check_update ( )
2021-07-26 21:49:47 -04:00
message = ' <a data-id= " ' + str ( self . server_id ) + ' " class= " " > UPDATING...</i></a> '
2021-11-27 18:32:53 -05:00
websocket_helper . broadcast_page ( ' /panel/server_detail ' , ' update_button_status ' , {
2021-07-26 21:45:33 -04:00
' isUpdating ' : self . check_update ( ) ,
' server_id ' : self . server_id ,
' wasRunning ' : wasStarted ,
' string ' : message
} )
2021-11-27 18:32:53 -05:00
websocket_helper . broadcast_page ( ' /panel/dashboard ' , ' send_start_reload ' , {
} )
2021-11-21 11:52:29 +01:00
backup_dir = os . path . join ( helper . get_os_understandable_path ( self . settings [ ' path ' ] ) , ' crafty_executable_backups ' )
2021-07-24 21:32:16 -04:00
#checks if backup directory already exists
if os . path . isdir ( backup_dir ) :
backup_executable = os . path . join ( backup_dir , ' old_server.jar ' )
2021-07-24 21:09:39 -04:00
else :
2021-07-25 17:04:10 -04:00
logger . info ( " Executable backup directory not found for Server: {} . Creating one. " . format ( self . name ) )
2021-07-24 21:32:16 -04:00
os . mkdir ( backup_dir )
backup_executable = os . path . join ( backup_dir , ' old_server.jar ' )
if os . path . isfile ( backup_executable ) :
#removes old backup
2021-07-25 11:05:16 -04:00
logger . info ( " Old backup found for server: {} . Removing... " . format ( self . name ) )
2021-07-24 21:32:16 -04:00
os . remove ( backup_executable )
2021-07-25 11:05:16 -04:00
logger . info ( " Old backup removed for server: {} . " . format ( self . name ) )
2021-07-24 21:09:39 -04:00
else :
2021-07-25 11:05:16 -04:00
logger . info ( " No old backups found for server: {} " . format ( self . name ) )
2021-07-24 21:09:39 -04:00
2021-11-21 11:52:29 +01:00
current_executable = os . path . join ( helper . get_os_understandable_path ( self . settings [ ' path ' ] ) , self . settings [ ' executable ' ] )
2021-07-24 21:09:39 -04:00
2021-07-24 21:32:16 -04:00
#copies to backup dir
helper . copy_files ( current_executable , backup_executable )
2021-07-24 21:09:39 -04:00
2021-07-24 21:32:16 -04:00
#boolean returns true for false for success
downloaded = helper . download_file ( self . settings [ ' executable_update_url ' ] , current_executable )
2021-07-24 21:09:39 -04:00
2021-09-09 00:01:10 +02:00
while servers_helper . get_server_stats_by_id ( self . server_id ) [ ' updating ' ] :
2021-08-11 23:33:18 -04:00
if downloaded and not self . is_backingup :
logger . info ( " Executable updated successfully. Starting Server " )
2021-09-09 00:01:10 +02:00
servers_helper . set_update ( self . server_id , False )
2021-08-11 23:33:18 -04:00
if len ( websocket_helper . clients ) > 0 :
# There are clients
self . check_update ( )
websocket_helper . broadcast ( ' notification ' , " Executable update finished for " + self . name )
time . sleep ( 3 )
2021-11-27 18:32:53 -05:00
websocket_helper . broadcast_page ( ' /panel/server_detail ' , ' update_button_status ' , {
2021-08-11 23:33:18 -04:00
' isUpdating ' : self . check_update ( ) ,
' server_id ' : self . server_id ,
' wasRunning ' : wasStarted
} )
2021-11-27 18:32:53 -05:00
websocket_helper . broadcast_page ( ' /panel/dashboard ' , ' send_start_reload ' , {
} )
2021-08-11 23:33:18 -04:00
websocket_helper . broadcast ( ' notification ' , " Executable update finished for " + self . name )
2021-09-09 00:01:10 +02:00
management_helper . add_to_audit_log_raw ( ' Alert ' , ' -1 ' , self . server_id , " Executable update finished for " + self . name , self . settings [ ' server_ip ' ] )
2021-08-11 23:33:18 -04:00
if wasStarted :
self . start_server ( )
2021-08-11 23:49:06 -04:00
elif not downloaded and not self . is_backingup :
time . sleep ( 5 )
2021-09-09 00:01:10 +02:00
servers_helper . set_update ( self . server_id , False )
2021-08-11 23:49:06 -04:00
websocket_helper . broadcast ( ' notification ' ,
" Executable update failed for " + self . name + " . Check log file for details. " )
logger . error ( " Executable download failed. " )
2021-08-11 23:33:18 -04:00
pass