This repository has been archived by the owner on Sep 19, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 13
/
crafty.py
336 lines (265 loc) · 11.5 KB
/
crafty.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
import os
import sys
import time
import json
import logging
import argparse
import threading
import logging.config
import datetime
import subprocess
def is_venv():
return hasattr(sys, 'real_prefix') or sys.base_prefix != sys.prefix
try:
import yaml
import secrets
import schedule
from termcolor import colored
from app.classes.console import console
except Exception as e:
print("/" * 75 + "\n")
print("\t\t\tWTF!!! \n \t\t(What a terrible failure) \n")
print("/" * 75 + "\n")
print(" Crafty is unable to find required modules")
print(" Some common causes of this issue include:")
print("\t * Modules didn't install: Did pip install -r requirements.txt run correctly?")
print("\t * You haven't activated your virtual environment, maybe try activating it?")
print("\n Need Help? We are here to help! - https://discord.gg/XR5x3ZM \n")
if is_venv:
pipinstall = str(input("A virtual environment has been detected, would you like to try reinstalling the modules? [yes/no]: "))
pipinstall = pipinstall.lower()
print(pipinstall)
if pipinstall == str("yes"):
file = open("requirements.txt" , "r")
for line in file:
req = line.split("/n")
command_list = [sys.executable, "-m", "pip", "install", req]
with subprocess.Popen(command_list, stdout=subprocess.PIPE) as proc:
print(proc.stdout.read())
print("Please Run Crafty Again!")
sys.exit(1)
else:
print("Not reinstalling modules, join the discord for further assistance!")
sys.exit(1)
else:
print("It has been detected that you are not in a virtual environment, maybe try activating it?")
print("If you are not sure how to do this, ask for help in our discord!")
pipinstall = str(input("If you have chosen to not use a virtual environment, would you like to try reinstalling the modules? [yes/no]: "))
pipinstall = pipinstall.lower()
print(pipinstall)
if pipinstall == str("yes"):
file = open("requirements.txt" , "r")
for line in file:
req = line.split("/n")
command_list = [sys.executable, "-m", "pip", "install", req]
with subprocess.Popen(command_list, stdout=subprocess.PIPE) as proc:
print(proc.stdout.read())
print("Please Run Crafty Again!")
sys.exit(1)
else:
print("Not reinstalling modules, join the discord for further assistance!")
sys.exit(1)
def setup_logging(debug=False):
logging_config_file = os.path.join(os.path.curdir,
'app',
'config',
'logging.json'
)
if os.path.exists(logging_config_file):
# open our logging config file
with open(logging_config_file, 'rt') as f:
logging_config = json.load(f)
if debug:
logging_config['loggers']['']['level'] = 'DEBUG'
logging.config.dictConfig(logging_config)
else:
logging.basicConfig(level=logging.DEBUG)
logging.warning("Unable to read logging config from {}".format(logging_config_file))
def do_intro():
version_data = helper.get_version()
version = "{}.{}.{}".format(version_data['major'], version_data['minor'], version_data['sub'])
intro = """
{lines}
#\t\tWelcome to Crafty Controller - v.{version}\t\t #
{lines}
# \tServer Manager / Web Portal for your Minecraft server\t #
# \t\tHomepage: www.craftycontrol.com\t\t\t #
{lines}
""".format(lines="/" * 75, version=version)
print(intro)
def show_help():
console.help("-h: shows this message")
console.help("-k: stops all crafty processes")
console.help("--no-console: don't start the console")
sys.exit(0)
def start_scheduler():
while True:
schedule.run_pending()
time.sleep(1)
def send_kill_command():
# load the remote commands obj
from app.classes.remote_coms import remote_commands
from app.classes.http import tornado_srv
from app.classes.models import peewee, Remote
# start the remote commands watcher thread
remote_coms = remote_commands(tornado_srv)
remote_coms_thread = threading.Thread(target=remote_coms.start_watcher, daemon=True, name="Remote_Coms")
remote_coms_thread.start()
Remote.insert({
Remote.command: 'exit_crafty',
Remote.server_id: 1,
Remote.command_source: 'local'
}).execute()
time.sleep(2)
sys.exit(0)
if __name__ == '__main__':
""" Our Main Starter """
log_file = os.path.join(os.path.curdir, 'logs', 'crafty.log')
# ensure the log directory is there
try:
os.makedirs(os.path.join(os.path.curdir, 'logs'))
except Exception as e:
pass
# ensure the log file is there
try:
open(log_file, 'a').close()
except Exception as e:
console.critical("Unable to open log file!")
sys.exit(1)
daemon_mode = False
parser = argparse.ArgumentParser("Crafty Web - A Minecraft Server GUI")
parser.add_argument('-k', '--kill-all',
action='store_true',
help="Find and terminate all running Crafty instances on the host system."
)
parser.add_argument('-v', '--verbose',
action='store_true',
help="Sets Crafty's logging level to debug."
)
parser.add_argument('-d', '--daemonize',
action='store_true',
help="Prevent exit of crafty.py and disable console."
)
parser.add_argument('-c', '--config',
help="Specify a config file to tell Crafty where to store it's database, version, etc."
)
args = parser.parse_args()
# sets up our logger
setup_logging(debug=args.verbose)
# setting up the logger object
logger = logging.getLogger(__name__)
# now that logging is setup - let's import the rest of the things we need to run
from app.classes.helpers import helper
if not is_venv:
logger.critical("Not in a virtual environment! Exiting")
console.critical("Not in a virtual environment! Exiting")
sys.exit(1)
logger.info("***** Crafty Launched: Verbose {} *****".format(args.verbose))
# announce the program
do_intro()
admin_pass = None
# load config file and reprogram default values
if args.config:
config_path = os.path.join(os.curdir, args.config)
logger.info("Loading config from file {}".format(config_path))
try:
with open(config_path) as f:
cfg = yaml.safe_load(f)
f.close()
except:
logger.exception("Specified config has invalid syntax/cannot be read. Traceback:")
else:
logger.info("Setting config and db paths")
helper.redefine_paths(cfg['config_dir'], cfg['db_dir'])
if not args.daemonize:
daemon_mode = cfg['daemon_mode']
else:
logger.warning("No config specified")
# prioritize command line flags
if args.kill_all:
send_kill_command()
if args.daemonize:
daemon_mode = args.daemonize
# do we have access to write to our folder?
if not helper.check_writeable(os.curdir):
logger.info("***** Crafty Stopped ***** \n")
sys.exit(1)
# is this a fresh install?
fresh_install = helper.is_fresh_install()
# doing a more focused import here, because * imports can be a little crazy.
# also import after config and cmd args
from app.classes.models import peewee, Users, MC_settings, Webserver, Schedules, History, Crafty_settings, Backups, Roles, Remote, Ftp_Srv
# creates the database tables / sqlite database file
peewee.create_tables()
if fresh_install:
# save a file in app/config/new_install so we know this is a new install
helper.make_new_install_file()
admin_pass = helper.random_string_generator()
admin_token = secrets.token_urlsafe(32)
peewee.default_settings(admin_pass, admin_token)
else:
peewee.do_database_migrations()
# only import / new database tables are created do we load the rest of the things!
from app.classes.ftp import ftp_svr_object
# from app.classes.minecraft_server import mc_server
from app.classes.http import tornado_srv
from app.classes.craftycmd import MainPrompt
from app.classes.minecraft_server import mc_server
from app.classes.remote_coms import remote_commands
from app.classes.multiserv import multi
logger.info("Starting Scheduler Daemon")
console.info("Starting Scheduler Daemon")
scheduler = threading.Thread(name='Scheduler', target=start_scheduler, daemon=True)
scheduler.start()
# startup Tornado if we aren't killing all craftys
tornado_srv.start_web_server(True)
websettings = Webserver.get()
port_number = websettings.port_number
console.info("Starting Tornado HTTPS Server https://{}:{}".format(helper.get_local_ip(), port_number))
if fresh_install:
console.info("Please connect to https://{}:{} to continue the install:".format(
helper.get_local_ip(), port_number))
console.info("Your Username is: Admin")
console.info("Your Password is: {}".format(admin_pass))
console.info("Your Admin token is: {}".format(admin_token))
''' moving this to 3.2
if not daemon_mode:
currentDT = datetime.datetime.now().strftime("%Y-%m-%d %I:%M:%S %p")
message = "Would you like us to setup a minecraft server for you? [y/n]: "
setupmcsrv = str(input(colored("[+] Crafty: {} - INFO:\t{}".format(currentDT, message), 'white')))
setupmcsrv = setupmcsrv.lower()
if setupmcsrv == 'y':
if os.name == 'nt':
os.system("python app\minecraft\mcservcreate.py")
else:
os.system("python app/minecraft/mcservcreate.py")
else:
console.warning("Not prompting for first server due to daemonize mode")
'''
# for each server that is defined, we set them up in the multi class, so we have them ready for later.
multi.init_all_servers()
# do one now...
multi.do_host_status()
# do our scheduling
multi.reload_scheduling()
# schedule our stats
schedule.every(10).seconds.do(multi.do_stats_for_servers).tag('server_stats')
schedule.every(10).seconds.do(multi.do_host_status).tag('server_stats')
multi.reload_user_schedules()
# start the remote commands watcher thread
remote_coms = remote_commands(tornado_srv)
remote_coms_thread = threading.Thread(target=remote_coms.start_watcher, daemon=True, name="Remote_Coms")
remote_coms_thread.start()
console.info("Crafty Startup Procedure Complete")
console.help("Type 'stop' or 'exit' to shutdown Crafty")
if not daemon_mode:
Crafty = MainPrompt(mc_server)
Crafty.cmdloop()
else:
logger.info("Not starting crafty console due to daemonize mode")
if daemon_mode:
# Freeze the program in a loop
logger.info("Freezing program due to daemonize mode")
while True:
# fixes a 100% CPU usage issue in daemonized mode - thanks ImMeta for finding this.
time.sleep(1)