diff --git a/Dockerfile-unit.template b/Dockerfile-unit.template new file mode 100644 index 00000000..0095b351 --- /dev/null +++ b/Dockerfile-unit.template @@ -0,0 +1,164 @@ +FROM unit:%%UNIT_VERSION%%-php%%PHP_VERSION%% + +# make nginx-unit co-operate with the nextcloud entrypoint +RUN set -ex; \ + \ + mv /docker-entrypoint.d /unit-entrypoint.d; \ + mv /usr/local/bin/docker-entrypoint.sh /usr/local/bin/unit-entrypoint.sh; \ + sed -e 's/docker-entrypoint.d/unit-entrypoint.d/' -i /usr/local/bin/unit-entrypoint.sh + +# entrypoint.sh and cron.sh dependencies +RUN set -ex; \ + \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + busybox-static \ + bzip2 \ + libldap-common \ + libmagickcore-6.q16-6-extra \ + rsync \ + ; \ + rm -rf /var/lib/apt/lists/*; \ + \ + mkdir -p /var/spool/cron/crontabs; \ + echo '*/%%CRONTAB_INT%% * * * * php -f /var/www/html/cron.php' > /var/spool/cron/crontabs/www-data + +# install the PHP extensions we need +# see https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html +ENV PHP_MEMORY_LIMIT 512M +ENV PHP_UPLOAD_LIMIT 512M +RUN set -ex; \ + \ + savedAptMark="$(apt-mark showmanual)"; \ + \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + libcurl4-openssl-dev \ + libevent-dev \ + libfreetype6-dev \ + libgmp-dev \ + libicu-dev \ + libjpeg-dev \ + libldap2-dev \ + libmagickwand-dev \ + libmcrypt-dev \ + libmemcached-dev \ + libpng-dev \ + libpq-dev \ + libwebp-dev \ + libxml2-dev \ + libzip-dev \ + ; \ + \ + debMultiarch="$(dpkg-architecture --query DEB_BUILD_MULTIARCH)"; \ + docker-php-ext-configure gd --with-freetype --with-jpeg --with-webp; \ + docker-php-ext-configure ldap --with-libdir="lib/$debMultiarch"; \ + docker-php-ext-install -j "$(nproc)" \ + bcmath \ + exif \ + gd \ + gmp \ + intl \ + ldap \ + opcache \ + pcntl \ + pdo_mysql \ + pdo_pgsql \ + sysvsem \ + zip \ + ; \ + \ +# pecl will claim success even if one install fails, so we need to perform each install separately + pecl install APCu-%%APCU_VERSION%%; \ + pecl install imagick-%%IMAGICK_VERSION%%; \ + pecl install memcached-%%MEMCACHED_VERSION%%; \ + pecl install redis-%%REDIS_VERSION%%; \ + \ + docker-php-ext-enable \ + apcu \ + imagick \ + memcached \ + redis \ + ; \ + rm -r /tmp/pear; \ + \ +# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies + apt-mark auto '.*' > /dev/null; \ + apt-mark manual $savedAptMark; \ + ldd "$(php -r 'echo ini_get("extension_dir");')"/*.so \ + | awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); print so }' \ + | sort -u \ + | xargs -r dpkg-query --search \ + | cut -d: -f1 \ + | sort -u \ + | xargs -rt apt-mark manual; \ + \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + rm -rf /var/lib/apt/lists/* + +# set recommended PHP.ini settings +# see https://docs.nextcloud.com/server/latest/admin_manual/installation/server_tuning.html#enable-php-opcache +RUN { \ + echo 'opcache.enable=1'; \ + echo 'opcache.interned_strings_buffer=32'; \ + echo 'opcache.max_accelerated_files=10000'; \ + echo 'opcache.memory_consumption=128'; \ + echo 'opcache.save_comments=1'; \ + echo 'opcache.revalidate_freq=60'; \ + echo 'opcache.jit=1255'; \ + echo 'opcache.jit_buffer_size=128M'; \ + } > "${PHP_INI_DIR}/conf.d/opcache-recommended.ini"; \ + \ + echo 'apc.enable_cli=1' >> "${PHP_INI_DIR}/conf.d/docker-php-ext-apcu.ini"; \ + \ + { \ + echo 'memory_limit=${PHP_MEMORY_LIMIT}'; \ + echo 'upload_max_filesize=${PHP_UPLOAD_LIMIT}'; \ + echo 'post_max_size=${PHP_UPLOAD_LIMIT}'; \ + } > "${PHP_INI_DIR}/conf.d/nextcloud.ini"; \ + \ + mkdir /var/www/data; \ + mkdir -p /docker-entrypoint-hooks.d/pre-installation \ + /docker-entrypoint-hooks.d/post-installation \ + /docker-entrypoint-hooks.d/pre-upgrade \ + /docker-entrypoint-hooks.d/post-upgrade \ + /docker-entrypoint-hooks.d/before-starting; \ + chown -R www-data:root /var/www; \ + chmod -R g=u /var/www + +VOLUME /var/www/html +%%VARIANT_EXTRAS%% + +ENV NEXTCLOUD_VERSION %%VERSION%% + +RUN set -ex; \ + fetchDeps=" \ + gnupg \ + dirmngr \ + "; \ + apt-get update; \ + apt-get install -y --no-install-recommends $fetchDeps; \ + \ + curl -fsSL -o nextcloud.tar.bz2 "%%DOWNLOAD_URL%%"; \ + curl -fsSL -o nextcloud.tar.bz2.asc "%%DOWNLOAD_URL_ASC%%"; \ + export GNUPGHOME="$(mktemp -d)"; \ +# gpg key from https://nextcloud.com/nextcloud.asc + gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 28806A878AE423A28372792ED75899B9A724937A; \ + gpg --batch --verify nextcloud.tar.bz2.asc nextcloud.tar.bz2; \ + tar -xjf nextcloud.tar.bz2 -C /usr/src/; \ + gpgconf --kill all; \ + rm nextcloud.tar.bz2.asc nextcloud.tar.bz2; \ + rm -rf "$GNUPGHOME" /usr/src/nextcloud/updater; \ + mkdir -p /usr/src/nextcloud/data; \ + mkdir -p /usr/src/nextcloud/custom_apps; \ + chmod +x /usr/src/nextcloud/occ; \ + \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false $fetchDeps; \ + rm -rf /var/lib/apt/lists/* + +COPY *.sh upgrade.exclude / +COPY config/* /usr/src/nextcloud/config/ +COPY nextcloud-unit.json /unit-entrypoint.d/ + +ENTRYPOINT ["/entrypoint.sh"] +CMD ["%%CMD%%"] diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 5b627f07..a91a116a 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -82,7 +82,7 @@ if expr "$1" : "apache" 1>/dev/null; then fi fi -if expr "$1" : "apache" 1>/dev/null || [ "$1" = "php-fpm" ] || [ "${NEXTCLOUD_UPDATE:-0}" -eq 1 ]; then +if expr "$1" : "apache" 1>/dev/null || [ "$1" = "php-fpm" ] || expr "$1" : "unitd" 1>/dev/null || [ "${NEXTCLOUD_UPDATE:-0}" -eq 1 ]; then uid="$(id -u)" gid="$(id -g)" if [ "$uid" = '0' ]; then @@ -276,4 +276,8 @@ if expr "$1" : "apache" 1>/dev/null || [ "$1" = "php-fpm" ] || [ "${NEXTCLOUD_UP run_path before-starting fi +if expr "$1" : "unitd" 1>/dev/null; then + exec /usr/local/bin/unit-entrypoint.sh "$@" +fi + exec "$@" diff --git a/nextcloud-unit.json b/nextcloud-unit.json new file mode 100644 index 00000000..12b8ac00 --- /dev/null +++ b/nextcloud-unit.json @@ -0,0 +1,162 @@ +{ + "listeners": { + "*:80": { + "pass": "routes", + "forwarded": { + "client_ip": "X-Forwarded-For", + "source": [ + "10.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + "fc00::/7" + ] + } + } + }, + + "routes": [ + { + "match": { + "uri": [ + "/.well-known/carddav", + "/.well-known/caldav" + ] + }, + + "action": { + "return": 301, + "location": "/remote.php/dav" + } + }, + { + "match": { + "uri": [ + "/.well-known/*" + ] + }, + + "action": { + "pass": "applications/nextcloud/index" + } + }, + { + "match": { + "uri": [ + "/build/*", + "/tests/*", + "/config/*", + "/lib/*", + "/3rdparty/*", + "/templates/*", + "/data/*", + "/.*", + "/autotest*", + "/occ*", + "/issue*", + "/indie*", + "/db_*", + "/console*" + ] + }, + + "action": { + "return": 404 + } + }, + { + "match": { + "uri": [ + "/core/ajax/update.php*", + "/cron.php*", + "/ocs-provider*.php*", + "/ocs/v1.php*", + "/ocs/v2.php*", + "/public.php*", + "/remote.php*", + "/status.php*", + "/updater*.php*" + ] + }, + + "action": { + "pass": "applications/nextcloud/direct" + } + }, + { + "match": { + "uri": "/ocs-provider*" + }, + + "action": { + "pass": "applications/nextcloud/ocs" + } + }, + { + "match": { + "uri": [ + "/index.php", + "index.php/*" + ] + }, + "action": { + "pass": "applications/nextcloud/index" + } + }, + { + "match": { + "uri": [ + "~\\.(?:css|js|mjs|svg|gif|png|jpg|ico|wasm|tflite|map|ogg|flac|woff2?)$" + ] + }, + "action": { + "share": "/var/www/html$uri", + "fallback": { + "pass": "applications/nextcloud/index" + }, + "response_headers": { + "Cache-Control": "public, max-age=15778463" + } + } + }, + { + "action": { + "share": "/var/www/html$uri", + "fallback": { + "pass": "applications/nextcloud/index" + } + } + } + ], + + "applications": { + "nextcloud": { + "type": "php", + "user": "www-data", + "processes": {}, + "targets": { + "direct": { + "root": "/var/www/html/" + }, + + "index": { + "root": "/var/www/html/", + "script": "index.php" + }, + + "ocs": { + "root": "/var/www/html/ocs-provider/", + "script": "index.php" + } + }, + "environment": { + "front_controller_active": "true" + } + } + }, + + "settings": { + "http": { + "max_body_size": 1073741824 + } + } +} diff --git a/update.sh b/update.sh index 015cf9ae..458352b6 100755 --- a/update.sh +++ b/update.sh @@ -17,24 +17,37 @@ declare -A cmd=( [apache]='apache2-foreground' [fpm]='php-fpm' [fpm-alpine]='php-fpm' + [unit]='unitd --no-daemon' ) declare -A base=( [apache]='debian' [fpm]='debian' [fpm-alpine]='alpine' + [unit]='unit' ) declare -A extras=( [apache]='\nRUN a2enmod headers rewrite remoteip ; \\\n { \\\n echo '\''RemoteIPHeader X-Real-IP'\''; \\\n echo '\''RemoteIPInternalProxy 10.0.0.0/8'\''; \\\n echo '\''RemoteIPInternalProxy 172.16.0.0/12'\''; \\\n echo '\''RemoteIPInternalProxy 192.168.0.0/16'\''; \\\n } > /etc/apache2/conf-available/remoteip.conf; \\\n a2enconf remoteip\n\n# set apache config LimitRequestBody\nENV APACHE_BODY_LIMIT 1073741824\nRUN { \\\n echo '\''LimitRequestBody ${APACHE_BODY_LIMIT}'\''; \\\n } > /etc/apache2/conf-available/apache-limits.conf; \\\n a2enconf apache-limits' [fpm]='' [fpm-alpine]='' + [unit]='' ) declare -A crontab_int=( [default]='5' ) +unit_version="$( + git ls-remote --tags https://github.com/nginx/unit.git \ + | cut -d/ -f3 \ + | grep -v -- '-1' \ + | grep -v '\^' \ + | sed -E 's/^v//' \ + | sort -V \ + | tail -1 +)" + apcu_version="$( git ls-remote --tags https://github.com/krakjoe/apcu.git \ | cut -d/ -f3 \ @@ -82,6 +95,7 @@ variants=( apache fpm fpm-alpine + unit ) min_version='27' @@ -91,6 +105,13 @@ function version_greater_or_equal() { [[ "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1" || "$1" == "$2" ]]; } +# joins a list of strings together with a delimiter +# join_by delim first rest... +function join_by() { + local delim=${1-} first=${2-} + shift 2 && printf %s "${first}" "${@/#/$delim}" +} + function create_variant() { dir="$1/$variant" alpineVersion=${alpine_version[$version]-${alpine_version[default]}} @@ -118,12 +139,13 @@ function create_variant() { s/%%VERSION%%/'"$fullversion"'/g; s/%%DOWNLOAD_URL%%/'"$(sed -e 's/[\/&]/\\&/g' <<< "$url")"'/g; s/%%DOWNLOAD_URL_ASC%%/'"$(sed -e 's/[\/&]/\\&/g' <<< "$ascUrl")"'/g; - s/%%CMD%%/'"${cmd[$variant]}"'/g; + s/%%CMD%%/'"$(join_by '", "' ${cmd[$variant]})"'/g; s|%%VARIANT_EXTRAS%%|'"${extras[$variant]}"'|g; s/%%APCU_VERSION%%/'"${pecl_versions[APCu]}"'/g; s/%%MEMCACHED_VERSION%%/'"${pecl_versions[memcached]}"'/g; s/%%REDIS_VERSION%%/'"${pecl_versions[redis]}"'/g; s/%%IMAGICK_VERSION%%/'"${pecl_versions[imagick]}"'/g; + s/%%UNIT_VERSION%%/'"${unit_version}"'/g; s/%%CRONTAB_INT%%/'"$crontabInt"'/g; ' "$dir/Dockerfile" @@ -132,6 +154,11 @@ function create_variant() { cp "docker-$name.sh" "$dir/$name.sh" done + # Copy the nginx-unit configuration if unit variant. + if [ "$variant" == "unit" ]; then + cp nextcloud-unit.json "$dir/nextcloud-unit.json" + fi + # Copy the upgrade.exclude cp upgrade.exclude "$dir/"