#!/bin/bash # # WyOJ install script -- runs *inside* the web container. # # install.sh -p | --prep prepare the container: apache + php config, web root # symlink, runtime directories. No database access. # Called once from the Dockerfile. # install.sh -i | --init first-boot init: write app/.config.php from the # environment, wait for the database, prepare the # data volume, mark the install as done. # Called from docker-entrypoint.sh. # # Package installation used to live here too (getAptPackage); it now lives in # the Dockerfile so that there is exactly one place that installs software. set -eu _web_root_="/opt/uoj/web" _data_root_="${UOJ_DATA:-/var/uoj_data}" usage() { cat <<'EOF' Usage: install.sh [-p|--prep] [-i|--init] -p, --prep prepare apache/php configuration and the web root symlink -i, --init first-boot initialisation (needs the database to be up) EOF } # --------------------------------------------------------------------------- # apache + php # --------------------------------------------------------------------------- setLAMPConf() { echo "==> configuring apache and php" # AllowOverride All is set here, inside the vhost, instead of patching # apache2.conf by line number (`sed -i '172s/...'`). # Logs go to the container's stdout/stderr so that `docker logs` works. cat >/etc/apache2/sites-available/000-uoj.conf < ServerAdmin ${SERVER_ADMIN:-admin@local_uoj.ac} DocumentRoot /var/www/uoj Options -Indexes +FollowSymLinks AllowOverride All Require all granted SetEnvIf Request_URI "^/judge/.*\$" judgelog ErrorLog /proc/self/fd/2 CustomLog /proc/self/fd/1 combined env=!judgelog CustomLog /proc/self/fd/1 common env=judgelog XSendFile On XSendFilePath ${_data_root_} XSendFilePath ${_web_root_}/app/storage UOJEOF a2ensite 000-uoj.conf a2dissite 000-default.conf 2>/dev/null || true a2enmod rewrite headers # PHP session store used by .htaccess (session.save_path). install -d -m 1733 /var/lib/php/uoj_sessions } # --------------------------------------------------------------------------- # web root # --------------------------------------------------------------------------- setWebConf() { echo "==> preparing web root and runtime directories" ln -sfn "${_web_root_}" /var/www/uoj mkdir -p "${_data_root_}/upload" \ "${_web_root_}/app/storage/submission" \ "${_web_root_}/app/storage/tmp" \ "${_web_root_}/app/storage/paste" chown -R www-data:www-data "${_data_root_}" "${_web_root_}/app/storage" # storage is bind-mounted from the host; it must stay group-writable. chmod -R g+ws "${_web_root_}/app/storage" } # --------------------------------------------------------------------------- # config file # # The old version piped a heredoc into `php -a` and wrote # str_replace(\'_httpHost_\', ...) # Bash does not strip the backslash in an unquoted heredoc, so that produced a # PHP parse error and no config file at all. This uses a *quoted* heredoc plus # getenv() instead, which needs no escaping and honours the variables that # docker-compose.yml already passes in. # --------------------------------------------------------------------------- writeConfig() { echo "==> writing app/.config.php from the environment" php <<'PHPEOF' waiting for the database" # The web image has no mysql client; use mysqli from the config environment. php -r ' $host = getenv("DATABASE_HOST") ?: "uoj-db"; $user = getenv("DATABASE_USER") ?: "root"; $pass = getenv("DATABASE_PASSWORD") ?: "root"; $db = getenv("DATABASE_NAME") ?: "app_uoj233"; for ($i = 0; $i < 60; $i++) { $m = @mysqli_connect($host, $user, $pass, $db); if ($m) { echo "database $host is up\n"; exit(0); } sleep(2); } fwrite(STDERR, "ERROR: database $host unreachable after 120s\n"); exit(1); ' } # --------------------------------------------------------------------------- initProgress() { writeConfig waitForDatabase echo "==> preparing the data volume" mkdir -p "${_data_root_}/upload" \ "${_web_root_}/app/storage/submission" \ "${_web_root_}/app/storage/tmp" \ "${_web_root_}/app/storage/paste" chown -R www-data:www-data "${_data_root_}" "${_web_root_}/app/storage" chmod -R g+ws "${_web_root_}/app/storage" # /judge/download/judger serves this to judge clients (self-update). # It is built into the image because app/storage is a bind mount and would # otherwise shadow whatever the image put there. if [ -f /opt/uoj/judge_client.zip ]; then cp /opt/uoj/judge_client.zip "${_web_root_}/app/storage/judge_client.zip" chown www-data:www-data "${_web_root_}/app/storage/judge_client.zip" echo "==> published judge_client.zip to app/storage" else echo "WARN: /opt/uoj/judge_client.zip is missing; /judge/download/judger will 404" fi # The schema comes from the db image (db/app_uoj233.sql), so this is # normally a no-op. It is kept so that app/upgrade/* still works later. echo "==> running upgrade:latest" php /var/www/uoj/app/cli.php upgrade:latest \ || echo "WARN: upgrade:latest failed; the schema is expected to come from the db image" touch "${_data_root_}/.UOJSetupDone" echo "*** WyOJ is ready ***" } if [ $# -le 0 ]; then usage exit 1 fi while [ $# -gt 0 ]; do case "$1" in -p | --prep) echo 'Preparing WyOJ web container...' setLAMPConf setWebConf ;; -i | --init) echo 'Initialising WyOJ...' initProgress ;; -h | --help) usage ;; *) echo "Illegal option $1" >&2 usage >&2 exit 1 ;; esac shift done