-
Notifications
You must be signed in to change notification settings - Fork 266
/
Copy pathdj_setup_database.in
executable file
·482 lines (418 loc) · 11.5 KB
/
dj_setup_database.in
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
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
#!/bin/bash
# @configure_input@
# This script allows one to perform DOMjudge database setup actions.
# See usage below for possible commands and options.
set -e
BINDIR="@domserver_bindir@"
ETCDIR="@domserver_etcdir@"
WEBAPPDIR="@domserver_webappdir@"
EXAMPLEPROBDIR="@domserver_exampleprobdir@"
DATABASEDUMPDIR="@domserver_databasedumpdir@"
PASSWDFILE="$ETCDIR/dbpasswords.secret"
verbose()
{
if [ -z "$QUIET" ]; then
echo "$@"
fi
}
usage() {
cat <<EOF
Usage: $0 [option]... <command> [argument]
Commands:
status check database installation status
genpass generate DB,API,Symfony,admin password files
create-db-users create (empty) database and users
update-password update DB user database to that in 'etc/dbpasswords.secret'
install create database, example contest and users if not existing
bare-install create database, setup defaults if not existing
uninstall remove database users and database, INCLUDING ALL DATA!
install-examples install examples only
install-loadtest configure for load testing. WARNING: CREATES A LOT OF EXTRA ITEMS!
upgrade upgrade MySQL database schema to current version
dump [filename] backup the current database to file (without .sql.gz suffix)
load [filename] load a backup from file (without .sql.gz suffix), REMOVES ALL PREVIOUS DATA!
Options:
-u <user> connect to MySQL with DB admin <user>
-p <pass> use password <pass> for DB admin user
-q be (mostly) quiet
-r read DB admin password from prompt
-s connect via local socket (do not specify port)
Note: you may want to store your credentials in ~/.my.cnf in order to
not have to pass any of the options above.
EOF
}
urlencode()
{
php -r "echo rawurlencode('$1');"
}
# This is global variable to be able to return the output from
# mysql_options() below as an array, which is not possible otherwise.
declare -a _mysql_options
mysql_options()
{
local user pass
_mysql_options=()
# shellcheck disable=SC2153
if [ -n "$DBUSER" ]; then
_mysql_options+=('-u' "$DBUSER")
elif [ -n "$DBA_USER" ]; then
_mysql_options+=('-u' "$DBA_USER")
fi
# shellcheck disable=SC2153
if [ -n "$PASSWD" ]; then
_mysql_options+=("-p$PASSWD")
elif [ -n "$DBA_PASSWD" ]; then
_mysql_options+=("-p$DBA_PASSWD")
elif [ -n "$PROMPT_PASSWD" ]; then
_mysql_options+=('-p')
fi
_mysql_options+=('-h' "$DBHOST")
if [ -z "$USE_SOCKET" ]; then
_mysql_options+=("-P$DBPORT")
fi
}
# Wrapper around mysql command to allow setting options, user, etc.
mysql()
{
mysql_options
command mysql "${_mysql_options[@]}" --silent --skip-column-names "$@"
}
# Quick shell hack to get a key from an INI file.
# This is not perfect and ignores sections at least!
get_ini_file_key()
{
(
FILE="$1"
KEY="$2"
grep "^[[:space:]]*${KEY}[[:space:]]*=" "$FILE" 2>/dev/null \
| sed -r "s/^[[:space:]]*${KEY}[[:space:]]*=[[:space:]]*//"
)
}
# Wrapper around the Symfony console to allow setting the connection and passing options
symfony_console()
{
if [ -n "$QUIET" ]; then
ARG="-q"
else
ARG=""
fi
DATABASE_URL=
# If we do not have an explicit user and password set, determine them from passed arguments,
# ~/.my.cnf or the current user
if [ -z "$DBUSER" ]; then
if [ -n "$DBA_USER" ]; then
if [ -n "$PROMPT_PASSWD" ]; then
stty -echo
printf "Enter password: "
read -r DBA_PASSWD
stty echo
printf "\n"
fi
fi
# If we do not have a user or password yet, try to read it from ~/.my.cnf
if [ -f ~/.my.cnf ]; then
[ -z "$DBA_USER" ] && DBA_USER=$( get_ini_file_key ~/.my.cnf 'user')
[ -z "$DBA_PASSWD" ] && DBA_PASSWD=$(get_ini_file_key ~/.my.cnf 'password')
fi
if [ -z "$DBA_USER" ]; then
DBA_USER=$(whoami)
fi
if [ -n "$DBA_USER" ]; then
user=$(urlencode "${DBA_USER}")
host=$(urlencode "${domjudge_DBHOST}")
db=$(urlencode "${domjudge_DBNAME}")
if [ -n "$DBA_PASSWD" ]; then
DATABASE_URL="mysql://$user:$(urlencode "${DBA_PASSWD}")@$host:${domjudge_DBPORT}/$db"
else
DATABASE_URL="mysql://$user@$host:${domjudge_DBPORT}/$db"
fi
fi
fi
if [ -n "$DATABASE_URL" ]; then
DATABASE_URL=$DATABASE_URL ${WEBAPPDIR}/bin/console -v $ARG "$@"
else
${WEBAPPDIR}/bin/console -v $ARG "$@"
fi
# Make sure any generated cache data has the right permissions if we ran as root.
if [ `id -u` -eq 0 ]; then
${BINDIR}/fix_permissions
fi
}
read_dbpasswords()
{
if [ ! -r "$PASSWDFILE" ]; then
echo "Error: password file '$PASSWDFILE' not found or not readable."
echo "You may want to run: $0 genpass"
return 1
fi
OLDIFS="$IFS"
IFS=":"
# Don't pipe $PASSWDFILE into this while loop as that spawns a
# subshell and then variables are not retained in the original shell.
while read -r role host db user passwd port dummy; do
# Skip lines beginning with a '#'
[ "x$role" != "x${role###}" ] && continue
domjudge_DBHOST=$host
domjudge_DBPORT=$port
domjudge_DBNAME=$db
domjudge_DBUSER=$user
domjudge_PASSWD=$passwd
DBHOST=$host
DBNAME=$db
DBPORT=$port
done < "$PASSWDFILE"
IFS="$OLDIFS"
if [ -z "$domjudge_DBPORT" ]; then
domjudge_DBPORT=3306
fi
if [ -z "$DBPORT" ]; then
DBPORT=3306
fi
if [ -z "$domjudge_PASSWD" ]; then
echo "Error: no login info found."
return 1
fi
verbose "Database credentials read from '$PASSWDFILE'."
}
status()
{
if [ ! -r "$PASSWDFILE" ]; then
echo "Error: cannot read database password file '$PASSWDFILE'."
return 1
fi
read_dbpasswords || return 1
printf "Trying to connect to the server as DB admin: "
mysql -e 'SELECT "success.";' || return 1
printf "Trying to connect to the server with user '%s': " "$domjudge_DBUSER"
DBUSER=$domjudge_DBUSER PASSWD=$domjudge_PASSWD mysql -e 'SELECT "success.";' || return 1
printf "Searching for database '%s': " "$DBNAME"
mysql -e "USE \`$DBNAME\`; SELECT 'found.';" || return 1
printf "Searching for data in database: "
DBUSER=$domjudge_DBUSER PASSWD=$domjudge_PASSWD \
mysql -e "USE \`$DBNAME\`; SELECT name FROM team WHERE name = 'DOMjudge';" || return 1
printf "MySQL server version: "
mysql -e 'SELECT version();'
}
create_db_users()
{
(
# The MySQL character set and collation are hardcoded here, but
# can be changed in the database and their configuration settings
# in etc/domserver-config.php updated after installation.
echo "CREATE DATABASE IF NOT EXISTS \`$DBNAME\` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
echo "CREATE USER IF NOT EXISTS '$domjudge_DBUSER'@'localhost' IDENTIFIED BY '$domjudge_PASSWD';"
echo "GRANT SELECT, INSERT, UPDATE, DELETE ON \`$DBNAME\`.* TO '$domjudge_DBUSER'@'localhost';"
echo "FLUSH PRIVILEGES;"
) | mysql
verbose "DOMjudge database and user(s) created."
}
remove_db_users()
{
(
echo "DROP DATABASE IF EXISTS \`$DBNAME\`;"
echo "DROP USER IF EXISTS '$domjudge_DBUSER'@'localhost';"
echo "FLUSH PRIVILEGES;"
) | mysql -f
verbose "DOMjudge database and user(s) removed."
}
update_password()
{
read_dbpasswords
(
echo "ALTER USER '$domjudge_DBUSER'@'localhost' IDENTIFIED BY '$domjudge_PASSWD';"
echo "FLUSH PRIVILEGES;"
) | mysql
verbose "ALTER USER '$domjudge_DBUSER'@'localhost' IDENTIFIED BY '$domjudge_PASSWD';"
verbose "Database user password updated from credentials file."
}
install_examples()
{
DBUSER=$domjudge_DBUSER PASSWD=$domjudge_PASSWD symfony_console domjudge:load-example-data
"$EXAMPLEPROBDIR"/generate-contest-yaml
( cd "$EXAMPLEPROBDIR" && yes y | "$BINDIR"/import-contest )
}
uninstall_helper()
{
read_dbpasswords
remove_db_users
}
create_db_users_helper()
{
read_dbpasswords
create_db_users
verbose "Created empty database and users."
}
create_database_dump () {
sudo mysqldump $(mysql_options) --opt --skip-lock-tables domjudge | pv | gzip > "$DATABASEDUMPDIR/${1}.sql.gz"
}
### Script starts here ###
# Parse command-line options:
while getopts ':u:p:qrs' OPT ; do
case "$OPT" in
u)
DBA_USER=$OPTARG
;;
p)
DBA_PASSWD=$OPTARG
;;
q)
QUIET=1
;;
r)
PROMPT_PASSWD=1
;;
s)
USE_SOCKET=1
;;
:)
echo "Error: option '$OPTARG' requires an argument."
usage
exit 1
;;
?)
echo "Error: unknown option '$OPTARG'."
usage
exit 1
;;
*)
echo "Error: unknown error reading option '$OPT', value '$OPTARG'."
usage
exit 1
;;
esac
done
shift $((OPTIND-1))
case "$1" in
status)
if status ; then
echo "Database check successful: database and users present and accessible."
exit 0
else
echo "Database status check failed."
exit 1
fi
;;
genpass)
$ETCDIR/gen_all_secrets
;;
uninstall)
uninstall_helper
;;
install-examples)
read_dbpasswords
install_examples
;;
install-loadtest)
read_dbpasswords
create_db_users
export DB_FIRST_INSTALL=1
symfony_console doctrine:migrations:migrate -n
unset DB_FIRST_INSTALL
DBUSER=$domjudge_DBUSER PASSWD=$domjudge_PASSWD symfony_console domjudge:load-default-data
DBUSER=$domjudge_DBUSER PASSWD=$domjudge_PASSWD symfony_console domjudge:load-gatling-data
;;
create-db-users)
create_db_users_helper
;;
update-password)
update_password
;;
bare-install|install)
read_dbpasswords
create_db_users
export DB_FIRST_INSTALL=1
symfony_console doctrine:migrations:migrate -n
unset DB_FIRST_INSTALL
DBUSER=$domjudge_DBUSER PASSWD=$domjudge_PASSWD symfony_console domjudge:load-default-data
if [ "$1" = "install" ]; then
install_examples
verbose "SQL structure and default/example data installed."
else
verbose "SQL structure and defaults installed (no sample data)."
fi
;;
upgrade)
# check for legacy dbpasswords.secret content
if grep -Eq ^team: $PASSWDFILE >/dev/null 2>&1 ; then
echo "Warning: please remove all non-jury users from $PASSWDFILE"
echo "You may also remove those users from MySQL."
fi
read_dbpasswords
# Check if we need to upgrade the Doctrine migrations table
if ! echo "SHOW CREATE TABLE \`doctrine_migration_versions\`" | mysql "$DBNAME" >/dev/null 2>&1; then
symfony_console doctrine:migrations:sync-metadata-storage -n
# shellcheck disable=SC2016
echo 'INSERT INTO `doctrine_migration_versions`
(version, executed_at, execution_time)
SELECT concat("DoctrineMigrations\\\\Version", version), executed_at, 1
FROM migration_versions;' | mysql "$DBNAME"
echo "DROP TABLE \`migration_versions\`" | mysql "$DBNAME"
fi
symfony_console doctrine:migrations:migrate -n
DBUSER=$domjudge_DBUSER PASSWD=$domjudge_PASSWD symfony_console domjudge:load-default-data
verbose "DOMjudge database upgrade completed."
;;
dump)
read_dbpasswords
DUMPNAME="$2"
if [ -z "$DUMPNAME" ]; then
usage
exit 1
fi
if [ -f "${DATABASEDUMPDIR}/${DUMPNAME}.sql.gz" ]; then
while true; do
read -p "Overwrite existing database dump (y/N)? " yn
case $yn in
[Yy]* ) break ;;
''|[Nn]* ) exit 0;;
esac
done
fi
create_database_dump $DUMPNAME
exit 0
;;
load)
DUMPNAME="$2"
FILE=""
if [ -z "$DUMPNAME" ]; then
databases=$(find "$DATABASEDUMPDIR" -name "*.sql.gz" -type f -print0)
if [ -z "$databases" ]; then
echo "No files with .sql.gz suffix found in '$DATABASEDUMPDIR'"
exit 1
fi
ind=1
for i in "$databases"; do
echo "$ind) $i"
: $((ind+=1))
done
while true; do
read -p "Which database should be loaded? " db
ind=1
for i in "$databases"; do
if [ "$ind" = "$db" ]; then
FILE="$i"
break
fi
: $((ind+=1))
done
if [ -n "$FILE" ]; then
break
fi
done
else
FILE="$DATABASEDUMPDIR/${DUMPNAME}.sql.gz"
fi
if [ ! -f "${FILE}" ]; then
echo "Error: file ${FILE} not found."
exit 1
fi
uninstall_helper
create_db_users_helper
pv "${FILE}" | gunzip | mysql domjudge
;;
*)
echo "Error: Unknown subcommand '$1'"
usage
exit 1
;;
esac