-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMC_Management.py
More file actions
289 lines (265 loc) · 12.4 KB
/
MC_Management.py
File metadata and controls
289 lines (265 loc) · 12.4 KB
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
import socket
import ssl
import asyncio
import asyncssh
import time
import os
import ResourcePath
import re
import zipfile
import shutil
from datetime import date, datetime
import glob
import base64
import MC_Settings
local_dir = os.getcwd()
# Dummy standard variables
remote_dir = ''
world_name = ''
server_jar_compiler_command = ''
server_jar_dir = ''
server_jar = ''
_target_host = ''
_proxy_host = ''
_proxy_port = 443
_client_cert = ''
_client_key = ''
_known_hosts = ''
_private_key = ''
_max_backups = 4
# Read settings from settings file
_Variables = MC_Settings.ReadSettings()
assert _Variables is not None, 'Missing settings in settings.json'
locals().update(_Variables)
_known_hosts = ResourcePath.resource_path(_known_hosts)
_private_key = ResourcePath.resource_path(_private_key)
Progress_Bar = None
Progress_Text = None
CurrentWorldSize = 0
if not os.path.isdir(f'{local_dir}/files'):
os.mkdir(f'{local_dir}/files')
if not os.path.isdir(f'{local_dir}/files/plugins'):
os.mkdir(f'{local_dir}/files/plugins')
class SSL_Socket:
async def create_connection(self, protocol_factory, host, port):
loop = asyncio.get_event_loop()
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) # Verify Server cert
context.load_cert_chain(certfile=_client_cert, keyfile=_client_key) # Load Client cert
context.set_alpn_protocols(['ssh/2.0'])
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((_proxy_host, _proxy_port))
return (await loop.create_connection(protocol_factory, sock=s, ssl=context, server_hostname=host))
if _proxy_host == '':
_tunnel = ()
else:
_tunnel = SSL_Socket()
_client_cert = ResourcePath.resource_path(_client_cert)
_client_key = ResourcePath.resource_path(_client_key)
def ProgressBarHandler(FilePathA, FilePathB, CurrentSize, TotalSize):
if Progress_Bar is None:
return
Progress_Bar['value'] = CurrentSize / TotalSize * 100
Progress_Bar.update()
def BackupProgressBarHandler(FilePathA, FilePathB, CurrentSize, TotalSize):
global CurrentWorldSize
if Progress_Bar is None or Progress_Text is None:
return
else:
if TotalSize != 0:
if CurrentSize / TotalSize == 1:
CurrentWorldSize += TotalSize
Progress_Bar['value'] = CurrentWorldSize / TotalWorldSize * 100
Progress_Bar.update()
Progress_Text['text'] = f'{CurrentWorldSize / 1048576:.2f} [MB] out of {TotalWorldSize / 1048576:.2f} [MB]'
Progress_Text.update()
def zipdir(path, ziph):
"""ziph is zipfile handle"""
for root, dirs, files in os.walk(path):
for file in files:
ziph.write(os.path.join(root, file), os.path.relpath(os.path.join(root, file), os.path.join(path, '..')))
async def StopStartServer(StopStart, conn):
"""
Stops or starts the Minecraft server.
StopStart='Stop' or 'Start',
conn=(asyncssh connection class)
"""
if StopStart == 'Start':
# Start server
await conn.run(f'screen -dmS Minecraft bash -c \"{remote_dir}/start.sh\"')
if Progress_Text is not None:
Progress_Text['text'] = 'Starting Server...'
Progress_Text.update()
if Progress_Bar is not None:
Progress_Bar['value'] = 0
Progress_Bar.update()
time.sleep(5)
elif StopStart == 'Stop':
# 1 minute warning
await conn.run('screen -S Minecraft -X stuff \'say \u00A7c Restart in 1 minute...\u00A7r\r\'')
if Progress_Text is not None:
Progress_Text['text'] = 'Stopping Server in 1 minute...'
Progress_Text.update()
time.sleep(30)
# 30 seconds warning
await conn.run('screen -S Minecraft -X stuff \'say \u00A7c Restart in 30 seconds...\u00A7r\r\'')
if Progress_Text is not None:
Progress_Text['text'] = 'Stopping Server in 30 seconds...'
Progress_Text.update()
time.sleep(20)
# 10 seconds warning
await conn.run('screen -S Minecraft -X stuff \'say \u00A7c Restart in 10 seconds...\u00A7r\r\'')
if Progress_Text is not None:
Progress_Text['text'] = 'Stopping Server in 10 seconds...'
Progress_Text.update()
time.sleep(10)
# Shutdown server
await conn.run('screen -S Minecraft -X stuff \'stop\r\'')
if Progress_Text is not None:
Progress_Text['text'] = 'Stopping Server...'
Progress_Text.update()
# Waiting for max 60 seconds for shutdown
for _ in range(60):
if not 'Minecraft' in (await conn.run('screen -list')).stdout:
break
time.sleep(1)
async def ASYNC_MC_Management(Action, Username, Password, File=None):
try:
async with asyncssh.connect(_target_host, tunnel=_tunnel, username=base64.b64decode(Username).decode('utf-8'), password=base64.b64decode(Password).decode('utf-8'), client_keys=_private_key, known_hosts=_known_hosts) as conn:
async with conn.start_sftp_client() as sftp:
if Action == 'Update':
await StopStartServer('Stop', conn)
# Upload new server.jar
if Progress_Text is not None:
Progress_Text['text'] = 'Updating Server...'
Progress_Text.update()
await sftp.put(f'{local_dir}/files/{server_jar}.jar', remote_dir, progress_handler=ProgressBarHandler)
await StopStartServer('Start', conn)
conn.close()
elif Action == 'UpdatePlugins':
await StopStartServer('Stop', conn)
if Progress_Text is not None:
Progress_Text['text'] = 'Updating Plugins...'
Progress_Text.update()
# Removing old plugin jars
Files = await sftp.listdir(f'{remote_dir}/plugins')
for File in [i for i in Files if i.endswith('.jar')]:
await sftp.remove(f'{remote_dir}/plugins/{File}')
# Upload new plugin jars
await sftp.put(f'{local_dir}/files/plugins', remote_dir, recurse=True, progress_handler=ProgressBarHandler)
await StopStartServer('Start', conn)
conn.close()
elif Action == 'Backup':
global TotalWorldSize
global CurrentWorldSize
CurrentWorldSize = 0
# Download world folders
TotalWorldSize = sum([int(i) for i in re.split('\t|\n', (await conn.run(f'du {remote_dir}/\"{world_name}\"* -bs')).stdout)[:-1:2]])
for directory in (world_name, f'{world_name}_nether', f'{world_name}_the_end'):
await sftp.get(f'{remote_dir}/{directory}', local_dir, recurse=True, preserve=True, progress_handler=BackupProgressBarHandler)
conn.close()
# Say program is zipping files
if Progress_Text is not None:
Progress_Text['text'] = 'Compressing Backup into Zip...'
Progress_Text.update()
# Generate Zip name
if not os.path.isdir(f'{local_dir}/files/backups'):
os.mkdir(f'{local_dir}/files/backups')
Name = f"MC_{date.today().strftime('%d-%m-%Y')}_"
Glob = glob.glob(f'{local_dir}/files/backups/{Name}*.zip')
if len(Glob) != 0:
ZipInt = max([int(re.split('_|\.', i)[-2]) for i in Glob]) + 1
else:
ZipInt = 0
# Zip world folders
zipf = zipfile.ZipFile(f'{local_dir}/files/backups/{Name}{ZipInt}.zip', 'w', zipfile.ZIP_DEFLATED)
for directory in (f'{local_dir}/{world_name}', f'{local_dir}/{world_name}_nether', f'{local_dir}/{world_name}_the_end'):
zipdir(directory, zipf)
zipf.close()
# Delete temporary world folders, backup zip remains
for directory in (f'{local_dir}/{world_name}', f'{local_dir}/{world_name}_nether', f'{local_dir}/{world_name}_the_end'):
shutil.rmtree(directory)
# Remove backups older than the most recent 4
Backups = glob.glob(f'{local_dir}/files/backups/MC_*_*.zip')
Backups.sort(key=lambda date: datetime.strptime(date.split('_')[-2], "%d-%m-%Y"))
if len(Backups) > _max_backups:
for i in Backups[:-_max_backups]:
os.remove(i)
# Set progressbar back to 0%
if Progress_Bar is not None:
Progress_Bar['value'] = 0
Progress_Bar.update()
elif Action == 'DownloadFile':
if File is not None:
if not os.path.isdir(f'{local_dir}/files'):
os.mkdir(f'{local_dir}/files')
# Download File
await sftp.get(f'{remote_dir}/{File}', f'{local_dir}/files', preserve=True, progress_handler=ProgressBarHandler)
conn.close()
# Set progressbar back to 0%
if Progress_Bar is not None:
Progress_Bar['value'] = 0
Progress_Bar.update()
elif Action == 'UploadFile':
if File is not None:
# Upload File
File = File.split('/')
Filename = File[-1]
Filepath = '/'.join(File[:-1])
await sftp.put(f'{local_dir}/files/{Filename}', f'{remote_dir}/{Filepath}', progress_handler=ProgressBarHandler)
conn.close()
# Set progressbar back to 0%
if Progress_Bar is not None:
Progress_Bar['value'] = 0
Progress_Bar.update()
elif Action == 'RestartServer':
await StopStartServer('Stop', conn)
await StopStartServer('Start', conn)
conn.close()
elif Action == 'Login':
conn.close()
SFTP_Succes = True
except:
SFTP_Succes = False
finally:
Username, Password = None, None
return SFTP_Succes
def MC_Management(Action, Username, Password, File=None, Tries=4, ProgressBar=None, ProgressText=None):
"""
Manages the Minecraft server.
Action='Login', 'Update', 'UpdatePlugins', 'Backup', 'DownloadFile', 'UploadFile' or 'RestartServer',
Username=(str),
Password=(str),
Tries=(int), default = 4,
ProgressBar=None (ttk.Progressbar)
"""
if ProgressBar is not None:
global Progress_Bar
Progress_Bar = ProgressBar
if ProgressText is not None:
global Progress_Text
Progress_Text = ProgressText
tried = 0
while tried < Tries:
SFTP_Succes = asyncio.get_event_loop().run_until_complete(ASYNC_MC_Management(Action, Username, Password, File))
if SFTP_Succes:
break
tried += 1
time.sleep(0.5)
return SFTP_Succes
def CompileServerJar(Progress_Text=None):
if (server_jar_compiler_command or server_jar_dir) == '' and Progress_Text is not None:
Progress_Text['text'] = 'No Compile Command'
Progress_Text.update()
Progress_Text.after(1000, lambda: Progress_Text.config(text='', fg='white'))
Progress_Text.update()
return
if Progress_Text is not None:
Progress_Text['text'] = 'Compiling server jar...'
Progress_Text.update()
os.system(server_jar_compiler_command)
shutil.copyfile(f'{server_jar_dir}/{server_jar}.jar', f'{local_dir}/files/{server_jar}.jar')
if Progress_Text is not None:
Progress_Text.config(text='Done', fg='green')
Progress_Text.update()
Progress_Text.after(1000, lambda: Progress_Text.config(text='', fg='white'))
Progress_Text.update()