-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpz.bashrc
370 lines (307 loc) · 9.39 KB
/
pz.bashrc
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
declare STEAMCMD_HOME=/opt/steamcmd
declare STEAMCMD_BIN="${STEAMCMD_HOME}/steamcmd.sh"
declare STEAM_GAMES_DIR=/opt/steamgames
declare PZ_HOME="${STEAM_GAMES_DIR}/ProjectZomboid"
declare STEAM_DATA_DIR=/opt/steamdata
declare PZ_DATA="${STEAM_DATA_DIR}/Zomboid"
declare PZ_DATABASE_DIR="${PZ_DATA}/db"
declare STEAM_DATA_BACKUP_DIR="${STEAM_DATA_DIR}/Backups"
declare PZ_SAVE_SLEEP=10s
declare STEAMCMD_USER=steam
declare PZ_USER=pz_user
declare PZ_RUN_STEAMCMD_UPDATE="${PZ_HOME}/.run_steamcmd_update"
declare PZ_NODE_PATH=/opt/pz_node_modules
declare CHAR_DOUBLEQUOTE='"'
function rcon_cmd() {
rcon -c /etc/rcon/rcon.yaml "$@"
}
function pz_save() {
rcon_cmd 'save'
}
function pz_quit() {
rcon_cmd 'quit'
}
function pz_reloadoptions() {
rcon_cmd 'reloadoptions'
}
function pz_changeoption() {
local optionName="${1:-}"
local optionValue="${2:-}"
if [[ -z "${optionName}" ]]; then
echo "WARNING: no option passed"
return 1
fi
if [[ -z "${optionValue}" ]]; then
echo "WARNING: no option value passed"
return 1
fi
rcon_cmd "changeoption \"${optionName}\" \"${optionValue}\""
}
function pz_start() {
sudo systemctl start pz-server.service
}
function pz_stop() {
pz_quit
sleep "${PZ_SAVE_SLEEP}"
sudo systemctl stop pz-server.service
}
function pz_restart() {
pz_quit
sleep "${PZ_SAVE_SLEEP}"
sudo systemctl restart pz-server.service
}
function pz_broad() {
local message="$@"
if [[ -z "${message}" ]]; then
echo "WARNING: no message passed"
return 1
fi
# project zomboid server only seems to allow receiving command arguments with double quotes
rcon_cmd "servermsg \"${message}\""
}
function pz_players() {
rcon_cmd 'players'
}
function pz_adduser() {
local userName="${1:-}"
local password="${2:-}"
if [[ -z "${userName}" ]]; then
echo "WARNING: no username passed"
return 1
fi
if [[ -z "${password}" ]]; then
password="$(makepasswd --chars=8)"
fi
rcon_cmd "adduser \"${userName}\" \"${password}\""
echo -e "USERNAME: ${userName}\nPASSWORD: ${password}"
}
function pz_resetuser_password() {
local serverName="${1:-}"
local userName="${2:-}"
local password="${3:-}"
if [[ -z "${serverName}" ]]; then
echo "WARNING: no serverName passed"
return 1
fi
if [[ -z "${userName}" ]]; then
echo "WARNING: no userName passed"
return 1
fi
local databaseFile="${PZ_DATABASE_DIR}/${serverName}.db"
if [[ ! -f "${databaseFile}" ]]; then
echo "WARNING: db file not found for server -- ${databaseFile}"
return 1
fi
hasPzUser "${databaseFile}" "${userName}" || {
echo "WARNING: userName not found in db -- ${userName}";
return 1;
}
if [[ -z "${password}" ]]; then
password="$(makepasswd --chars=8)"
fi
local bcryptPassword
bcryptPassword="$(hashPzPassword "${password}")"
# update password
local sqlQuery="UPDATE [whitelist] SET [password] = '${bcryptPassword}', [encryptedPwd] = 'true', [pwdEncryptType] = 2
WHERE [username] = '${userName}';
"
sudo sqlite3 "${databaseFile}" <<< "${sqlQuery}"
echo "Reset ${userName} password to: ${password}"
}
function pz_additem() {
local userName="${1:-}"
local itemName="${2:-}"
local itemCount="${3:-1}"
if [[ -z "${userName}" ]]; then
echo "WARNING: no username passed"
return 1
fi
if [[ -z "${itemName}" ]]; then
echo "WARNING: no item passed"
return 1
fi
rcon_cmd "additem \"${userName}\" \"${itemName}\" ${itemCount}"
}
function pz_addvehicle() {
local userName="${1:-}"
local vehicleName="${2:-}"
if [[ -z "${userName}" ]]; then
echo "WARNING: no username passed"
return 1
fi
if [[ -z "${vehicleName}" ]]; then
echo "WARNING: no vehicle passed"
return 1
fi
rcon_cmd "addvehicle \"${vehicleName}\" \"${userName}\""
}
function pz_addxp() {
local userName="${1:-}"
local perkName="${2:-}"
if [[ -z "${userName}" ]]; then
echo "WARNING: no username passed"
return 1
fi
if [[ -z "${perkName}" ]]; then
echo "WARNING: no perk passed"
return 1
fi
rcon_cmd "addxp \"${userName}\" \"${perkName}\""
}
### graceful restart with message
### also backs up server
function pz_restart_graceful() {
local restartReason="${1:-}"
if [[ -n "${restartReason}" ]]; then
pz_broad "${restartReason}"
fi
# everything triggering off the one systemd unit
# should prevent double restarts (scheduled time of day and workshop update, or manual restart)
sudo systemctl start pz-scheduled-restart.service
}
function pz_create_backup() {
if [[ ! -d "${STEAM_DATA_BACKUP_DIR}" ]]; then
echo "WARNING: Backup dir does not exist - ${STEAM_DATA_BACKUP_DIR}"
return 2
fi
local backupDate="$(date -u +"%Y-%m-%dT%H-%M-%S")"
sudo tar cf - --exclude='./Zomboid/Logs' --exclude='./Zomboid/backups' -C "${STEAM_DATA_DIR}" './Zomboid' | \
7z a -si -t7z -m0=lzma2 -mx=4 -ms=on -mfb=64 -md=32m "${STEAM_DATA_BACKUP_DIR}/Zomboid_${backupDate}.tar.7z"
}
function pz_restore_backup() {
local backupFile="$1"
if [[ ! -f "${backupFile}" ]]; then
echo "ERROR: backup file not found - ${backupFile}"
return 2
fi
# as the backup restores EVERYTHING, we want to assure no old files remain
# utilize pz_reset_server followed by extraction, this will keep our Logs dir untouched
pz_reset_server 1 1
7z x -so "${backupFile}" | sudo tar xf - -C "${STEAM_DATA_DIR}"
}
function pz_reset_server() {
local purgeServerSettings="${1:-}"
local purgeUserDb="${2:-}"
local -a deleteDirs=(
# mod additional settings?
"${PZ_DATA}/Lua"
# the actual saved data
"${PZ_DATA}/Saves"
# does not seem to get used in MP
"${PZ_DATA}/Statistic"
# always empty, possibly not relevant on dedicated MP servers
"${PZ_DATA}/Workshop"
# weird debug settings, possibly not relevant on MP
"${PZ_DATA}/messaging"
# possibly not relevant on MP
"${PZ_DATA}/options.ini"
)
if [[ ${purgeServerSettings} -eq 1 ]]; then
deleteDirs+=("${PZ_DATA}/Server")
fi
if [[ ${purgeUserDb} -eq 1 ]]; then
deleteDirs+=("${PZ_DATA}/db")
fi
sudo rm -Rf "${deleteDirs[@]}"
}
### should only be run when server is stopped and backed up
function pz_reset_map_partial() {
local serverName="${1}"
local coordXRegex="${2}"
local coordYRegex="${3}"
if [[ -z "${serverName}" || -z "${coordXRegex}" || -z "${coordYRegex}" ]]; then
echo 'Missing input parameters'
return 1
fi
local pzServerSavesDir="${PZ_DATA}/Saves/Multiplayer/${serverName}"
if [[ ! -d "${pzServerSavesDir}" ]]; then
echo "Saves director does not exist for serverName - ${pzServerSavesDir}"
return 2
fi
# escape pipes for find
coordXRegex="${coordXRegex//|/\\|}"
coordYRegex="${coordYRegex//|/\\|}"
local findRegex files filesCount=0 keypress=''
findRegex="${pzServerSavesDir}"'/map_\('"${coordXRegex}"'\)_\('"${coordYRegex}"'\)\.bin'
files="$(sudo find "${pzServerSavesDir}" -type f -iregex "${findRegex}" | sort)"
if [[ -n "${files}" ]]; then
filesCount="$(wc -l <<< "${files}")"
fi
echo -e "Files:\n${files}"
while [[ "${keypress,,}" != 'y' && "${keypress,,}" != 'n' ]]; do
read -N1 -p "Prepared to delete the above ${filesCount} files? (Y/N) " keypress
done
echo
if [[ "${keypress,,}" != 'y' ]]; then
# not deleting
echo 'NOT DELETING'
return
fi
sudo find "${pzServerSavesDir}" -type f -iregex "${findRegex}" -delete
}
function parseAcfToJson() {
local acfFile="${1}"
if [[ ! -f "${acfFile}" ]]; then
echo "ERROR: acf file not found - ${acfFile}" 1>&2
return 1
fi
# impromptu nodejs app to parse acf into json and echo back to stdout
local acfParserScript='try {
const AcfParser = require("'"${PZ_NODE_PATH}/node_modules/steam-acf2json"'");
const Fs = require("fs");
const acfFile = Fs.readFileSync(process.argv[2], "utf-8");
const decoded = AcfParser.decode(acfFile);
console.log(JSON.stringify(decoded));
} catch (err) {
console.error(err.stack);
process.exit(1);
}
'
node - "${acfFile}" <<< "${acfParserScript}"
}
function hashPzPassword() {
local password="${1}"
# holding any common nodejs modules under this path
mkdir -p "${PZ_NODE_PATH}"
local npmStdErr retval
npmStdErr="$(npm install --prefix "${PZ_NODE_PATH}" bcryptjs 2>&1)"
retval=$?
if [[ ${retval} -ne 0 ]]; then
echo "${npmStdErr}" 1>&2
return ${retval}
fi
# we also want to assure to encode the password as a safe to pass javascript string
local jsPassword
jsPassword="$(jq '.' <<< "${CHAR_DOUBLEQUOTE}${password}${CHAR_DOUBLEQUOTE}")"
# class PZcrypt logic is rather obtuse
# first they hash password with MD5, and then feed that into bcrypt with a static salt
# shellcheck disable=SC2016
local hashPasswordScript='try {
const Crypto = require("crypto");
const Bcrypt = require("'"${PZ_NODE_PATH}/node_modules/bcryptjs"'");
const md5Hashed = Crypto.createHash("md5").update('"${jsPassword}"').digest("hex");
const hashedPassword = Bcrypt.hashSync(md5Hashed, "$2a$12$O/BFHoDFPrfFaNPAACmWpu");
console.log(hashedPassword);
} catch (err) {
console.error(err.stack);
process.exit(1);
}
'
local hashedPassword
hashedPassword="$(node - <<< "${hashPasswordScript}")"
echo "${hashedPassword}"
}
function hasPzUser() {
local databaseFile="${1}"
local userName="${2}"
local sqlQuery="SELECT '1' FROM [whitelist] WHERE [username] = '${userName}';"
local sqlResult
sqlResult="$(sqlite3 "${databaseFile}" <<< "${sqlQuery}")"
if [[ "${sqlResult}" == '1' ]]; then
# true
return 0
else
# false
return 1
fi
}