Prechádzať zdrojové kódy

more docs and rework

Christian Winther 1 rok pred
rodič
commit
a08a5e7cde

+ 37 - 4
contrib/docker/Dockerfile

@@ -5,14 +5,29 @@
 # Configuration
 #######################################################
 
+# See: https://github.com/composer/composer/releases
 ARG COMPOSER_VERSION="2.6"
+
+# See: https://nginx.org/
 ARG NGINX_VERSION=1.25.3
+
+# See: https://github.com/ddollar/forego
 ARG FOREGO_VERSION=0.17.2
 
+# See: https://github.com/hairyhenderson/gomplate
+ARG GOMPLATE_VERSION=v3.11.6
+
+###
 # PHP base configuration
+###
+
+# See: https://hub.docker.com/_/php/tags
 ARG PHP_VERSION="8.1"
+
+# See: https://github.com/docker-library/docs/blob/master/php/README.md#image-variants
 ARG PHP_BASE_TYPE="apache"
 ARG PHP_DEBIAN_RELEASE="bullseye"
+
 ARG RUNTIME_UID=33 # often called 'www-data'
 ARG RUNTIME_GID=33 # often called 'www-data'
 
@@ -57,17 +72,31 @@ FROM nginx:${NGINX_VERSION} AS nginx-image
 # See: https://github.com/nginx-proxy/forego
 FROM nginxproxy/forego:${FOREGO_VERSION}-debian AS forego-image
 
+# gomplate-image grabs the gomplate binary from GitHub releases
+#
+# It's in its own layer so it can be fetched in parallel with other build steps
+FROM php:${PHP_VERSION}-${PHP_BASE_TYPE}-${PHP_DEBIAN_RELEASE} AS gomplate-image
+
+ARG BUILDARCH
+ARG BUILDOS
+ARG GOMPLATE_VERSION
+
+RUN set -ex \
+    && curl --silent --show-error --location --output /usr/local/bin/gomplate https://github.com/hairyhenderson/gomplate/releases/download/${GOMPLATE_VERSION}/gomplate_${BUILDOS}-${BUILDARCH} \
+    && chmod +x /usr/local/bin/gomplate \
+    && /usr/local/bin/gomplate --version
+
 #######################################################
 # Base image
 #######################################################
 
 FROM php:${PHP_VERSION}-${PHP_BASE_TYPE}-${PHP_DEBIAN_RELEASE} AS base
 
-ARG PHP_VERSION
-ARG PHP_DEBIAN_RELEASE
 ARG APT_PACKAGES_EXTRA
-ARG RUNTIME_UID
+ARG PHP_DEBIAN_RELEASE
+ARG PHP_VERSION
 ARG RUNTIME_GID
+ARG RUNTIME_UID
 
 ARG TARGETPLATFORM
 ARG BUILDKIT_SBOM_SCAN_STAGE=true
@@ -173,8 +202,11 @@ USER root:root
 
 FROM base AS shared-runtime
 
-ARG RUNTIME_UID
+ARG BUILDARCH
+ARG BUILDOS
+ARG GOMPLATE_VERSION
 ARG RUNTIME_GID
+ARG RUNTIME_UID
 
 ENV RUNTIME_UID=${RUNTIME_UID}
 ENV RUNTIME_GID=${RUNTIME_GID}
@@ -183,6 +215,7 @@ COPY --link --from=php-extensions /usr/local/lib/php/extensions /usr/local/lib/p
 COPY --link --from=php-extensions /usr/local/etc/php /usr/local/etc/php
 COPY --link --from=composer-and-src --chown=${RUNTIME_UID}:${RUNTIME_GID} /var/www /var/www
 COPY --link --from=forego-image /usr/local/bin/forego /usr/local/bin/forego
+COPY --link --from=gomplate-image /usr/local/bin/gomplate /usr/local/bin/gomplate
 
 # for detail why storage is copied this way, pls refer to https://github.com/pixelfed/pixelfed/pull/2137#discussion_r434468862
 RUN set -ex \

+ 66 - 0
contrib/docker/README.md

