-
Notifications
You must be signed in to change notification settings - Fork 0
/
spiffymanagement.py
325 lines (290 loc) · 11.4 KB
/
spiffymanagement.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
from subprocess import run
import os
from time import sleep, time
from datetime import datetime
import tarfile
import logging
from cogs.minecraftcogs.utils.mcservutils import isUp, termProc, buildCountdownSteps
log = logging.getLogger('spiffymanagement')
def screenCmd(server, *cmds):
for cmd in cmds:
run(['screen', '-S', server, '-X', 'stuff', f'{cmd}\r'])
def start(cfg, server):
"""Starts a server, if it is not running already."""
if server not in cfg['servers']:
log.warning(f'{server} has been misspelled or not configured!')
return False
if isUp(server):
log.info(f'{server} appears to be running already!')
else:
os.chdir(cfg['serverspath'] + f'/{server}')
log.info(f'Starting {server}')
run(['screen', '-h', '5000', '-dmS', server,
*(cfg['servers'][server]['invocation']).split(), 'nogui'])
sleep(5)
if isUp(server):
log.info(f'{server} is now running!')
return True
else:
log.warning(f'{server} does not appear to have started!')
return False
def stop(cfg, server, countdown=None):
"""Stops a server immediately, if it is currently running."""
if server not in cfg['servers']:
log.warning(f'{server} has been misspelled or not configured!')
return False
if isUp(server):
if countdown:
countdownSteps = ["20m", "15m", "10m", "5m", "3m",
"2m", "1m", "30s", "10s", "5s"]
if countdown not in countdownSteps:
log.error(f'{countdown} is an undefined step, aborting!')
availableSteps1 = ', '.join(countdownSteps[:5])
availableSteps2 = ', '.join(countdownSteps[5:])
log.info('> Available countdown steps are:\n'
f'> {availableSteps1},\n'
f'> {availableSteps2}')
return False
log.info(f'Stopping {server} with {countdown}-countdown.')
indx = countdownSteps.index(countdown)
cntd = countdownSteps[indx:]
steps = buildCountdownSteps(cntd)
for step in steps:
screenCmd(
server,
'title @a times 20 40 20',
f'title @a subtitle {{\"text\":\"in {step[0]} {step[2]}!\",\"italic\":true}}',
'title @a title {\"text\":\"Stopping\", \"bold\":true}',
f'tellraw @a {{\"text\":\"[Stopping in {step[0]} {step[2]}!]\",\"color\":\"green\"}}'
)
sleep(step[1])
log.info(f'Stopping {server} now...')
screenCmd(
server,
'title @a times 20 40 20',
'title @a title {\"text\":\"STOPPING SERVER NOW\", \"bold\":true, \"italic\":true}',
f'tellraw @a {{\"text\":\"[Stopping now!]\",\"color\":\"green\"}}'
'save-all',
)
sleep(5)
screenCmd(
server,
'stop'
)
waiting = 6
while isUp(server) and waiting > 0:
waiting -= 1
sleep(20)
if isUp(server):
log.warning(f'{server} does not appear to have stopped!')
log.warning(f'Terminating {server} process!')
terminated = terminate(cfg, server)
if not terminated:
return
log.info(f'{server} was stopped.')
return True
else:
log.info(f'{server} already is not running.')
return True
def restart(cfg, server, countdown=None):
"""Restarts a server with a countdown, if it is currently running."""
if server not in cfg['servers']:
log.warning(f'{server} has been misspelled or not configured!')
return False
if isUp(server):
countdownSteps = ["20m", "15m", "10m", "5m", "3m",
"2m", "1m", "30s", "10s", "5s"]
if countdown:
if countdown not in countdownSteps:
log.error(f'{countdown} is an undefined step, aborting!')
availableSteps1 = ', '.join(countdownSteps[:5])
availableSteps2 = ', '.join(countdownSteps[5:])
log.info('> Available countdown steps are:\n'
f'> {availableSteps1},\n'
f'> {availableSteps2}')
return False
log.info(f'Restarting {server} with {countdown}-countdown.')
indx = countdownSteps.index(countdown)
cntd = countdownSteps[indx:]
else:
log.info(f'Restarting {server} with default 10min countdown.')
cntd = countdownSteps[2:]
steps = buildCountdownSteps(cntd)
for step in steps:
screenCmd(
server,
'title @a times 20 40 20',
f'title @a subtitle {{\"text\":\"in {step[0]} {step[2]}!\",\"italic\":true}}',
'title @a title {\"text\":\"Restarting\", \"bold\":true}',
f'tellraw @a {{\"text\":\"[Restarting in {step[0]} {step[2]}!]\",\"color\":\"green\"}}'
)
sleep(step[1])
screenCmd(
server,
'save-all'
)
sleep(5)
screenCmd(
server,
'stop'
)
waiting = 6
while isUp(server) and waiting > 0:
waiting -= 1
sleep(20)
if isUp(server):
log.warning(f'Restart failed, {server} appears not to have stopped!')
log.warning(f'Terminating {server} process!')
terminated = terminate(cfg, server)
if not terminated:
return
log.info('Restart in progress...')
log.info(f'Starting {server}')
os.chdir(cfg['serverspath'] + f'/{server}')
run(['screen', '-h', '5000', '-dmS', server,
*(cfg['servers'][server]['invocation']).split(), 'nogui'])
sleep(5)
if isUp(server):
log.info(f'Restart successful, {server} is now running!')
return True
else:
log.warning(f'Restart failed, {server} does not appear to have started!')
return False
else:
log.warning(f'Restart cancelled, {server} is offline!')
return False
def terminate(cfg, server):
"""Terminates the process corresponding to the given servername."""
if server not in cfg['servers']:
log.warning(f'{server} has been misspelled or not configured!')
return
result = termProc(server)
if result:
log.info(f'{server} has been terminated!')
else:
log.warning(f'{server} termination failed! Oh dear...')
return result
def status(cfg, server):
"""Checks if a server's process is running."""
if server not in cfg['servers']:
log.warning(f'{server} has been misspelled or not configured!')
return False
if isUp(server):
log.info(f'{server} is running.')
return True
else:
log.info(f'{server} is not running.')
return False
def backup(cfg, server, world=None):
"""Backup a server's world directory.
Also deletes backups older than a configured age.
"""
bpath = cfg['backupspath']
if server not in cfg['servers']:
log.warning(f'{server} has been misspelled or not configured!')
return
if world is None:
try:
world = cfg['servers'][server]['worldname']
except KeyError:
log.warning(f'{server} has no main world directory specified!')
return
else:
try:
moreworlds = cfg['servers'][server]['moreworlds']
except KeyError:
moreworlds = []
log.info(f'Starting backup for {server}...')
if isUp(server):
log.info(f'{server} is running, announcing backup and toggling save!')
screenCmd(
server,
'Starting Backup!',
'save-off',
'save-all'
)
sleep(10)
sbpath = f'{bpath}/{server}'
try:
os.makedirs(sbpath, exist_ok=True)
except Exception as e:
log.error(e + '\nBackups aborted!')
return False
else:
log.info('Created missing directories! (if they were missing)')
log.info('Deleting outdated backups...')
now = time()
with os.scandir(sbpath) as d:
for entry in d:
if not entry.name.startswith('.') and entry.is_file():
stats = entry.stat()
if stats.st_mtime < now - (int(cfg['oldTimer']) * 60):
try:
os.remove(entry.path)
except OSError as e:
log.error(e)
else:
log.info(f'Deleted {entry.path} for being too old!')
log.info('Creating backup(s)...')
for w in ([world] + moreworlds):
log.info(f'Backing up {w}...')
bname = datetime.now().strftime('%Y.%m.%d-%H-%M-%S') + f'-{server}-{w}.tar.gz'
os.chdir(sbpath)
serverpath = cfg['serverspath']
with tarfile.open(bname, 'w:gz') as tf:
tf.add(f'{serverpath}/{server}/{w}', f'{w}')
log.info(f'{w} backed up!')
log.info('Backup(s) created!')
if isUp(server):
log.info(f'{server} is running, re-enabling save!')
screenCmd(
server,
'save-on',
'say Backup complete!'
)
def questbackup(cfg, server):
"""Silly solution to a silly problem."""
if server not in cfg['servers']:
log.warning(f'{server} has been misspelled or not configured!')
elif 'worldname' not in cfg['servers'][server]:
log.warning(f'{server} has no world directory specified!')
elif 'questing' not in cfg['servers'][server]:
log.warning(f'{server} has is not setup for questing backup!')
else:
bpath = cfg['backupspath']
world = cfg['servers'][server]['worldname']
quests = cfg['servers'][server]['questing']
log.info(f'Starting backup for {server}\'s quests...')
if isUp(server):
log.info(f'{server} is running, don\'t care, just want {quests.upper()}!')
sbpath = f'{bpath}/{server}/questing/{quests}'
try:
os.makedirs(sbpath, exist_ok=True)
except Exception as e:
log.error(e + '\nBackup aborted, DANGER! might loose quests!')
return False
else:
log.info('Created missing directories! (if they were missing)')
log.info('Deleting old quest backups...')
now = time()
with os.scandir(sbpath) as d:
for entry in d:
if not entry.name.startswith('.') and entry.is_file():
stats = entry.stat()
if stats.st_mtime < now - (10080 * 60):
try:
os.remove(entry.path)
except OSError as e:
log.error(e)
else:
log.info(f'Deleted {entry.path} for being too old!')
log.info('Creating quest backup...')
bname = datetime.now().strftime('%Y.%m.%d-%H-%M-%S') + f'-{server}-{world}-{quests.replace("/", "_")}.tar.gz'
os.chdir(sbpath)
serverpath = cfg['serverspath']
with tarfile.open(bname, 'w:gz') as tf:
tf.add(f'{serverpath}/{server}/{world}/{quests}', quests)
log.info('Quest backup created!')
if isUp(server):
log.info(f'{server} is running, STILL DON\'T CARE!')
log.info('DONE!')