From 282d13f63243ce9fe0ebf927b8d3e6ace25737c7 Mon Sep 17 00:00:00 2001 From: Shish Date: Tue, 7 Nov 2023 20:58:46 +0000 Subject: [PATCH] Use nginx Unit rather than the php development server --- .devcontainer/devcontainer.json | 10 ++- .docker/entrypoint.d/config.json | 76 ++++++++++++++++++++++ .docker/entrypoint.sh | 106 +++++++++++++++++++++++++++++++ Dockerfile | 26 ++++---- core/util.php | 14 +++- tests/docker-init.sh | 12 ---- 6 files changed, 216 insertions(+), 28 deletions(-) create mode 100644 .docker/entrypoint.d/config.json create mode 100755 .docker/entrypoint.sh delete mode 100644 tests/docker-init.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6acb534b..43bbf0a1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -8,6 +8,9 @@ "target": "devcontainer" }, + "workspaceMount": "source=${localWorkspaceFolder},target=/app,type=bind", + "workspaceFolder": "/app", + // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, @@ -15,8 +18,11 @@ "forwardPorts": [8000], // Uncomment the next line to run commands after the container is created. - "postCreateCommand": "cd /workspaces/shimmie2 && /usr/bin/php -S 0.0.0.0:8000 tests/router.php 2>&1 | grep --line-buffered -vE \" (Accepted|Closing)\"", - + "postCreateCommand": "./.docker/entrypoint.sh unitd --no-daemon --control unix:/var/run/control.unit.sock", + "containerEnv": { + "UID": "2000", + "GID": "2000" + }, "customizations": { "vscode": { "extensions": [ diff --git a/.docker/entrypoint.d/config.json b/.docker/entrypoint.d/config.json new file mode 100644 index 00000000..3d7a9ff2 --- /dev/null +++ b/.docker/entrypoint.d/config.json @@ -0,0 +1,76 @@ +{ + "listeners": { + "*:8000": { + "pass": "routes", + "forwarded": { + "client_ip": "X-Forwarded-For", + "recursive": false, + "source": [ + "172.17.0.0/16" + ] + } + } + }, + "routes": [ + { + "match": { + "uri": "~/_(thumbs|images)/.*" + }, + "action": { + "share": [ + "`/app/data/${uri.replace(/_(thumbs|images)\\/(..)(..)(.*?)\\/.*/, '$1/$2/$3/$2$3$4')}`", + "`/app/data/${uri.replace(/_(thumbs|images)\\/(..)(.*?)\\/.*/, '$1/$2/$2$3')}`" + ], + "response_headers": { + "Cache-Control": "public, max-age=31556926" + } + } + }, + { + "action": { + "share": [ + "/app/ext/static_files/static/$uri", + "/app/$uri" + ], + "types": [ + "image/*", + "application/javascript", + "text/css", + "!" + ], + "response_headers": { + "Cache-Control": "public, max-age=31556926" + }, + "fallback": { + "pass": "applications/shimmie" + } + } + } + ], + "applications": { + "shimmie": { + "type": "php", + "user": "shimmie", + "root": "/app/", + "script": "index.php", + "working_directory": "/app/", + "options": { + "admin": { + "memory_limit": "256M", + "upload_max_filesize": "50M", + "post_max_size": "50M" + } + }, + "processes": { + "max": 8, + "spare": 2, + "idle_timeout": 60 + } + } + }, + "settings": { + "http": { + "max_body_size": 104857600 + } + } +} diff --git a/.docker/entrypoint.sh b/.docker/entrypoint.sh new file mode 100755 index 00000000..9649d66c --- /dev/null +++ b/.docker/entrypoint.sh @@ -0,0 +1,106 @@ +#!/bin/sh + +set -e + +# if user shimmie doesn't already exist, create it +if ! id -u shimmie >/dev/null 2>&1; then + groupadd -g $GID shimmie || true + useradd -ms /bin/bash -u $UID -g $GID shimmie || true +fi +mkdir -p /app/data +chown shimmie:shimmie /app/data + +rm -rf /var/lib/unit/* + +WAITLOOPS=5 +SLEEPSEC=1 + +curl_put() +{ + RET=$(/usr/bin/curl -s -w '%{http_code}' -X PUT --data-binary @$1 --unix-socket /var/run/control.unit.sock http://localhost/$2) + RET_BODY=$(echo $RET | /bin/sed '$ s/...$//') + RET_STATUS=$(echo $RET | /usr/bin/tail -c 4) + if [ "$RET_STATUS" -ne "200" ]; then + echo "$0: Error: HTTP response status code is '$RET_STATUS'" + echo "$RET_BODY" + return 1 + else + echo "$0: OK: HTTP response status code is '$RET_STATUS'" + echo "$RET_BODY" + fi + return 0 +} + +if [ "$1" = "unitd" ] || [ "$1" = "unitd-debug" ]; then + if /usr/bin/find "/var/lib/unit/" -mindepth 1 -print -quit 2>/dev/null | /bin/grep -q .; then + echo "$0: /var/lib/unit/ is not empty, skipping initial configuration..." + else + echo "$0: Launching Unit daemon to perform initial configuration..." + /usr/sbin/$1 --control unix:/var/run/control.unit.sock + + for i in $(/usr/bin/seq $WAITLOOPS); do + if [ ! -S /var/run/control.unit.sock ]; then + echo "$0: Waiting for control socket to be created..." + /bin/sleep $SLEEPSEC + else + break + fi + done + # even when the control socket exists, it does not mean unit has finished initialisation + # this curl call will get a reply once unit is fully launched + /usr/bin/curl -s -X GET --unix-socket /var/run/control.unit.sock http://localhost/ + if /usr/bin/find "/app/.docker/entrypoint.d/" -mindepth 1 -print -quit 2>/dev/null | /bin/grep -q .; then + echo "$0: /app/.docker/entrypoint.d/ is not empty, applying initial configuration..." + + echo "$0: Looking for certificate bundles in /app/.docker/entrypoint.d/..." + for f in $(/usr/bin/find /app/.docker/entrypoint.d/ -type f -name "*.pem"); do + echo "$0: Uploading certificates bundle: $f" + curl_put $f "certificates/$(basename $f .pem)" + done + echo "$0: Looking for JavaScript modules in /app/.docker/entrypoint.d/..." + for f in $(/usr/bin/find /app/.docker/entrypoint.d/ -type f -name "*.js"); do + echo "$0: Uploading JavaScript module: $f" + curl_put $f "js_modules/$(basename $f .js)" + done + + echo "$0: Looking for configuration snippets in /app/.docker/entrypoint.d/..." + for f in $(/usr/bin/find /app/.docker/entrypoint.d/ -type f -name "*.json"); do + echo "$0: Applying configuration $f"; + curl_put $f "config" + done + echo "$0: Looking for shell scripts in /app/.docker/entrypoint.d/..." + for f in $(/usr/bin/find /app/.docker/entrypoint.d/ -type f -name "*.sh"); do + echo "$0: Launching $f"; + "$f" + done + + # warn on filetypes we don't know what to do with + for f in $(/usr/bin/find /app/.docker/entrypoint.d/ -type f -not -name "*.sh" -not -name "*.json" -not -name "*.pem" -not -name "*.js"); do + echo "$0: Ignoring $f"; + done + else + echo "$0: /app/.docker/entrypoint.d/ is empty, creating 'welcome' configuration..." + curl_put /usr/share/unit/welcome/welcome.json "config" + fi + echo "$0: Stopping Unit daemon after initial configuration..." + kill -TERM $(/bin/cat /var/run/unit.pid) + + for i in $(/usr/bin/seq $WAITLOOPS); do + if [ -S /var/run/control.unit.sock ]; then + echo "$0: Waiting for control socket to be removed..." + /bin/sleep $SLEEPSEC + else + break + fi + done + if [ -S /var/run/control.unit.sock ]; then + kill -KILL $(/bin/cat /var/run/unit.pid) + rm -f /var/run/control.unit.sock + fi + echo + echo "$0: Unit initial configuration complete; ready for start up..." + echo + fi +fi + +exec "$@" diff --git a/Dockerfile b/Dockerfile index 7477642c..75f18988 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,11 +2,15 @@ ARG PHP_VERSION=8.2 # Install base packages which all stages (build, test, run) need FROM debian:bookworm AS base -RUN apt update && apt upgrade -y && apt install -y \ +RUN apt update && apt upgrade -y +RUN apt update && apt install -y curl +RUN curl --output /usr/share/keyrings/nginx-keyring.gpg https://unit.nginx.org/keys/nginx-keyring.gpg +RUN echo 'deb [signed-by=/usr/share/keyrings/nginx-keyring.gpg] https://packages.nginx.org/unit/debian/ bookworm unit' > /etc/apt/sources.list.d/unit.list +RUN apt update && apt install -y \ php${PHP_VERSION}-cli php${PHP_VERSION}-gd php${PHP_VERSION}-zip php${PHP_VERSION}-xml php${PHP_VERSION}-mbstring \ php${PHP_VERSION}-pgsql php${PHP_VERSION}-mysql php${PHP_VERSION}-sqlite3 \ - gosu curl imagemagick ffmpeg zip unzip && \ - rm -rf /var/lib/apt/lists/* + gosu curl imagemagick ffmpeg zip unzip git unit unit-php +RUN apt update && apt install -y procps net-tools # Composer has 100MB of dependencies, and we only need that during build and test FROM base AS composer @@ -15,7 +19,7 @@ ENV XDEBUG_MODE=coverage # "Build" shimmie (composer install - done in its own stage so that we don't # need to include all the composer fluff in the final image) -FROM composer AS app +FROM composer AS build COPY composer.json composer.lock /app/ WORKDIR /app RUN composer install --no-dev @@ -39,16 +43,12 @@ RUN [ $RUN_TESTS = false ] || (\ # Devcontainer target FROM composer AS devcontainer -RUN apt update && apt upgrade -y && apt install -y git && rm -rf /var/lib/apt/lists/* +EXPOSE 8000 # Actually run shimmie -FROM base +FROM base AS run EXPOSE 8000 HEALTHCHECK --interval=1m --timeout=3s CMD curl --fail http://127.0.0.1:8000/ || exit 1 -ENV UID=1000 \ - GID=1000 \ - UPLOAD_MAX_FILESIZE=50M -COPY --from=app /app /app - -WORKDIR /app -CMD ["/bin/sh", "/app/tests/docker-init.sh"] +COPY --from=build /app /app +ENTRYPOINT ["/app/.docker/unit-entrypoint.sh"] +CMD ["unitd", "--no-daemon", "--control", "unix:/var/run/control.unit.sock"] \ No newline at end of file diff --git a/core/util.php b/core/util.php index 8a59321e..d2150d27 100644 --- a/core/util.php +++ b/core/util.php @@ -686,7 +686,19 @@ function _get_user(): User function _get_query(): string { - return (@$_POST["q"] ?: @$_GET["q"]) ?: "/"; + // if query is explicitly set, use it + $q = @$_POST["q"] ?: @$_GET["q"]; + if(!empty($q)) { + return $q; + } + // if we're just looking at index.php, use the default query + elseif (str_contains($_SERVER['REQUEST_URI'], "index.php")) { + return "/"; + } + // otherwise, use the request URI + else { + return explode("?", $_SERVER['REQUEST_URI'])[0]; + } } diff --git a/tests/docker-init.sh b/tests/docker-init.sh deleted file mode 100644 index e8385443..00000000 --- a/tests/docker-init.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh -groupadd -g $GID shimmie || true -useradd -ms /bin/bash -u $UID -g $GID shimmie -mkdir -p /app/data -chown $UID:$GID /app/data -export PHP_CLI_SERVER_WORKERS=8 -exec gosu shimmie:shimmie \ - /usr/bin/php \ - -d upload_max_filesize=$UPLOAD_MAX_FILESIZE \ - -d post_max_size=$UPLOAD_MAX_FILESIZE \ - -S 0.0.0.0:8000 \ - tests/router.php 2>&1 | grep --line-buffered -vE " (Accepted|Closing)"