-
Notifications
You must be signed in to change notification settings - Fork 8
/
k8s-mc.sh
executable file
·425 lines (348 loc) · 13.3 KB
/
k8s-mc.sh
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
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
#!/bin/bash
# source this script for some helper functions to control minecraft on k3s
if [[ $0 = $BASH_SOURCE ]]; then
echo "This script must be sourced"
exit 1
elif [[ $ZSH_EVAL_CONTEXT == 'toplevel' ]]; then
echo "This script must be sourced"
exit 1
fi
export THIS_DIR=$(dirname "${BASH_SOURCE[0]}")
export THIS_SHELL=$(basename $(ps -p$$ -ocmd=))
source <(helm completion $THIS_SHELL)
source <(kubectl completion $THIS_SHELL)
###############################################################################
## Helper Functions
###############################################################################
function k8s-mccheckname()
{
# verify that a minecraft server name exists in the cluster
# set pod to the name of the pod it is running in if it is active
# set deploy to the name of its deployment
unset pod deploy
if [ -z "${1}" ]; then
echo 'please supply an mc server name'
return 1
fi
shortname="${1}"
deployname="${1}"-minecraft
deploy=$(kubectl get deploy -n minecraft -l app="${deployname}" -o name)
if [ -z "${deploy}" ]; then
echo "${1} does not exist. Please use one of the following names:"
echo
k8s-mclist
return 1
fi
# if there is a pod associated with the app name then set $pod to its name
pod=$(kubectl get pods -n minecraft -l app=${deployname} --field-selector=status.phase==Running -o name)
}
function k8s-mccheck ()
{
# verify that a minecraft server is running
# checks that the pod is active and checks the health of the server
if k8s-mccheckname "${1}" ; then
if [ ${pod} ]; then
# the pod is running - check the state of minecraft server
output=$(kubectl exec -n minecraft ${pod} -- /health.sh)
echo "${output}"
# a good result contains a motd
if [[ "${output}" == *"motd"* ]]; then
return 0
fi
fi
fi
return 1
}
function k8s-mcvalidyaml()
{
# verify that a yaml file is a valid helm substitution
if [[ $(helm template -f "${1}" minecraft-server-charts/minecraft) == *name-minecraft* ]]
then
return 0
fi
return 1
}
function k8s-mcvalidbackup()
{
# verify that a minecraft backup is valid (or at least has a level.dat file)
k8smcbackupFileName="${1}"
MCBACKUP=${MCBACKUP:-$(read -p "path to backup folder: " IN; echo $IN)}
case "${k8smcbackupFileName}" in
"")
# nothing specified - look for most recent backup
k8smcbackupFileName=$(ls ${MCBACKUP}/*${releasename}.zip | tail -n 1)
echo "using latest backup $k8smcbackupFileName"
;;
/*)
# full path with leading slash - leave it alone
;;
*)
k8smcbackupFileName=${MCBACKUP}/"${k8smcbackupFileName}"
;;
esac
case "${k8smcbackupFileName}" in
*.zip)
if grep -iq "level.dat" < <( unzip -l "${k8smcbackupFileName}" 2>/dev/null); then
return 0
fi
;;
*)
# assume this is a folder
if [[ -n $(find ${MCBACKUP} -name level.dat) ]]; then
return 0
fi
;;
esac
return 1
}
function k8s-mcwait()
{
# block until an mc server has good health
deployname="${1}"-minecraft
downfirst="${2:-false}"
if [[ ${downfirst} == "true" ]] ; then
# first wait for the pod to be inactive
echo "waiting for ${deployname} pod to delete pod"
kubectl wait deployment ${deployname} --for=condition=available=false --timeout 30s -n minecraft
fi
# first wait for the pod to be active
echo "waiting for ${deployname} pod to start"
kubectl wait deployment ${deployname} --for=condition=available --timeout 300s -n minecraft
# get the pod name
pod=$(kubectl get pods -l app=${deployname} -n minecraft -o name)
# if there is no pod then the deployment is at 0 replicas
if [[ ! -z "${pod}" ]]; then
# wait for the mc server to be healthy
echo "waiting for minecraft server ${1}"
kubectl wait ${pod} --for=condition=ready --timeout 160s -n minecraft
fi
}
###############################################################################
## User Functions
###############################################################################
format=custom-columns=\
NAME:metadata.labels.release\
,MODE:spec.template.spec.containers[0].env[22].value\
,VERSION:spec.template.spec.containers[0].env[2].value\
,SERVER:'spec.template.spec.nodeSelector.kubernetes\.io/hostname'\
,RUNNING:status.availableReplicas
function k8s-mclist()
{
# list the minecraft servers deployed in the cluster with useful status info
kubectl -n minecraft get deploy -o $format
echo
}
portsformat=custom-columns=\
NAME:metadata.labels.release,\
PORT:spec.ports[0].nodePort,\
TYPE:spec.ports[0].name
function k8s-mcports()
{
# list all server and rcon ports
kubectl -n minecraft get services -o ${portsformat}\
--sort-by=spec.ports[0].nodePort
}
function k8s-mcstart()
{
if k8s-mccheck "${1}" ; then
echo "${1}" is already running
export was_shutdown=false
else
if [[ ${deploy} ]]; then
# the server is not running, spin it up
was_shutdown=true
echo "starting ${deploy} ..."
kubectl scale -n minecraft ${deploy} --replicas=1
k8s-mcwait ${1}
fi
fi
}
function k8s-mcstop()
{
# stop the minecraft server named $1
if k8s-mccheck ${1}; then
kubectl -n minecraft scale --replicas=0 ${deploy}
else
echo "${1} is not running"
fi
}
function k8s-mcexec()
{
# execute bash in the container for server $1
# for debugging and also can directly edit server.properties
if k8s-mccheck ${1}; then
kubectl -n minecraft exec -it ${deploy} -- bash
else
echo "${1} is not running"
fi
}
function k8s-mclog()
{
# see the server log for server $1
# add -f to attach to the log stream
if k8s-mccheckname "${1}"; then
shift
kubectl -n minecraft logs ${deploy} ${*}
fi
}
function k8s-mcdeploy()
{
# deploy a minecraft server based on the helm chart override values in file $1
# the release name (used by all k8s-mc functions in this script to identify server)
# is taken from the basename of the file
# To use: copy minecraft-helm.yaml to my-new-server-name.yaml and edit it as
# required, then k8s-mcdeploy my-new-server-name.yaml
filename="${1}"
if k8s-mcvalidyaml ${filename}; then
base=$(basename ${filename})
releasename="${base%.*}"
MCPASSWD=${MCPASSWD:-$(read -p "password for rcon: " IN; echo $IN)}
MCEULA=${MCEULA:-$(read -p "agree to minecraft EULA? (type 'true'): " IN; echo $IN)}
helm repo add minecraft-server-charts https://itzg.github.io/minecraft-server-charts/
# default to using itzg repo but allow override through export MCHELMREPO=xxx for testing
MCHELMREPO=${MCHELMREPO:-minecraft-server-charts/minecraft}
helm upgrade --install ${releasename} -f ${filename} --set minecraftServer.eula=${MCEULA},minecraftServer.rcon.password="${MCPASSWD}" ${MCHELMREPO}
k8s-mcwait $releasename true
else
echo "please supply a valid helm values override file for parameter 1 (see example dashboard-admin.yaml)"
fi
}
###############################################################################
## Backup and Restore Functions
###############################################################################
function k8s-mcbackups()
{
# list the backups in the $MCBACKUP folder
# you can refer to these in k8s-mcrestore or k8s-mcbackup
# without using full path
MCBACKUP=${MCBACKUP:-$(read -p "path to backup folder: " IN; echo $IN)}
echo "MCBACKUP folder is ${MCBACKUP}"
ls -R ${MCBACKUP}
echo
du -d 0 -h ${MCBACKUP}
}
function k8s-mcbackup()
{
# backup a minecraft server to a zip file
MCBACKUP=${MCBACKUP:-$(read -p "path to backup folder: " IN; echo $IN)}
k8s-mcstart "${1}"
if [[ "${pod}" ]]; then
zipname=$(date '+%Y-%m-%d_%H:%M:%S')-${shortname}.zip
kubectl exec -n minecraft ${pod} -- rcon-cli save-off
kubectl exec -n minecraft ${pod} -- rcon-cli save-all
sleep 5
tmp_dir=$(mktemp -d)
echo "copying data from ${deploy} ..."
# note #pod/ removes the pod/ prefix from pod name
# this uses rsync instead of the less reliable 'kubectl cp'
if kubectl cp -n minecraft ${pod#pod/}:/data ${tmp_dir}; then
echo "zipping data ..."
zip -qr ${MCBACKUP}/${zipname} ${tmp_dir}
rm -r ${tmp_dir}
if [[ "${was_shutdown}" == "true" ]]; then
echo "stopping ${deploy} ..."
kubectl scale -n minecraft ${deploy} --replicas=0
fi
echo "backup of ${deploy} done."
else
echo "ERROR: rsync returned an error"
fi
echo "restoring auto save ..."
kubectl -n minecraft exec ${pod} -- rcon-cli save-on
fi
}
function k8s-mcbackupall()
{
releases=$( kubectl -n minecraft get deploy -o jsonpath="{.items[*]['metadata.labels.release']}")
for i in $releases ; do
k8s-mcbackup $i
done
}
function k8s-mcrestore()
{
# restore a backup into an exisitng server deployment
# IMPORTANT - the backup folder must be mounted read only in the
# server pods, see extraVolumes in minecraft-helm.yaml
yaml_filename="${1}"
backup_filename="${2}"
echo "attempting to restore world description ${yaml_filename} with backup ${backup_filename}"
base=$(basename ${yaml_filename})
releasename="${base%.*}"
if ! k8s-mcvalidyaml ${yaml_filename}; then
echo "please supply a valid helm values override file for parameter 1 (see example dashboard-admin.yaml)"
return 1
fi
read -p "WARNING this will overwrite the current world. OK? " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
if ! k8s-mcvalidbackup ${backup_filename}; then
echo "please name a valid zipped minecraft save from ${MCBACKUP} as a parameter 2"
return 1
fi
restore_settings="--set extraEnv.FORCE_WORLD_COPY=true,minecraftServer.downloadWorldUrl=${k8smcbackupFileName},minecraftServer.eula=true"
echo $restore_settings
helm repo add minecraft-server-charts https://itzg.github.io/minecraft-server-charts/
# default to using itzg repo but allow override through export MCHELMREPO=xxx for testing
MCHELMREPO=${MCHELMREPO:-minecraft-server-charts/minecraft}
helm upgrade ${releasename} -f ${yaml_filename} ${restore_settings} ${MCHELMREPO}
# reset the FORCE_WORLD_COPY so future changes will be preserved on restart
k8s-mcwait ${releasename} true
kubectl -n minecraft set env deployments.apps/${releasename}-minecraft FORCE_WORLD_COPY=false
# the env setting creates a new pod
k8s-mcwait ${releasename} true
fi
}
###############################################################################
## Experimental
###############################################################################
function k8s-mctry()
{
# try out a world backup in the 'tmp' deployment
# overwrites the previous tmp deployment with the world defined in a zip or folder provided in $1
if ! k8s-mcvalidbackup "${1}"; then
echo "please supply a valid zipped minecraft save as a parameter 1"
return 1
fi
helm upgrade --install tmp -f ${THIS_DIR}/giles-servers/tmp.yaml --set minecraftServer.eula=true,minecraftServer.downloadWorldUrl=${k8smcbackupFileName} minecraft-server-charts/minecraft
k8s-mcwait tmp
}
###############################################################################
## Help
###############################################################################
function k8s-mchelp()
{
echo """
This script adds the following functions to bash. It assumes that your
servers have been deployed using:
https://artifacthub.io/packages/helm/minecraft-server-charts/minecraft
or using the k8s-mcdeploy function below.
In all cases below <server name> is the helm chart release name.
k8s-mclist
list all minecraft servers deployed to the cluster
k8s-mcports
details of the ports exposed by servers and rcon
k8s-mcstart <server name>
start the server (set replicas to 1)
k8s-mcstop <server name>
stop the server (set replicas to 0)
k8s-mcexec <server name>
execute bash in the server's container
k8s-mclog <server name> [-p] [-f]
get logs for the server
-p = log for previous instance after a restart of the pod
-f = attach to the log and monitor it
k8s-mcdeploy <my_server_def.yaml>
deploys a server to the cluster with release name my_server_def and
with values overrides from my_server_def.yaml
recommended: copy and edit minecraft-helm.yaml for most common options,
see in file comments for details.
k8s-mcbackups
List the backups in the folder ${MCBACKUP:-[MCBACKUP not yet specified]}
k8s-mcbackup <server name>
zips up the server data folder to a dated zip file and places it
in the folder ${MCBACKUP:-[MCBACKUP not yet specified]} (prompts for MCBACKUP if not set)
k8s-mcrestore <my_server_def.yaml> <backup file name>
restore the world from backup for a server, overwriting its
current world
"""
}