@@ -93,6 +93,72 @@ services:
      PHP_BASE_TYPE: fpm
 ```
 
+## Customizing your `Dockerfile`
+
+### Running commands on container start
+
+#### Description
+
+When a Pixelfed container starts up, the [`ENTRYPOINT`](https://docs.docker.com/engine/reference/builder/#entrypoint) script will
+
+1. Search the `/docker/entrypoint.d/` directory for files and for each file (in lexical order).
+1. Check if the file is executable.
+    1. If the file is not executable, print an error and exit the container.
+1. If the file has the extension `.envsh` the file will be [sourced](https://superuser.com/a/46146).
+1. If the file has the extension `.sh` the file will be run like a normal script.
+1. Any other file extension will log a warning and will be ignored.
+
+#### Included scripts
+
+* `/docker/entrypoint.d/04-defaults.envsh` calculates Docker container environment variables needed for [templating](#templating) configuration files.
+* `/docker/entrypoint.d/05-templating.sh` renders [template](#templating) configuration files.
+* `/docker/entrypoint.d/10-storage.sh` ensures Pixelfed storage related permissions and commands are run.
+* `/docker/entrypoint.d/20-horizon.sh` ensures [Laravel Horizon](https://laravel.com/docs/master/horizon) used by Pixelfed is configured
+* `/docker/entrypoint.d/30-cache.sh` ensures all Pixelfed caches (router, view, config) is warmed
+
+#### Disabling entrypoint or individual scripts
+
+To disable the entire entrypoint you can set the variable `ENTRYPOINT_SKIP=1`.
+
+To disable individual entrypoint scripts you can add the filename to the space (`" "`) separated variable `ENTRYPOINT_SKIP_SCRIPTS`. (example: `ENTRYPOINT_SKIP_SCRIPTS="10-storage.sh 30-cache.sh"`)
+
+### Templating
+
+The Docker container can do some basic templating (more like variable replacement) as part of the entrypoint scripts via [gomplate](https://docs.gomplate.ca/).
+
+Any file put in the `/docker/templates/` directory will be templated and written to the right directory.
+
+#### File path examples
+
+1. To template `/usr/local/etc/php/php.ini` in the container put the source file in `/docker/templates/usr/local/etc/php/php.ini`.
+1. To template `/a/fantastic/example.txt` in the container put the source file in `/docker/templates/a/fantastic/example.txt`.
+1. To template `/some/path/anywhere` in the container put the source file in `/docker/templates/a/fantastic/example.txt`.
+
+#### Available variables
+
+Variables available for templating are sourced (in order, so *last* source takes precedence) like this:
+
+1. `env:` in your `docker-compose.yml` or `-e` in your `docker run` / `docker compose run`
+1. Any exported variables in `.envsh` files loaded *before* `05-templating.sh` (e.g. any file with `04-`, `03-`, `02-`, `01-` or `00-` prefix)
+1. All key/value pairs in `/var/www/.env.docker`
+1. All key/value pairs in `/var/www/.env`
+
+#### Template guide 101
+
+Please see the [gomplate documentation](https://docs.gomplate.ca/) for a more comprehensive overview.
+
+The most frequent use-case you have is likely to print a environment variable (or a default value if it's missing), so this is how to do that:
+
+* `{{ getenv "VAR_NAME" }}` print an environment variable and **fail** if the variable is not set. ([docs](https://docs.gomplate.ca/functions/env/#envgetenv))
+* `{{ getenv "VAR_NAME" "default" }}` print an environment variable and print `default` if the variable is not set. ([docs](https://docs.gomplate.ca/functions/env/#envgetenv))
+
+The script will *fail* if you reference a variable that does not exist (and don't have a default value) in a template.
+
+Please see the
+
+* [gomplate syntax documentation](https://docs.gomplate.ca/syntax/)
+* [gomplate functions documentation](https://docs.gomplate.ca/functions/)
+
 ## Build settings (arguments)
 
 The Pixelfed Dockerfile utilizes [Docker Multi-stage builds](https://docs.docker.com/build/building/multi-stage/) and [Build arguments](https://docs.docker.com/build/guide/build-args/).

+ 2 - 2
contrib/docker/nginx/root/etc/nginx/conf.d/default.conf → contrib/docker/nginx/root/docker/templates/conf.d/default.conf

@@ -1,7 +1,7 @@
 server {
     listen 80 default_server;
 
-    server_name ${APP_DOMAIN};
+    server_name {{ getenv "APP_DOMAIN" }};
     root /var/www/public;
 
     add_header X-Frame-Options "SAMEORIGIN";
@@ -14,7 +14,7 @@ server {
     index index.html index.htm index.php;
 
     charset utf-8;
-    client_max_body_size ${POST_MAX_SIZE};
+    client_max_body_size {{ getenv "POST_MAX_SIZE" }};
 
     location / {
         try_files $uri $uri/ /index.php?$query_string;

+ 26 - 27
contrib/docker/shared/root/docker/entrypoint.d/05-templating.sh

@@ -3,38 +3,37 @@ source /docker/helpers.sh
 
 set_identity "$0"
 
-auto_envsubst() {
-    local template_dir="${ENVSUBST_TEMPLATE_DIR:-/docker/templates}"
-    local output_dir="${ENVSUBST_OUTPUT_DIR:-}"
-    local filter="${ENVSUBST_FILTER:-}"
-    local template defined_envs relative_path output_path output_dir subdir
+declare template_dir="${ENVSUBST_TEMPLATE_DIR:-/docker/templates}"
+declare output_dir="${ENVSUBST_OUTPUT_DIR:-}"
+declare filter="${ENVSUBST_FILTER:-}"
+declare template defined_envs relative_path output_path output_dir subdir
 
-    # load all dot-env files
-    load-config-files
+# load all dot-env files
+load-config-files
 
-    # export all dot-env variables so they are available in templating
-    export ${seen_dot_env_variables[@]}
+: ${ENTRYPOINT_SHOW_TEMPLATE_DIFF:=1}
 
-    defined_envs=$(printf '${%s} ' $(awk "END { for (name in ENVIRON) { print ( name ~ /${filter}/ ) ? name : \"\" } }" </dev/null))
+# export all dot-env variables so they are available in templating
+export ${seen_dot_env_variables[@]}
 
-    find "$template_dir" -follow -type f -print | while read -r template; do
-        relative_path="${template#"$template_dir/"}"
-        subdir=$(dirname "$relative_path")
-        output_path="$output_dir/${relative_path}"
-        output_dir=$(dirname "$output_path")
+find "$template_dir" -follow -type f -print | while read -r template; do
+    relative_path="${template#"$template_dir/"}"
+    subdir=$(dirname "$relative_path")
+    output_path="$output_dir/${relative_path}"
+    output_dir=$(dirname "$output_path")
 
-        if [ ! -w "$output_dir" ]; then
-            log_error_and_exit "ERROR: $template_dir exists, but $output_dir is not writable"
-        fi
+    if [ ! -w "$output_dir" ]; then
+        log_error_and_exit "ERROR: $template_dir exists, but $output_dir is not writable"
+    fi
 
-        # create a subdirectory where the template file exists
-        mkdir -p "$output_dir/$subdir"
+    # create a subdirectory where the template file exists
+    mkdir -p "$output_dir/$subdir"
 
-        log "Running envsubst on $template to $output_path"
-        envsubst "$defined_envs" <"$template" >"$output_path"
-    done
-}
+    log "Running [gomplate] on [$template] --> [$output_path]"
+    cat "$template" | gomplate >"$output_path"
 
-auto_envsubst
-
-exit 0
+    # Show the diff from the envsubst command
+    if [[ ${ENTRYPOINT_SHOW_TEMPLATE_DIFF} = 1 ]]; then
+        git --no-pager diff "$template" "${output_path}" || :
+    fi
+done

+ 0 - 3
contrib/docker/shared/root/docker/entrypoint.d/10-storage.sh

@@ -3,10 +3,7 @@ source /docker/helpers.sh
 
 set_identity "$0"
 
-log "Create the storage tree if needed"
 as_runtime_user cp --recursive storage.skel/* storage/
-
-log "Ensure storage is linked"
 as_runtime_user php artisan storage:link
 
 log "Ensure permissions are correct"

+ 0 - 5
contrib/docker/shared/root/docker/entrypoint.d/30-cache.sh

@@ -3,11 +3,6 @@ source /docker/helpers.sh
 
 set_identity "$0"
 
-log "==> route:cache"
 as_runtime_user php artisan route:cache
-
-log "==> view:cache"
 as_runtime_user php artisan view:cache
-
-log "==> config:cache"
 as_runtime_user php artisan config:cache

+ 62 - 41
contrib/docker/shared/root/docker/entrypoint.sh

@@ -1,50 +1,71 @@
 #!/bin/bash
 set -e -o errexit -o nounset -o pipefail
 
-[[ -n ${ENTRYPOINT_DEBUG:-} ]] && set -x
-
-declare -g ME="$0"
-declare -gr ENTRYPOINT_ROOT=/docker/entrypoint.d/
-
-source /docker/helpers.sh
-
-# ensure the entrypoint folder exists
-mkdir -p "${ENTRYPOINT_ROOT}"
-
-if /usr/bin/find "${ENTRYPOINT_ROOT}" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then
-    log "looking for shell scripts in /docker/entrypoint.d/"
-    find "${ENTRYPOINT_ROOT}" -follow -type f -print | sort -V | while read -r f; do
-        case "$f" in
-        *.envsh)
-            if [ -x "$f" ]; then
-                log "Sourcing $f"
-                source "$f"
-                resetore_identity
-            else
-                # warn on shell scripts without exec bit
-                log_warning "Ignoring $f, not executable"
-            fi
-            ;;
-
-        *.sh)
-            if [ -x "$f" ]; then
-                log "Launching $f"
-                "$f"
-            else
-                # warn on shell scripts without exec bit
-                log_warning "Ignoring $f, not executable"
+: ${ENTRYPOINT_SKIP:=0}
+: ${ENTRYPOINT_SKIP_SCRIPTS:=""}
+: ${ENTRYPOINT_DEBUG:=0}
+: ${ENTRYPOINT_ROOT:="/docker/entrypoint.d/"}
+
+export ENTRYPOINT_ROOT
+
+if [[ ${ENTRYPOINT_SKIP} == 0 ]]; then
+    [[ ${ENTRYPOINT_DEBUG} == 1 ]] && set -x
+
+    source /docker/helpers.sh
+
+    declare -a skip_scripts=()
+    IFS=' ' read -a skip_scripts <<<"$ENTRYPOINT_SKIP_SCRIPTS"
+
+    declare script_name
+
+    # ensure the entrypoint folder exists
+    mkdir -p "${ENTRYPOINT_ROOT}"
+
+    if /usr/bin/find "${ENTRYPOINT_ROOT}" -mindepth 1 -maxdepth 1 -type f -print -quit 2>/dev/null | read v; then
+        log "looking for shell scripts in /docker/entrypoint.d/"
+
+        find "${ENTRYPOINT_ROOT}" -follow -type f -print | sort -V | while read -r f; do
+            script_name="$(get_script_name $f)"
+            if array_value_exists skip_scripts "${script_name}"; then
+                log_warning "Skipping script [${script_name}] since it's in the skip list (\$ENTRYPOINT_SKIP_SCRIPTS)"
+
+                continue
             fi
-            ;;
 
-        *)
-            log_warning "Ignoring $f"
-            ;;
-        esac
-    done
+            case "$f" in
+            *.envsh)
+                if [ -x "$f" ]; then
+                    log "Sourcing $f"
+
+                    source "$f"
+
+                    resetore_identity
+                else
+                    # warn on shell scripts without exec bit
+                    log_error_and_exit "File [$f] is not executable (please 'chmod +x' it)"
+                fi
+                ;;
+
+            *.sh)
+                if [ -x "$f" ]; then
+                    log "Launching $f"
+                    "$f"
+                else
+                    # warn on shell scripts without exec bit
+                    log_error_and_exit "File [$f] is not executable (please 'chmod +x' it)"
+                fi
+                ;;
+
+            *)
+                log_warning "Ignoring $f"
+                ;;
+            esac
+        done
 
-    log "Configuration complete; ready for start up"
-else
-    log_warning "No files found in ${ENTRYPOINT_ROOT}, skipping configuration"
+        log "Configuration complete; ready for start up"
+    else
+        log_warning "No files found in ${ENTRYPOINT_ROOT}, skipping configuration"
+    fi
 fi
 
 exec "$@"

+ 31 - 4
contrib/docker/shared/root/docker/helpers.sh

@@ -10,11 +10,11 @@ declare -ra dot_env_files=(
     /var/www/.env.docker
     /var/www/.env
 )
-declare -a seen_dot_env_variables=()
+declare -ga seen_dot_env_variables=()
 
 function set_identity() {
     old_log_prefix="${log_prefix}"
-    log_prefix="ENTRYPOINT - [${1}] - "
+    log_prefix="ENTRYPOINT - [$(get_script_name $1)] - "
 }
 
 function resetore_identity() {
@@ -22,7 +22,23 @@ function resetore_identity() {
 }
 
 function as_runtime_user() {
-    su --preserve-environment $(id -un ${RUNTIME_UID}) --shell /bin/bash --command "${*}"
+    local -i exit_code
+    local target_user
+
+    target_user=$(id -un ${RUNTIME_UID})
+
+    log "👷 Running [${*}] as [${target_user}]"
+
+    su --preserve-environment "${target_user}" --shell /bin/bash --command "${*}"
+    exit_code=$?
+
+    if [[ $exit_code != 0 ]]; then
+        log_error "❌ Error!"
+        return $exit_code
+    fi
+
+    log "✅ OK!"
+    return $exit_code
 }
 
 # @description Display the given error message with its line number on stderr and exit with error.
@@ -53,7 +69,7 @@ function log() {
 }
 
 function load-config-files() {
-    # Associative array (aka map/disctrionary) holding the unique keys found in dot-env files
+    # Associative array (aka map/dictionary) holding the unique keys found in dot-env files
     local -A _tmp_dot_env_keys
 
     for f in "${dot_env_files[@]}"; do
@@ -73,3 +89,14 @@ function load-config-files() {
 
     seen_dot_env_variables=(${!_tmp_dot_env_keys[@]})
 }
+
+function array_value_exists() {
+    local -nr validOptions=$1
+    local -r providedValue="\<${2}\>"
+
+    [[ ${validOptions[*]} =~ $providedValue ]]
+}
+
+function get_script_name() {
+    echo "${1#"$ENTRYPOINT_ROOT"}"
+}

+ 3 - 2
contrib/docker/shared/root/docker/install/base.sh

@@ -15,7 +15,7 @@ echo 'APT::Install-Suggests "false";' >>/etc/apt/apt.conf
 declare -ra standardPackages=(
     apt-utils
     ca-certificates
-    gettext-base
+    curl
     git
     gnupg1
     gosu
@@ -25,9 +25,10 @@ declare -ra standardPackages=(
     locales-all
     nano
     procps
+    software-properties-common
     unzip
+    wget
     zip
-    software-properties-common
 )
 
 # Image Optimization

+ 3 - 3
contrib/docker/shared/root/docker/templates/usr/local/etc/php/php.ini

@@ -679,7 +679,7 @@ auto_globals_jit = On
 ; Its value may be 0 to disable the limit. It is ignored if POST data reading
 ; is disabled through enable_post_data_reading.
 ; http://php.net/post-max-size
-post_max_size = ${POST_MAX_SIZE}
+post_max_size = {{ getenv "POST_MAX_SIZE" }}
 
 ; Automatically add files before PHP document.
 ; http://php.net/auto-prepend-file
@@ -831,10 +831,10 @@ file_uploads = On
 
 ; Maximum allowed size for uploaded files.
 ; http://php.net/upload-max-filesize
-upload_max_filesize = ${POST_MAX_SIZE}
+upload_max_filesize = {{ getenv "POST_MAX_SIZE" }}
 
 ; Maximum number of files that can be uploaded via a single request
-max_file_uploads = ${MAX_ALBUM_LENGTH}
+max_file_uploads = {{ getenv "MAX_ALBUM_LENGTH" }}
 
 ;;;;;;;;;;;;;;;;;;
 ; Fopen wrappers ;