diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 05e852b..2584d36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,22 @@ jobs: run: | strategy="$("$BASHBREW_SCRIPTS/github-actions/generate.sh")" strategy="$("$BASHBREW_SCRIPTS/github-actions/munge-i386.sh" -c <<<"$strategy")" - strategy="$(jq -c <<<"$strategy" '.matrix.include = [ .matrix.include[] | select(.name | split("-") | (.[0] | split(".")[0:2] | join(".") | tonumber >= 6.1) and ( .[1] // "" | test("bookworm.*i386")) | not) ]')" # https://github.com/docker-library/redmine/pull/393 (6.1+ deps need newer rust on other arches and just isn't worth the trouble) + # https://github.com/docker-library/redmine/pull/393 (6.1+ deps need newer rust on other arches and just isn't worth the trouble) + # https://github.com/docker-library/redmine/pull/424 (7.0+ rust deps of magnus gem don't compile on Alpine i386) + strategy="$( + jq -c <<<"$strategy" ' + .matrix.include |= map( + select( + .name + | split("-") + | (.[0] | split(".")[0:2] | join(".") | tonumber) as $version + | ($version >= 6.1 and (.[1] // "" | test("bookworm.*i386"))) + or ($version >= 7.0 and (.[1] // "" | test("alpine.*i386"))) + | not + ) + ) + ' + )" echo "strategy=$strategy" >> "$GITHUB_OUTPUT" jq . <<<"$strategy" # sanity check / debugging aid diff --git a/6.0/alpine3.23/Dockerfile b/6.0/alpine3.23/Dockerfile index cd21891..6cd0fb0 100644 --- a/6.0/alpine3.23/Dockerfile +++ b/6.0/alpine3.23/Dockerfile @@ -125,13 +125,42 @@ RUN set -eux; \ rm /usr/local/bundle/gems/rbpdf-font-1.19.*/lib/fonts/ttf2ufm/ttf2ufm; \ \ runDeps="$( \ - scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/bundle/gems \ - | tr ',' '\n' \ + scanelf --needed --nobanner --rpath --format '%n|%r|%F' --recursive /usr/local/bundle/gems \ + | awk -F '|' '{ \ + split($1, libs, ","); \ + split($2, rpaths, ":"); \ + \ + # $3 is the full file path. Calculate $ORIGIN by stripping the filename. + origin = $3; \ + sub("/[^/]+$", "", origin); \ + \ + for (i in libs) { \ + lib = libs[i]; \ + \ + # 1. Skip if it exists in /usr/local/lib + if (!system("[ -e \"/usr/local/lib/" lib "\" ]")) continue; \ + \ + # 2. Check if satisfied by a bundled (relative) rpath + found = 0; \ + for (j in rpaths) { \ + rpath = rpaths[j]; \ + \ + # Only resolve $ORIGIN-relative rpaths. + if (sub(/^\$[{]?ORIGIN[}]?/, origin, rpath)) { \ + if (!system("[ -e \"" rpath "/" lib "\" ]")) { \ + found = 1; \ + break; \ + } \ + } \ + } \ + \ + # 3. If missing globally and not bundled locally, keep it as a system dep + if (!found) { \ + print "so:" lib; \ + } \ + } \ + }' \ | sort -u \ - | awk ' \ - system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } \ - { print "so:" $1 } \ - ' \ )"; \ apk add --no-network --virtual .redmine-rundeps $runDeps; \ apk del --no-network .build-deps; \ diff --git a/6.0/alpine3.24/Dockerfile b/6.0/alpine3.24/Dockerfile index 6be834f..cad6924 100644 --- a/6.0/alpine3.24/Dockerfile +++ b/6.0/alpine3.24/Dockerfile @@ -125,13 +125,42 @@ RUN set -eux; \ rm /usr/local/bundle/gems/rbpdf-font-1.19.*/lib/fonts/ttf2ufm/ttf2ufm; \ \ runDeps="$( \ - scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/bundle/gems \ - | tr ',' '\n' \ + scanelf --needed --nobanner --rpath --format '%n|%r|%F' --recursive /usr/local/bundle/gems \ + | awk -F '|' '{ \ + split($1, libs, ","); \ + split($2, rpaths, ":"); \ + \ + # $3 is the full file path. Calculate $ORIGIN by stripping the filename. + origin = $3; \ + sub("/[^/]+$", "", origin); \ + \ + for (i in libs) { \ + lib = libs[i]; \ + \ + # 1. Skip if it exists in /usr/local/lib + if (!system("[ -e \"/usr/local/lib/" lib "\" ]")) continue; \ + \ + # 2. Check if satisfied by a bundled (relative) rpath + found = 0; \ + for (j in rpaths) { \ + rpath = rpaths[j]; \ + \ + # Only resolve $ORIGIN-relative rpaths. + if (sub(/^\$[{]?ORIGIN[}]?/, origin, rpath)) { \ + if (!system("[ -e \"" rpath "/" lib "\" ]")) { \ + found = 1; \ + break; \ + } \ + } \ + } \ + \ + # 3. If missing globally and not bundled locally, keep it as a system dep + if (!found) { \ + print "so:" lib; \ + } \ + } \ + }' \ | sort -u \ - | awk ' \ - system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } \ - { print "so:" $1 } \ - ' \ )"; \ apk add --no-network --virtual .redmine-rundeps $runDeps; \ apk del --no-network .build-deps; \ diff --git a/6.1/alpine3.23/Dockerfile b/6.1/alpine3.23/Dockerfile index e53ffad..be7261f 100644 --- a/6.1/alpine3.23/Dockerfile +++ b/6.1/alpine3.23/Dockerfile @@ -122,14 +122,43 @@ RUN set -eux; \ rm /usr/local/bundle/gems/rbpdf-font-1.19.*/lib/fonts/ttf2ufm/ttf2ufm; \ \ runDeps="$( \ - scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/bundle/gems \ - | tr ',' '\n' \ + scanelf --needed --nobanner --rpath --format '%n|%r|%F' --recursive /usr/local/bundle/gems \ + | awk -F '|' '{ \ + split($1, libs, ","); \ + split($2, rpaths, ":"); \ + \ + # $3 is the full file path. Calculate $ORIGIN by stripping the filename. + origin = $3; \ + sub("/[^/]+$", "", origin); \ + \ + for (i in libs) { \ + lib = libs[i]; \ + if (lib == "libc.so") continue; \ + \ + # 1. Skip if it exists in /usr/local/lib + if (!system("[ -e \"/usr/local/lib/" lib "\" ]")) continue; \ + \ + # 2. Check if satisfied by a bundled (relative) rpath + found = 0; \ + for (j in rpaths) { \ + rpath = rpaths[j]; \ + \ + # Only resolve $ORIGIN-relative rpaths. + if (sub(/^\$[{]?ORIGIN[}]?/, origin, rpath)) { \ + if (!system("[ -e \"" rpath "/" lib "\" ]")) { \ + found = 1; \ + break; \ + } \ + } \ + } \ + \ + # 3. If missing globally and not bundled locally, keep it as a system dep + if (!found) { \ + print "so:" lib; \ + } \ + } \ + }' \ | sort -u \ - | awk ' \ - $1 == "libc.so" { next } \ - system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } \ - { print "so:" $1 } \ - ' \ )"; \ apk add --no-network --virtual .redmine-rundeps $runDeps; \ apk del --no-network .build-deps; \ diff --git a/6.1/alpine3.24/Dockerfile b/6.1/alpine3.24/Dockerfile index 3bd914b..13c25f5 100644 --- a/6.1/alpine3.24/Dockerfile +++ b/6.1/alpine3.24/Dockerfile @@ -122,14 +122,43 @@ RUN set -eux; \ rm /usr/local/bundle/gems/rbpdf-font-1.19.*/lib/fonts/ttf2ufm/ttf2ufm; \ \ runDeps="$( \ - scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/bundle/gems \ - | tr ',' '\n' \ + scanelf --needed --nobanner --rpath --format '%n|%r|%F' --recursive /usr/local/bundle/gems \ + | awk -F '|' '{ \ + split($1, libs, ","); \ + split($2, rpaths, ":"); \ + \ + # $3 is the full file path. Calculate $ORIGIN by stripping the filename. + origin = $3; \ + sub("/[^/]+$", "", origin); \ + \ + for (i in libs) { \ + lib = libs[i]; \ + if (lib == "libc.so") continue; \ + \ + # 1. Skip if it exists in /usr/local/lib + if (!system("[ -e \"/usr/local/lib/" lib "\" ]")) continue; \ + \ + # 2. Check if satisfied by a bundled (relative) rpath + found = 0; \ + for (j in rpaths) { \ + rpath = rpaths[j]; \ + \ + # Only resolve $ORIGIN-relative rpaths. + if (sub(/^\$[{]?ORIGIN[}]?/, origin, rpath)) { \ + if (!system("[ -e \"" rpath "/" lib "\" ]")) { \ + found = 1; \ + break; \ + } \ + } \ + } \ + \ + # 3. If missing globally and not bundled locally, keep it as a system dep + if (!found) { \ + print "so:" lib; \ + } \ + } \ + }' \ | sort -u \ - | awk ' \ - $1 == "libc.so" { next } \ - system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } \ - { print "so:" $1 } \ - ' \ )"; \ apk add --no-network --virtual .redmine-rundeps $runDeps; \ apk del --no-network .build-deps; \ diff --git a/6.1/bookworm/Dockerfile b/6.1/bookworm/Dockerfile index 543087f..210257f 100644 --- a/6.1/bookworm/Dockerfile +++ b/6.1/bookworm/Dockerfile @@ -93,11 +93,9 @@ RUN set -eux; \ savedAptMark="$(apt-mark showmanual)"; \ apt-get update; \ apt-get install -y --no-install-recommends \ - cargo \ default-libmysqlclient-dev \ freetds-dev \ gcc \ - libclang-dev \ libpq-dev \ libsqlite3-dev \ libxml2-dev \ diff --git a/7.0/alpine3.23/Dockerfile b/7.0/alpine3.23/Dockerfile new file mode 100644 index 0000000..9511b44 --- /dev/null +++ b/7.0/alpine3.23/Dockerfile @@ -0,0 +1,174 @@ +# +# NOTE: THIS DOCKERFILE IS GENERATED VIA "apply-templates.sh" +# +# PLEASE DO NOT EDIT IT DIRECTLY. +# + +FROM ruby:4.0-alpine3.23 + +# explicitly set uid/gid to guarantee that it won't change in the future +# the values 999:999 are identical to the current user/group id assigned +# alpine already has a gid 999, so we'll use the next id +RUN addgroup -S -g 1000 redmine && adduser -S -H -G redmine -u 999 redmine + +RUN set -eux; \ + apk add --no-cache \ + bash \ + breezy \ + ca-certificates \ + findutils \ + ghostscript \ + ghostscript-fonts \ + git \ + imagemagick \ + mercurial \ + openssh-client \ + subversion \ + tini \ + tzdata \ + wget \ + ; + +# grab gosu for easy step-down from root +# https://github.com/tianon/gosu/releases +ENV GOSU_VERSION 1.19 +RUN set -eux; \ + \ + apk add --no-cache --virtual .gosu-deps \ + dpkg \ + gnupg \ + ; \ + \ + dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \ + wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \ + wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \ + export GNUPGHOME="$(mktemp -d)"; \ + gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \ + gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \ + gpgconf --kill all; \ + rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \ + \ + apk del --no-network .gosu-deps; \ + \ +# smoke test + chmod +x /usr/local/bin/gosu; \ + gosu --version; \ + gosu nobody true + +ENV RAILS_ENV production +WORKDIR /usr/src/redmine + +# https://github.com/docker-library/redmine/issues/138#issuecomment-438834176 +# (bundler needs this for running as an arbitrary user) +ENV HOME /home/redmine +RUN set -eux; \ + [ ! -d "$HOME" ]; \ + mkdir -p "$HOME"; \ + chown redmine:redmine "$HOME"; \ + chmod 1777 "$HOME" + +ENV REDMINE_VERSION 7.0.0 +ENV REDMINE_DOWNLOAD_URL https://www.redmine.org/releases/redmine-7.0.0.tar.gz +ENV REDMINE_DOWNLOAD_SHA256 857e9f8860c31e4c531389e5d93eea26488dba69830484a3b0aa904be615e90a +ENV RAILS_LOG_TO_STDOUT true + +RUN set -eux; \ + wget -O redmine.tar.gz "$REDMINE_DOWNLOAD_URL"; \ + echo "$REDMINE_DOWNLOAD_SHA256 *redmine.tar.gz" | sha256sum -c -; \ + tar -xf redmine.tar.gz --strip-components=1; \ + rm redmine.tar.gz files/delete.me log/delete.me; \ + # https://www.redmine.org/projects/redmine/wiki/RedmineInstall#Step-8-File-system-permissions + set -- 'config' 'db' 'log' 'public/assets' 'sqlite' 'tmp' 'tmp/pdf' 'tmp/pids'; \ + mkdir -p "$@"; \ + chown -R redmine:redmine ./; \ +# fix permissions for running as an arbitrary user + chmod -R ugo=rwX "$@"; \ + find "$@" -type d -exec chmod 1777 '{}' + + +RUN set -eux; \ + apk add --no-cache --virtual .build-deps \ + cargo \ + clang21-dev \ + coreutils \ + freetds-dev \ + gcc \ + make \ + mariadb-dev \ + musl-dev \ + patch \ + postgresql-dev \ + yaml-dev \ + ; \ + \ + gosu redmine bundle config --local without 'development test'; \ +# https://github.com/redmine/redmine/commit/23dc108e70a0794f444803ac827a690085dcd557 +# ("gem puma" already exists in the Gemfile, but under "group :test" and we want it all the time) + puma="$(grep -E "^[[:space:]]*gem [:'\"]puma['\",[:space:]].*\$" Gemfile)"; \ + { echo; echo "$puma"; } | sed -re 's/^[[:space:]]+//' >> Gemfile; \ +# fill up "database.yml" with bogus entries so the redmine Gemfile will pre-install all database adapter dependencies +# https://github.com/redmine/redmine/blob/e9f9767089a4e3efbd73c35fc55c5c7eb85dd7d3/Gemfile#L50-L79 + echo '# the following entries only exist to force `bundle install` to pre-install all database adapter dependencies -- they can be safely removed/ignored' > ./config/database.yml; \ + for adapter in mysql2 postgresql sqlserver sqlite3; do \ + echo "$adapter:" >> ./config/database.yml; \ + echo " adapter: $adapter" >> ./config/database.yml; \ + done; \ + gosu redmine bundle install --jobs "$(nproc)"; \ + rm ./config/database.yml; \ +# fix permissions for running as an arbitrary user + chmod -R ugo=rwX Gemfile.lock "$GEM_HOME"; \ + rm -rf ~redmine/.bundle; \ + \ +# https://github.com/naitoh/rbpdf/issues/31 + rm /usr/local/bundle/gems/rbpdf-font-1.19.*/lib/fonts/ttf2ufm/ttf2ufm; \ + \ + runDeps="$( \ + scanelf --needed --nobanner --rpath --format '%n|%r|%F' --recursive /usr/local/bundle/gems \ + | awk -F '|' '{ \ + split($1, libs, ","); \ + split($2, rpaths, ":"); \ + \ + # $3 is the full file path. Calculate $ORIGIN by stripping the filename. + origin = $3; \ + sub("/[^/]+$", "", origin); \ + \ + for (i in libs) { \ + lib = libs[i]; \ + if (lib == "libc.so") continue; \ + \ + # 1. Skip if it exists in /usr/local/lib + if (!system("[ -e \"/usr/local/lib/" lib "\" ]")) continue; \ + \ + # 2. Check if satisfied by a bundled (relative) rpath + found = 0; \ + for (j in rpaths) { \ + rpath = rpaths[j]; \ + \ + # Only resolve $ORIGIN-relative rpaths. + if (sub(/^\$[{]?ORIGIN[}]?/, origin, rpath)) { \ + if (!system("[ -e \"" rpath "/" lib "\" ]")) { \ + found = 1; \ + break; \ + } \ + } \ + } \ + \ + # 3. If missing globally and not bundled locally, keep it as a system dep + if (!found) { \ + print "so:" lib; \ + } \ + } \ + }' \ + | sort -u \ + )"; \ + apk add --no-network --virtual .redmine-rundeps $runDeps; \ + apk del --no-network .build-deps; \ + # verify Kyiv timezone isn't missing + gosu redmine bundle exec rake time:zones:all | grep -q 'Kyiv' + +VOLUME /usr/src/redmine/files + +COPY docker-entrypoint.sh / +ENTRYPOINT ["/docker-entrypoint.sh"] + +EXPOSE 3000 +CMD ["rails", "server", "-b", "0.0.0.0"] diff --git a/7.0/alpine3.23/docker-entrypoint.sh b/7.0/alpine3.23/docker-entrypoint.sh new file mode 100755 index 0000000..fc68a28 --- /dev/null +++ b/7.0/alpine3.23/docker-entrypoint.sh @@ -0,0 +1,180 @@ +#!/usr/bin/env bash +set -Eeo pipefail +# TODO add "-u" + +# usage: file_env VAR [DEFAULT] +# ie: file_env 'XYZ_DB_PASSWORD' 'example' +# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of +# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) +file_env() { + local var="$1" + local fileVar="${var}_FILE" + local def="${2:-}" + if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then + echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + exit 1 + fi + local val="$def" + if [ "${!var:-}" ]; then + val="${!var}" + elif [ "${!fileVar:-}" ]; then + val="$(< "${!fileVar}")" + fi + export "$var"="$val" + unset "$fileVar" +} + +isLikelyRedmine= +case "$1" in + rails | rake ) isLikelyRedmine=1 ;; + bundle ) if [ "${2:-}" = 'exec' ]; then isLikelyRedmine=1; fi ;; # https://github.com/docker-library/redmine/pull/386 - "bundle exec sidekiq" +esac + +_fix_permissions() { + # https://www.redmine.org/projects/redmine/wiki/RedmineInstall#Step-8-File-system-permissions + local dirs=( config log public/*assets tmp ) args=() + if [ "$(id -u)" = '0' ]; then + args+=( ${args[@]:+,} '(' '!' -user redmine -exec chown redmine:redmine '{}' + ')' ) + + # https://github.com/docker-library/redmine/issues/268 - scanning "files" might be *really* expensive, so we should skip it if it seems like it's "already correct" + local filesOwnerMode + filesOwnerMode="$(stat -c '%U:%a' files)" + if [ "$filesOwnerMode" != 'redmine:755' ]; then + dirs+=( files ) + fi + fi + # directories 755, files 644: + args+=( ${args[@]:+,} '(' -type d '!' -perm 755 -exec sh -c 'chmod 755 "$@" 2>/dev/null || :' -- '{}' + ')' ) + args+=( ${args[@]:+,} '(' -type f '!' -perm 644 -exec sh -c 'chmod 644 "$@" 2>/dev/null || :' -- '{}' + ')' ) + find "${dirs[@]}" "${args[@]}" +} + +# allow the container to be started with `--user` +if [ -n "$isLikelyRedmine" ] && [ "$(id -u)" = '0' ]; then + _fix_permissions + exec gosu redmine "$BASH_SOURCE" "$@" +fi + +if [ -n "$isLikelyRedmine" ]; then + _fix_permissions + if [ ! -f './config/database.yml' ]; then + file_env 'REDMINE_DB_MYSQL' + file_env 'REDMINE_DB_POSTGRES' + file_env 'REDMINE_DB_SQLSERVER' + + if [ "$MYSQL_PORT_3306_TCP" ] && [ -z "$REDMINE_DB_MYSQL" ]; then + export REDMINE_DB_MYSQL='mysql' + elif [ "$POSTGRES_PORT_5432_TCP" ] && [ -z "$REDMINE_DB_POSTGRES" ]; then + export REDMINE_DB_POSTGRES='postgres' + fi + + if [ "$REDMINE_DB_MYSQL" ]; then + adapter='mysql2' + host="$REDMINE_DB_MYSQL" + file_env 'REDMINE_DB_PORT' '3306' + file_env 'REDMINE_DB_USERNAME' "${MYSQL_ENV_MYSQL_USER:-root}" + file_env 'REDMINE_DB_PASSWORD' "${MYSQL_ENV_MYSQL_PASSWORD:-${MYSQL_ENV_MYSQL_ROOT_PASSWORD:-}}" + file_env 'REDMINE_DB_DATABASE' "${MYSQL_ENV_MYSQL_DATABASE:-${MYSQL_ENV_MYSQL_USER:-redmine}}" + file_env 'REDMINE_DB_ENCODING' '' + elif [ "$REDMINE_DB_POSTGRES" ]; then + adapter='postgresql' + host="$REDMINE_DB_POSTGRES" + file_env 'REDMINE_DB_PORT' '5432' + file_env 'REDMINE_DB_USERNAME' "${POSTGRES_ENV_POSTGRES_USER:-postgres}" + file_env 'REDMINE_DB_PASSWORD' "${POSTGRES_ENV_POSTGRES_PASSWORD}" + file_env 'REDMINE_DB_DATABASE' "${POSTGRES_ENV_POSTGRES_DB:-${REDMINE_DB_USERNAME:-}}" + file_env 'REDMINE_DB_ENCODING' 'utf8' + elif [ "$REDMINE_DB_SQLSERVER" ]; then + adapter='sqlserver' + host="$REDMINE_DB_SQLSERVER" + file_env 'REDMINE_DB_PORT' '1433' + file_env 'REDMINE_DB_USERNAME' '' + file_env 'REDMINE_DB_PASSWORD' '' + file_env 'REDMINE_DB_DATABASE' '' + file_env 'REDMINE_DB_ENCODING' '' + else + echo >&2 + echo >&2 'warning: missing REDMINE_DB_MYSQL, REDMINE_DB_POSTGRES, or REDMINE_DB_SQLSERVER environment variables' + echo >&2 + echo >&2 '*** Using sqlite3 as fallback. ***' + echo >&2 + + adapter='sqlite3' + host='localhost' + file_env 'REDMINE_DB_PORT' '' + file_env 'REDMINE_DB_USERNAME' 'redmine' + file_env 'REDMINE_DB_PASSWORD' '' + file_env 'REDMINE_DB_DATABASE' 'sqlite/redmine.db' + file_env 'REDMINE_DB_ENCODING' 'utf8' + + mkdir -p "$(dirname "$REDMINE_DB_DATABASE")" + if [ "$(id -u)" = '0' ]; then + find "$(dirname "$REDMINE_DB_DATABASE")" \! -user redmine -exec chown redmine '{}' + + fi + fi + + REDMINE_DB_ADAPTER="$adapter" + REDMINE_DB_HOST="$host" + echo "$RAILS_ENV:" > config/database.yml + for var in \ + adapter \ + host \ + port \ + username \ + password \ + database \ + encoding \ + ; do + env="REDMINE_DB_${var^^}" + val="${!env}" + [ -n "$val" ] || continue + if [ "$var" != 'adapter' ]; then + # https://github.com/docker-library/redmine/issues/353 🙃 + val='"'"$val"'"' + # (only add double quotes to every value *except* `adapter: xxx`) + fi + echo " $var: $val" >> config/database.yml + done + fi + + # install additional gems for Gemfile.local and plugins + bundle check || bundle install + + file_env 'REDMINE_SECRET_KEY_BASE' + # just use the rails variable rather than trying to put it into a yml file + # https://github.com/rails/rails/blob/6-1-stable/railties/lib/rails/application.rb#L438 + # https://github.com/rails/rails/blob/1aa9987169213ce5ce43c20b2643bc64c235e792/railties/lib/rails/application.rb#L484 (rails 7.1-stable) + if [ -n "${SECRET_KEY_BASE:-}" ] && [ -n "${REDMINE_SECRET_KEY_BASE:-}" ]; then + echo >&2 + echo >&2 'warning: both SECRET_KEY_BASE and REDMINE_SECRET_KEY_BASE{_FILE} set, only SECRET_KEY_BASE will apply' + echo >&2 + fi + : "${SECRET_KEY_BASE:=${REDMINE_SECRET_KEY_BASE:-}}" + export SECRET_KEY_BASE + if [ -z "$SECRET_KEY_BASE" ]; then + # https://github.com/docker-library/redmine/issues/397 + # empty string is truthy in ruby and so masks the generated fallback config + # https://github.com/rails/rails/blob/1aa9987169213ce5ce43c20b2643bc64c235e792/railties/lib/rails/application.rb#L454 + unset SECRET_KEY_BASE + # generate SECRET_KEY_BASE in-file since it is not set or empty; this is not recommended unless the secret_token.rb is saved when container is recreated + if [ ! -f config/initializers/secret_token.rb ]; then + echo >&2 + echo >&2 'warning: no *SECRET_KEY_BASE set; running `rake generate_secret_token` to create one in "config/initializers/secret_token.rb"' + echo >&2 + rake generate_secret_token + fi + fi + + if [ "$1" != 'rake' -a -z "$REDMINE_NO_DB_MIGRATE" ]; then + rake db:migrate + fi + + if [ "$1" != 'rake' -a -n "$REDMINE_PLUGINS_MIGRATE" ]; then + rake redmine:plugins:migrate + fi + + # remove PID file to enable restarting the container + rm -f tmp/pids/server.pid +fi + +exec "$@" diff --git a/7.0/alpine3.24/Dockerfile b/7.0/alpine3.24/Dockerfile new file mode 100644 index 0000000..103e107 --- /dev/null +++ b/7.0/alpine3.24/Dockerfile @@ -0,0 +1,174 @@ +# +# NOTE: THIS DOCKERFILE IS GENERATED VIA "apply-templates.sh" +# +# PLEASE DO NOT EDIT IT DIRECTLY. +# + +FROM ruby:4.0-alpine3.24 + +# explicitly set uid/gid to guarantee that it won't change in the future +# the values 999:999 are identical to the current user/group id assigned +# alpine already has a gid 999, so we'll use the next id +RUN addgroup -S -g 1000 redmine && adduser -S -H -G redmine -u 999 redmine + +RUN set -eux; \ + apk add --no-cache \ + bash \ + breezy \ + ca-certificates \ + findutils \ + ghostscript \ + ghostscript-fonts \ + git \ + imagemagick \ + mercurial \ + openssh-client \ + subversion \ + tini \ + tzdata \ + wget \ + ; + +# grab gosu for easy step-down from root +# https://github.com/tianon/gosu/releases +ENV GOSU_VERSION 1.19 +RUN set -eux; \ + \ + apk add --no-cache --virtual .gosu-deps \ + dpkg \ + gnupg \ + ; \ + \ + dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \ + wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \ + wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \ + export GNUPGHOME="$(mktemp -d)"; \ + gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \ + gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \ + gpgconf --kill all; \ + rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \ + \ + apk del --no-network .gosu-deps; \ + \ +# smoke test + chmod +x /usr/local/bin/gosu; \ + gosu --version; \ + gosu nobody true + +ENV RAILS_ENV production +WORKDIR /usr/src/redmine + +# https://github.com/docker-library/redmine/issues/138#issuecomment-438834176 +# (bundler needs this for running as an arbitrary user) +ENV HOME /home/redmine +RUN set -eux; \ + [ ! -d "$HOME" ]; \ + mkdir -p "$HOME"; \ + chown redmine:redmine "$HOME"; \ + chmod 1777 "$HOME" + +ENV REDMINE_VERSION 7.0.0 +ENV REDMINE_DOWNLOAD_URL https://www.redmine.org/releases/redmine-7.0.0.tar.gz +ENV REDMINE_DOWNLOAD_SHA256 857e9f8860c31e4c531389e5d93eea26488dba69830484a3b0aa904be615e90a +ENV RAILS_LOG_TO_STDOUT true + +RUN set -eux; \ + wget -O redmine.tar.gz "$REDMINE_DOWNLOAD_URL"; \ + echo "$REDMINE_DOWNLOAD_SHA256 *redmine.tar.gz" | sha256sum -c -; \ + tar -xf redmine.tar.gz --strip-components=1; \ + rm redmine.tar.gz files/delete.me log/delete.me; \ + # https://www.redmine.org/projects/redmine/wiki/RedmineInstall#Step-8-File-system-permissions + set -- 'config' 'db' 'log' 'public/assets' 'sqlite' 'tmp' 'tmp/pdf' 'tmp/pids'; \ + mkdir -p "$@"; \ + chown -R redmine:redmine ./; \ +# fix permissions for running as an arbitrary user + chmod -R ugo=rwX "$@"; \ + find "$@" -type d -exec chmod 1777 '{}' + + +RUN set -eux; \ + apk add --no-cache --virtual .build-deps \ + cargo \ + clang21-dev \ + coreutils \ + freetds-dev \ + gcc \ + make \ + mariadb-dev \ + musl-dev \ + patch \ + postgresql-dev \ + yaml-dev \ + ; \ + \ + gosu redmine bundle config --local without 'development test'; \ +# https://github.com/redmine/redmine/commit/23dc108e70a0794f444803ac827a690085dcd557 +# ("gem puma" already exists in the Gemfile, but under "group :test" and we want it all the time) + puma="$(grep -E "^[[:space:]]*gem [:'\"]puma['\",[:space:]].*\$" Gemfile)"; \ + { echo; echo "$puma"; } | sed -re 's/^[[:space:]]+//' >> Gemfile; \ +# fill up "database.yml" with bogus entries so the redmine Gemfile will pre-install all database adapter dependencies +# https://github.com/redmine/redmine/blob/e9f9767089a4e3efbd73c35fc55c5c7eb85dd7d3/Gemfile#L50-L79 + echo '# the following entries only exist to force `bundle install` to pre-install all database adapter dependencies -- they can be safely removed/ignored' > ./config/database.yml; \ + for adapter in mysql2 postgresql sqlserver sqlite3; do \ + echo "$adapter:" >> ./config/database.yml; \ + echo " adapter: $adapter" >> ./config/database.yml; \ + done; \ + gosu redmine bundle install --jobs "$(nproc)"; \ + rm ./config/database.yml; \ +# fix permissions for running as an arbitrary user + chmod -R ugo=rwX Gemfile.lock "$GEM_HOME"; \ + rm -rf ~redmine/.bundle; \ + \ +# https://github.com/naitoh/rbpdf/issues/31 + rm /usr/local/bundle/gems/rbpdf-font-1.19.*/lib/fonts/ttf2ufm/ttf2ufm; \ + \ + runDeps="$( \ + scanelf --needed --nobanner --rpath --format '%n|%r|%F' --recursive /usr/local/bundle/gems \ + | awk -F '|' '{ \ + split($1, libs, ","); \ + split($2, rpaths, ":"); \ + \ + # $3 is the full file path. Calculate $ORIGIN by stripping the filename. + origin = $3; \ + sub("/[^/]+$", "", origin); \ + \ + for (i in libs) { \ + lib = libs[i]; \ + if (lib == "libc.so") continue; \ + \ + # 1. Skip if it exists in /usr/local/lib + if (!system("[ -e \"/usr/local/lib/" lib "\" ]")) continue; \ + \ + # 2. Check if satisfied by a bundled (relative) rpath + found = 0; \ + for (j in rpaths) { \ + rpath = rpaths[j]; \ + \ + # Only resolve $ORIGIN-relative rpaths. + if (sub(/^\$[{]?ORIGIN[}]?/, origin, rpath)) { \ + if (!system("[ -e \"" rpath "/" lib "\" ]")) { \ + found = 1; \ + break; \ + } \ + } \ + } \ + \ + # 3. If missing globally and not bundled locally, keep it as a system dep + if (!found) { \ + print "so:" lib; \ + } \ + } \ + }' \ + | sort -u \ + )"; \ + apk add --no-network --virtual .redmine-rundeps $runDeps; \ + apk del --no-network .build-deps; \ + # verify Kyiv timezone isn't missing + gosu redmine bundle exec rake time:zones:all | grep -q 'Kyiv' + +VOLUME /usr/src/redmine/files + +COPY docker-entrypoint.sh / +ENTRYPOINT ["/docker-entrypoint.sh"] + +EXPOSE 3000 +CMD ["rails", "server", "-b", "0.0.0.0"] diff --git a/7.0/alpine3.24/docker-entrypoint.sh b/7.0/alpine3.24/docker-entrypoint.sh new file mode 100755 index 0000000..fc68a28 --- /dev/null +++ b/7.0/alpine3.24/docker-entrypoint.sh @@ -0,0 +1,180 @@ +#!/usr/bin/env bash +set -Eeo pipefail +# TODO add "-u" + +# usage: file_env VAR [DEFAULT] +# ie: file_env 'XYZ_DB_PASSWORD' 'example' +# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of +# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) +file_env() { + local var="$1" + local fileVar="${var}_FILE" + local def="${2:-}" + if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then + echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + exit 1 + fi + local val="$def" + if [ "${!var:-}" ]; then + val="${!var}" + elif [ "${!fileVar:-}" ]; then + val="$(< "${!fileVar}")" + fi + export "$var"="$val" + unset "$fileVar" +} + +isLikelyRedmine= +case "$1" in + rails | rake ) isLikelyRedmine=1 ;; + bundle ) if [ "${2:-}" = 'exec' ]; then isLikelyRedmine=1; fi ;; # https://github.com/docker-library/redmine/pull/386 - "bundle exec sidekiq" +esac + +_fix_permissions() { + # https://www.redmine.org/projects/redmine/wiki/RedmineInstall#Step-8-File-system-permissions + local dirs=( config log public/*assets tmp ) args=() + if [ "$(id -u)" = '0' ]; then + args+=( ${args[@]:+,} '(' '!' -user redmine -exec chown redmine:redmine '{}' + ')' ) + + # https://github.com/docker-library/redmine/issues/268 - scanning "files" might be *really* expensive, so we should skip it if it seems like it's "already correct" + local filesOwnerMode + filesOwnerMode="$(stat -c '%U:%a' files)" + if [ "$filesOwnerMode" != 'redmine:755' ]; then + dirs+=( files ) + fi + fi + # directories 755, files 644: + args+=( ${args[@]:+,} '(' -type d '!' -perm 755 -exec sh -c 'chmod 755 "$@" 2>/dev/null || :' -- '{}' + ')' ) + args+=( ${args[@]:+,} '(' -type f '!' -perm 644 -exec sh -c 'chmod 644 "$@" 2>/dev/null || :' -- '{}' + ')' ) + find "${dirs[@]}" "${args[@]}" +} + +# allow the container to be started with `--user` +if [ -n "$isLikelyRedmine" ] && [ "$(id -u)" = '0' ]; then + _fix_permissions + exec gosu redmine "$BASH_SOURCE" "$@" +fi + +if [ -n "$isLikelyRedmine" ]; then + _fix_permissions + if [ ! -f './config/database.yml' ]; then + file_env 'REDMINE_DB_MYSQL' + file_env 'REDMINE_DB_POSTGRES' + file_env 'REDMINE_DB_SQLSERVER' + + if [ "$MYSQL_PORT_3306_TCP" ] && [ -z "$REDMINE_DB_MYSQL" ]; then + export REDMINE_DB_MYSQL='mysql' + elif [ "$POSTGRES_PORT_5432_TCP" ] && [ -z "$REDMINE_DB_POSTGRES" ]; then + export REDMINE_DB_POSTGRES='postgres' + fi + + if [ "$REDMINE_DB_MYSQL" ]; then + adapter='mysql2' + host="$REDMINE_DB_MYSQL" + file_env 'REDMINE_DB_PORT' '3306' + file_env 'REDMINE_DB_USERNAME' "${MYSQL_ENV_MYSQL_USER:-root}" + file_env 'REDMINE_DB_PASSWORD' "${MYSQL_ENV_MYSQL_PASSWORD:-${MYSQL_ENV_MYSQL_ROOT_PASSWORD:-}}" + file_env 'REDMINE_DB_DATABASE' "${MYSQL_ENV_MYSQL_DATABASE:-${MYSQL_ENV_MYSQL_USER:-redmine}}" + file_env 'REDMINE_DB_ENCODING' '' + elif [ "$REDMINE_DB_POSTGRES" ]; then + adapter='postgresql' + host="$REDMINE_DB_POSTGRES" + file_env 'REDMINE_DB_PORT' '5432' + file_env 'REDMINE_DB_USERNAME' "${POSTGRES_ENV_POSTGRES_USER:-postgres}" + file_env 'REDMINE_DB_PASSWORD' "${POSTGRES_ENV_POSTGRES_PASSWORD}" + file_env 'REDMINE_DB_DATABASE' "${POSTGRES_ENV_POSTGRES_DB:-${REDMINE_DB_USERNAME:-}}" + file_env 'REDMINE_DB_ENCODING' 'utf8' + elif [ "$REDMINE_DB_SQLSERVER" ]; then + adapter='sqlserver' + host="$REDMINE_DB_SQLSERVER" + file_env 'REDMINE_DB_PORT' '1433' + file_env 'REDMINE_DB_USERNAME' '' + file_env 'REDMINE_DB_PASSWORD' '' + file_env 'REDMINE_DB_DATABASE' '' + file_env 'REDMINE_DB_ENCODING' '' + else + echo >&2 + echo >&2 'warning: missing REDMINE_DB_MYSQL, REDMINE_DB_POSTGRES, or REDMINE_DB_SQLSERVER environment variables' + echo >&2 + echo >&2 '*** Using sqlite3 as fallback. ***' + echo >&2 + + adapter='sqlite3' + host='localhost' + file_env 'REDMINE_DB_PORT' '' + file_env 'REDMINE_DB_USERNAME' 'redmine' + file_env 'REDMINE_DB_PASSWORD' '' + file_env 'REDMINE_DB_DATABASE' 'sqlite/redmine.db' + file_env 'REDMINE_DB_ENCODING' 'utf8' + + mkdir -p "$(dirname "$REDMINE_DB_DATABASE")" + if [ "$(id -u)" = '0' ]; then + find "$(dirname "$REDMINE_DB_DATABASE")" \! -user redmine -exec chown redmine '{}' + + fi + fi + + REDMINE_DB_ADAPTER="$adapter" + REDMINE_DB_HOST="$host" + echo "$RAILS_ENV:" > config/database.yml + for var in \ + adapter \ + host \ + port \ + username \ + password \ + database \ + encoding \ + ; do + env="REDMINE_DB_${var^^}" + val="${!env}" + [ -n "$val" ] || continue + if [ "$var" != 'adapter' ]; then + # https://github.com/docker-library/redmine/issues/353 🙃 + val='"'"$val"'"' + # (only add double quotes to every value *except* `adapter: xxx`) + fi + echo " $var: $val" >> config/database.yml + done + fi + + # install additional gems for Gemfile.local and plugins + bundle check || bundle install + + file_env 'REDMINE_SECRET_KEY_BASE' + # just use the rails variable rather than trying to put it into a yml file + # https://github.com/rails/rails/blob/6-1-stable/railties/lib/rails/application.rb#L438 + # https://github.com/rails/rails/blob/1aa9987169213ce5ce43c20b2643bc64c235e792/railties/lib/rails/application.rb#L484 (rails 7.1-stable) + if [ -n "${SECRET_KEY_BASE:-}" ] && [ -n "${REDMINE_SECRET_KEY_BASE:-}" ]; then + echo >&2 + echo >&2 'warning: both SECRET_KEY_BASE and REDMINE_SECRET_KEY_BASE{_FILE} set, only SECRET_KEY_BASE will apply' + echo >&2 + fi + : "${SECRET_KEY_BASE:=${REDMINE_SECRET_KEY_BASE:-}}" + export SECRET_KEY_BASE + if [ -z "$SECRET_KEY_BASE" ]; then + # https://github.com/docker-library/redmine/issues/397 + # empty string is truthy in ruby and so masks the generated fallback config + # https://github.com/rails/rails/blob/1aa9987169213ce5ce43c20b2643bc64c235e792/railties/lib/rails/application.rb#L454 + unset SECRET_KEY_BASE + # generate SECRET_KEY_BASE in-file since it is not set or empty; this is not recommended unless the secret_token.rb is saved when container is recreated + if [ ! -f config/initializers/secret_token.rb ]; then + echo >&2 + echo >&2 'warning: no *SECRET_KEY_BASE set; running `rake generate_secret_token` to create one in "config/initializers/secret_token.rb"' + echo >&2 + rake generate_secret_token + fi + fi + + if [ "$1" != 'rake' -a -z "$REDMINE_NO_DB_MIGRATE" ]; then + rake db:migrate + fi + + if [ "$1" != 'rake' -a -n "$REDMINE_PLUGINS_MIGRATE" ]; then + rake redmine:plugins:migrate + fi + + # remove PID file to enable restarting the container + rm -f tmp/pids/server.pid +fi + +exec "$@" diff --git a/7.0/bookworm/Dockerfile b/7.0/bookworm/Dockerfile new file mode 100644 index 0000000..c23410a --- /dev/null +++ b/7.0/bookworm/Dockerfile @@ -0,0 +1,159 @@ +# +# NOTE: THIS DOCKERFILE IS GENERATED VIA "apply-templates.sh" +# +# PLEASE DO NOT EDIT IT DIRECTLY. +# + +FROM ruby:4.0-slim-bookworm + +# explicitly set uid/gid to guarantee that it won't change in the future +# the values 999:999 are identical to the current user/group id assigned +RUN groupadd -r -g 999 redmine && useradd -r -g redmine -u 999 redmine + +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + bzr \ + ca-certificates \ + ghostscript \ + git \ + gsfonts \ + imagemagick \ + mercurial \ + openssh-client \ + subversion \ + tini \ + wget \ + ; \ +# allow imagemagick to use ghostscript for PDF -> PNG thumbnail conversion (4.1+) + sed -ri 's/(rights)="none" (pattern="PDF")/\1="read" \2/' /etc/ImageMagick-6/policy.xml; \ + rm -rf /var/lib/apt/lists/* + +# grab gosu for easy step-down from root +# https://github.com/tianon/gosu/releases +ENV GOSU_VERSION 1.19 +RUN set -eux; \ + \ + savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + gnupg \ + ; \ + rm -rf /var/lib/apt/lists/*; \ + \ + dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \ + wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \ + wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \ + export GNUPGHOME="$(mktemp -d)"; \ + gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \ + gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \ + gpgconf --kill all; \ + rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \ + \ + apt-mark auto '.*' > /dev/null; \ + apt-mark manual $savedAptMark > /dev/null; \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + \ +# smoke test + chmod +x /usr/local/bin/gosu; \ + gosu --version; \ + gosu nobody true + +ENV RAILS_ENV production +WORKDIR /usr/src/redmine + +# https://github.com/docker-library/redmine/issues/138#issuecomment-438834176 +# (bundler needs this for running as an arbitrary user) +ENV HOME /home/redmine +RUN set -eux; \ + [ ! -d "$HOME" ]; \ + mkdir -p "$HOME"; \ + chown redmine:redmine "$HOME"; \ + chmod 1777 "$HOME" + +ENV REDMINE_VERSION 7.0.0 +ENV REDMINE_DOWNLOAD_URL https://www.redmine.org/releases/redmine-7.0.0.tar.gz +ENV REDMINE_DOWNLOAD_SHA256 857e9f8860c31e4c531389e5d93eea26488dba69830484a3b0aa904be615e90a +ENV RAILS_LOG_TO_STDOUT true + +RUN set -eux; \ + wget -O redmine.tar.gz "$REDMINE_DOWNLOAD_URL"; \ + echo "$REDMINE_DOWNLOAD_SHA256 *redmine.tar.gz" | sha256sum -c -; \ + tar -xf redmine.tar.gz --strip-components=1; \ + rm redmine.tar.gz files/delete.me log/delete.me; \ + # https://www.redmine.org/projects/redmine/wiki/RedmineInstall#Step-8-File-system-permissions + set -- 'config' 'db' 'log' 'public/assets' 'sqlite' 'tmp' 'tmp/pdf' 'tmp/pids'; \ + mkdir -p "$@"; \ + chown -R redmine:redmine ./; \ +# fix permissions for running as an arbitrary user + chmod -R ugo=rwX "$@"; \ + find "$@" -type d -exec chmod 1777 '{}' + + +RUN set -eux; \ + savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + default-libmysqlclient-dev \ + freetds-dev \ + gcc \ + libpq-dev \ + libsqlite3-dev \ + libxml2-dev \ + libxslt-dev \ + libyaml-dev \ + make \ + patch \ + pkgconf \ + xz-utils \ + ; \ + rm -rf /var/lib/apt/lists/*; \ + \ + gosu redmine bundle config --local without 'development test'; \ +# https://github.com/redmine/redmine/commit/23dc108e70a0794f444803ac827a690085dcd557 +# ("gem puma" already exists in the Gemfile, but under "group :test" and we want it all the time) + puma="$(grep -E "^[[:space:]]*gem [:'\"]puma['\",[:space:]].*\$" Gemfile)"; \ + { echo; echo "$puma"; } | sed -re 's/^[[:space:]]+//' >> Gemfile; \ +# fill up "database.yml" with bogus entries so the redmine Gemfile will pre-install all database adapter dependencies +# https://github.com/redmine/redmine/blob/e9f9767089a4e3efbd73c35fc55c5c7eb85dd7d3/Gemfile#L50-L79 + echo '# the following entries only exist to force `bundle install` to pre-install all database adapter dependencies -- they can be safely removed/ignored' > ./config/database.yml; \ + for adapter in mysql2 postgresql sqlserver sqlite3; do \ + echo "$adapter:" >> ./config/database.yml; \ + echo " adapter: $adapter" >> ./config/database.yml; \ + done; \ +# ruby gems (like nokogiri) don't publish arm32v5-compatible, pre-compiled libraries +# force them to compile so that they don't pull arm32v[6-7] libraries + arch="$(dpkg --print-architecture)"; \ + if [ "$arch" = 'armel' ]; then \ + gosu redmine bundle config set force_ruby_platform true; \ + fi; \ +# nokogiri's vendored libxml2 + libxslt do not build on mips64le, so use the apt packages when building + gosu redmine bundle config set build.nokogiri --use-system-libraries; \ + gosu redmine bundle install --jobs "$(nproc)"; \ + rm ./config/database.yml; \ +# fix permissions for running as an arbitrary user + chmod -R ugo=rwX Gemfile.lock "$GEM_HOME"; \ + rm -rf ~redmine/.bundle; \ + \ +# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies + apt-mark auto '.*' > /dev/null; \ + [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \ + find /usr/local -type f -executable -exec ldd '{}' ';' \ + | awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); printf "*%s\n", so }' \ + | sort -u \ + | xargs -rt dpkg-query --search \ +# https://manpages.debian.org/trixie/dpkg/dpkg-query.1.en.html#S (we ignore diversions and it'll be really unusual for more than one package to provide any given .so file) + | awk 'sub(":$", "", $1) { print $1 }' \ + | sort -u \ + | xargs -r apt-mark manual \ + ; \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + # verify Kyiv timezone isn't missing + gosu redmine bundle exec rake time:zones:all | grep -q 'Kyiv' + +VOLUME /usr/src/redmine/files + +COPY docker-entrypoint.sh / +ENTRYPOINT ["/docker-entrypoint.sh"] + +EXPOSE 3000 +CMD ["rails", "server", "-b", "0.0.0.0"] diff --git a/7.0/bookworm/docker-entrypoint.sh b/7.0/bookworm/docker-entrypoint.sh new file mode 100755 index 0000000..fc68a28 --- /dev/null +++ b/7.0/bookworm/docker-entrypoint.sh @@ -0,0 +1,180 @@ +#!/usr/bin/env bash +set -Eeo pipefail +# TODO add "-u" + +# usage: file_env VAR [DEFAULT] +# ie: file_env 'XYZ_DB_PASSWORD' 'example' +# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of +# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) +file_env() { + local var="$1" + local fileVar="${var}_FILE" + local def="${2:-}" + if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then + echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + exit 1 + fi + local val="$def" + if [ "${!var:-}" ]; then + val="${!var}" + elif [ "${!fileVar:-}" ]; then + val="$(< "${!fileVar}")" + fi + export "$var"="$val" + unset "$fileVar" +} + +isLikelyRedmine= +case "$1" in + rails | rake ) isLikelyRedmine=1 ;; + bundle ) if [ "${2:-}" = 'exec' ]; then isLikelyRedmine=1; fi ;; # https://github.com/docker-library/redmine/pull/386 - "bundle exec sidekiq" +esac + +_fix_permissions() { + # https://www.redmine.org/projects/redmine/wiki/RedmineInstall#Step-8-File-system-permissions + local dirs=( config log public/*assets tmp ) args=() + if [ "$(id -u)" = '0' ]; then + args+=( ${args[@]:+,} '(' '!' -user redmine -exec chown redmine:redmine '{}' + ')' ) + + # https://github.com/docker-library/redmine/issues/268 - scanning "files" might be *really* expensive, so we should skip it if it seems like it's "already correct" + local filesOwnerMode + filesOwnerMode="$(stat -c '%U:%a' files)" + if [ "$filesOwnerMode" != 'redmine:755' ]; then + dirs+=( files ) + fi + fi + # directories 755, files 644: + args+=( ${args[@]:+,} '(' -type d '!' -perm 755 -exec sh -c 'chmod 755 "$@" 2>/dev/null || :' -- '{}' + ')' ) + args+=( ${args[@]:+,} '(' -type f '!' -perm 644 -exec sh -c 'chmod 644 "$@" 2>/dev/null || :' -- '{}' + ')' ) + find "${dirs[@]}" "${args[@]}" +} + +# allow the container to be started with `--user` +if [ -n "$isLikelyRedmine" ] && [ "$(id -u)" = '0' ]; then + _fix_permissions + exec gosu redmine "$BASH_SOURCE" "$@" +fi + +if [ -n "$isLikelyRedmine" ]; then + _fix_permissions + if [ ! -f './config/database.yml' ]; then + file_env 'REDMINE_DB_MYSQL' + file_env 'REDMINE_DB_POSTGRES' + file_env 'REDMINE_DB_SQLSERVER' + + if [ "$MYSQL_PORT_3306_TCP" ] && [ -z "$REDMINE_DB_MYSQL" ]; then + export REDMINE_DB_MYSQL='mysql' + elif [ "$POSTGRES_PORT_5432_TCP" ] && [ -z "$REDMINE_DB_POSTGRES" ]; then + export REDMINE_DB_POSTGRES='postgres' + fi + + if [ "$REDMINE_DB_MYSQL" ]; then + adapter='mysql2' + host="$REDMINE_DB_MYSQL" + file_env 'REDMINE_DB_PORT' '3306' + file_env 'REDMINE_DB_USERNAME' "${MYSQL_ENV_MYSQL_USER:-root}" + file_env 'REDMINE_DB_PASSWORD' "${MYSQL_ENV_MYSQL_PASSWORD:-${MYSQL_ENV_MYSQL_ROOT_PASSWORD:-}}" + file_env 'REDMINE_DB_DATABASE' "${MYSQL_ENV_MYSQL_DATABASE:-${MYSQL_ENV_MYSQL_USER:-redmine}}" + file_env 'REDMINE_DB_ENCODING' '' + elif [ "$REDMINE_DB_POSTGRES" ]; then + adapter='postgresql' + host="$REDMINE_DB_POSTGRES" + file_env 'REDMINE_DB_PORT' '5432' + file_env 'REDMINE_DB_USERNAME' "${POSTGRES_ENV_POSTGRES_USER:-postgres}" + file_env 'REDMINE_DB_PASSWORD' "${POSTGRES_ENV_POSTGRES_PASSWORD}" + file_env 'REDMINE_DB_DATABASE' "${POSTGRES_ENV_POSTGRES_DB:-${REDMINE_DB_USERNAME:-}}" + file_env 'REDMINE_DB_ENCODING' 'utf8' + elif [ "$REDMINE_DB_SQLSERVER" ]; then + adapter='sqlserver' + host="$REDMINE_DB_SQLSERVER" + file_env 'REDMINE_DB_PORT' '1433' + file_env 'REDMINE_DB_USERNAME' '' + file_env 'REDMINE_DB_PASSWORD' '' + file_env 'REDMINE_DB_DATABASE' '' + file_env 'REDMINE_DB_ENCODING' '' + else + echo >&2 + echo >&2 'warning: missing REDMINE_DB_MYSQL, REDMINE_DB_POSTGRES, or REDMINE_DB_SQLSERVER environment variables' + echo >&2 + echo >&2 '*** Using sqlite3 as fallback. ***' + echo >&2 + + adapter='sqlite3' + host='localhost' + file_env 'REDMINE_DB_PORT' '' + file_env 'REDMINE_DB_USERNAME' 'redmine' + file_env 'REDMINE_DB_PASSWORD' '' + file_env 'REDMINE_DB_DATABASE' 'sqlite/redmine.db' + file_env 'REDMINE_DB_ENCODING' 'utf8' + + mkdir -p "$(dirname "$REDMINE_DB_DATABASE")" + if [ "$(id -u)" = '0' ]; then + find "$(dirname "$REDMINE_DB_DATABASE")" \! -user redmine -exec chown redmine '{}' + + fi + fi + + REDMINE_DB_ADAPTER="$adapter" + REDMINE_DB_HOST="$host" + echo "$RAILS_ENV:" > config/database.yml + for var in \ + adapter \ + host \ + port \ + username \ + password \ + database \ + encoding \ + ; do + env="REDMINE_DB_${var^^}" + val="${!env}" + [ -n "$val" ] || continue + if [ "$var" != 'adapter' ]; then + # https://github.com/docker-library/redmine/issues/353 🙃 + val='"'"$val"'"' + # (only add double quotes to every value *except* `adapter: xxx`) + fi + echo " $var: $val" >> config/database.yml + done + fi + + # install additional gems for Gemfile.local and plugins + bundle check || bundle install + + file_env 'REDMINE_SECRET_KEY_BASE' + # just use the rails variable rather than trying to put it into a yml file + # https://github.com/rails/rails/blob/6-1-stable/railties/lib/rails/application.rb#L438 + # https://github.com/rails/rails/blob/1aa9987169213ce5ce43c20b2643bc64c235e792/railties/lib/rails/application.rb#L484 (rails 7.1-stable) + if [ -n "${SECRET_KEY_BASE:-}" ] && [ -n "${REDMINE_SECRET_KEY_BASE:-}" ]; then + echo >&2 + echo >&2 'warning: both SECRET_KEY_BASE and REDMINE_SECRET_KEY_BASE{_FILE} set, only SECRET_KEY_BASE will apply' + echo >&2 + fi + : "${SECRET_KEY_BASE:=${REDMINE_SECRET_KEY_BASE:-}}" + export SECRET_KEY_BASE + if [ -z "$SECRET_KEY_BASE" ]; then + # https://github.com/docker-library/redmine/issues/397 + # empty string is truthy in ruby and so masks the generated fallback config + # https://github.com/rails/rails/blob/1aa9987169213ce5ce43c20b2643bc64c235e792/railties/lib/rails/application.rb#L454 + unset SECRET_KEY_BASE + # generate SECRET_KEY_BASE in-file since it is not set or empty; this is not recommended unless the secret_token.rb is saved when container is recreated + if [ ! -f config/initializers/secret_token.rb ]; then + echo >&2 + echo >&2 'warning: no *SECRET_KEY_BASE set; running `rake generate_secret_token` to create one in "config/initializers/secret_token.rb"' + echo >&2 + rake generate_secret_token + fi + fi + + if [ "$1" != 'rake' -a -z "$REDMINE_NO_DB_MIGRATE" ]; then + rake db:migrate + fi + + if [ "$1" != 'rake' -a -n "$REDMINE_PLUGINS_MIGRATE" ]; then + rake redmine:plugins:migrate + fi + + # remove PID file to enable restarting the container + rm -f tmp/pids/server.pid +fi + +exec "$@" diff --git a/7.0/trixie/Dockerfile b/7.0/trixie/Dockerfile new file mode 100644 index 0000000..fefac61 --- /dev/null +++ b/7.0/trixie/Dockerfile @@ -0,0 +1,166 @@ +# +# NOTE: THIS DOCKERFILE IS GENERATED VIA "apply-templates.sh" +# +# PLEASE DO NOT EDIT IT DIRECTLY. +# + +FROM ruby:4.0-slim-trixie + +# explicitly set uid/gid to guarantee that it won't change in the future +# the values 999:999 are identical to the current user/group id assigned +RUN groupadd -r -g 999 redmine && useradd -r -g redmine -u 999 redmine + +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + bzr \ + ca-certificates \ + ghostscript \ + git \ + gsfonts \ + imagemagick \ + mercurial \ + openssh-client \ + subversion \ + tini \ + tzdata-legacy \ + wget \ + ; \ + rm -rf /var/lib/apt/lists/* + +# grab gosu for easy step-down from root +# https://github.com/tianon/gosu/releases +ENV GOSU_VERSION 1.19 +RUN set -eux; \ + \ + savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + gnupg \ + ; \ + rm -rf /var/lib/apt/lists/*; \ + \ + dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \ + wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \ + wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \ + export GNUPGHOME="$(mktemp -d)"; \ + gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \ + gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \ + gpgconf --kill all; \ + rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \ + \ + apt-mark auto '.*' > /dev/null; \ + apt-mark manual $savedAptMark > /dev/null; \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + \ +# smoke test + chmod +x /usr/local/bin/gosu; \ + gosu --version; \ + gosu nobody true + +ENV RAILS_ENV production +WORKDIR /usr/src/redmine + +# https://github.com/docker-library/redmine/issues/138#issuecomment-438834176 +# (bundler needs this for running as an arbitrary user) +ENV HOME /home/redmine +RUN set -eux; \ + [ ! -d "$HOME" ]; \ + mkdir -p "$HOME"; \ + chown redmine:redmine "$HOME"; \ + chmod 1777 "$HOME" + +ENV REDMINE_VERSION 7.0.0 +ENV REDMINE_DOWNLOAD_URL https://www.redmine.org/releases/redmine-7.0.0.tar.gz +ENV REDMINE_DOWNLOAD_SHA256 857e9f8860c31e4c531389e5d93eea26488dba69830484a3b0aa904be615e90a +ENV RAILS_LOG_TO_STDOUT true + +RUN set -eux; \ + wget -O redmine.tar.gz "$REDMINE_DOWNLOAD_URL"; \ + echo "$REDMINE_DOWNLOAD_SHA256 *redmine.tar.gz" | sha256sum -c -; \ + tar -xf redmine.tar.gz --strip-components=1; \ + rm redmine.tar.gz files/delete.me log/delete.me; \ + # https://www.redmine.org/projects/redmine/wiki/RedmineInstall#Step-8-File-system-permissions + set -- 'config' 'db' 'log' 'public/assets' 'sqlite' 'tmp' 'tmp/pdf' 'tmp/pids'; \ + mkdir -p "$@"; \ + chown -R redmine:redmine ./; \ +# fix permissions for running as an arbitrary user + chmod -R ugo=rwX "$@"; \ + find "$@" -type d -exec chmod 1777 '{}' + + +RUN set -eux; \ + savedAptMark="$(apt-mark showmanual)"; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + default-libmysqlclient-dev \ + freetds-dev \ + gcc \ + libclang-dev \ + libpq-dev \ + libsqlite3-dev \ + libxml2-dev \ + libxslt-dev \ + libyaml-dev \ + make \ + patch \ + pkgconf \ + xz-utils \ + ; \ +# error: rustc 1.85.0 is not supported by the following packages: ... requires rustc 1.88.0 +# install cargo/rust from backports so that it is new enough +# https://packages.debian.org/trixie-backports/cargo + echo 'deb http://deb.debian.org/debian trixie-backports main' > /etc/apt/sources.list.d/backports.list; \ + apt-get update; \ + apt-get install -y --no-install-recommends cargo/trixie-backports rustc/trixie-backports; \ + rm -f /etc/apt/sources.list.d/backports.list; \ + rm -rf /var/lib/apt/lists/*; \ + \ + gosu redmine bundle config --local without 'development test'; \ +# https://github.com/redmine/redmine/commit/23dc108e70a0794f444803ac827a690085dcd557 +# ("gem puma" already exists in the Gemfile, but under "group :test" and we want it all the time) + puma="$(grep -E "^[[:space:]]*gem [:'\"]puma['\",[:space:]].*\$" Gemfile)"; \ + { echo; echo "$puma"; } | sed -re 's/^[[:space:]]+//' >> Gemfile; \ +# fill up "database.yml" with bogus entries so the redmine Gemfile will pre-install all database adapter dependencies +# https://github.com/redmine/redmine/blob/e9f9767089a4e3efbd73c35fc55c5c7eb85dd7d3/Gemfile#L50-L79 + echo '# the following entries only exist to force `bundle install` to pre-install all database adapter dependencies -- they can be safely removed/ignored' > ./config/database.yml; \ + for adapter in mysql2 postgresql sqlserver sqlite3; do \ + echo "$adapter:" >> ./config/database.yml; \ + echo " adapter: $adapter" >> ./config/database.yml; \ + done; \ +# ruby gems (like nokogiri) don't publish arm32v5-compatible, pre-compiled libraries +# force them to compile so that they don't pull arm32v[6-7] libraries + arch="$(dpkg --print-architecture)"; \ + if [ "$arch" = 'armel' ]; then \ + gosu redmine bundle config set force_ruby_platform true; \ + fi; \ +# nokogiri's vendored libxml2 + libxslt do not build on mips64le, so use the apt packages when building + gosu redmine bundle config set build.nokogiri --use-system-libraries; \ + gosu redmine bundle install --jobs "$(nproc)"; \ + rm ./config/database.yml; \ +# fix permissions for running as an arbitrary user + chmod -R ugo=rwX Gemfile.lock "$GEM_HOME"; \ + rm -rf ~redmine/.bundle; \ + \ +# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies + apt-mark auto '.*' > /dev/null; \ + [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \ + find /usr/local -type f -executable -exec ldd '{}' ';' \ + | awk '/=>/ { so = $(NF-1); if (index(so, "/usr/local/") == 1) { next }; gsub("^/(usr/)?", "", so); printf "*%s\n", so }' \ + | sort -u \ + | xargs -rt dpkg-query --search \ +# https://manpages.debian.org/trixie/dpkg/dpkg-query.1.en.html#S (we ignore diversions and it'll be really unusual for more than one package to provide any given .so file) + | awk 'sub(":$", "", $1) { print $1 }' \ + | sort -u \ + | xargs -r apt-mark manual \ + ; \ + apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \ + # verify Kyiv timezone isn't missing + gosu redmine bundle exec rake time:zones:all | grep -q 'Kyiv' + +VOLUME /usr/src/redmine/files + +COPY docker-entrypoint.sh / +ENTRYPOINT ["/docker-entrypoint.sh"] + +EXPOSE 3000 +CMD ["rails", "server", "-b", "0.0.0.0"] diff --git a/7.0/trixie/docker-entrypoint.sh b/7.0/trixie/docker-entrypoint.sh new file mode 100755 index 0000000..fc68a28 --- /dev/null +++ b/7.0/trixie/docker-entrypoint.sh @@ -0,0 +1,180 @@ +#!/usr/bin/env bash +set -Eeo pipefail +# TODO add "-u" + +# usage: file_env VAR [DEFAULT] +# ie: file_env 'XYZ_DB_PASSWORD' 'example' +# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of +# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) +file_env() { + local var="$1" + local fileVar="${var}_FILE" + local def="${2:-}" + if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then + echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + exit 1 + fi + local val="$def" + if [ "${!var:-}" ]; then + val="${!var}" + elif [ "${!fileVar:-}" ]; then + val="$(< "${!fileVar}")" + fi + export "$var"="$val" + unset "$fileVar" +} + +isLikelyRedmine= +case "$1" in + rails | rake ) isLikelyRedmine=1 ;; + bundle ) if [ "${2:-}" = 'exec' ]; then isLikelyRedmine=1; fi ;; # https://github.com/docker-library/redmine/pull/386 - "bundle exec sidekiq" +esac + +_fix_permissions() { + # https://www.redmine.org/projects/redmine/wiki/RedmineInstall#Step-8-File-system-permissions + local dirs=( config log public/*assets tmp ) args=() + if [ "$(id -u)" = '0' ]; then + args+=( ${args[@]:+,} '(' '!' -user redmine -exec chown redmine:redmine '{}' + ')' ) + + # https://github.com/docker-library/redmine/issues/268 - scanning "files" might be *really* expensive, so we should skip it if it seems like it's "already correct" + local filesOwnerMode + filesOwnerMode="$(stat -c '%U:%a' files)" + if [ "$filesOwnerMode" != 'redmine:755' ]; then + dirs+=( files ) + fi + fi + # directories 755, files 644: + args+=( ${args[@]:+,} '(' -type d '!' -perm 755 -exec sh -c 'chmod 755 "$@" 2>/dev/null || :' -- '{}' + ')' ) + args+=( ${args[@]:+,} '(' -type f '!' -perm 644 -exec sh -c 'chmod 644 "$@" 2>/dev/null || :' -- '{}' + ')' ) + find "${dirs[@]}" "${args[@]}" +} + +# allow the container to be started with `--user` +if [ -n "$isLikelyRedmine" ] && [ "$(id -u)" = '0' ]; then + _fix_permissions + exec gosu redmine "$BASH_SOURCE" "$@" +fi + +if [ -n "$isLikelyRedmine" ]; then + _fix_permissions + if [ ! -f './config/database.yml' ]; then + file_env 'REDMINE_DB_MYSQL' + file_env 'REDMINE_DB_POSTGRES' + file_env 'REDMINE_DB_SQLSERVER' + + if [ "$MYSQL_PORT_3306_TCP" ] && [ -z "$REDMINE_DB_MYSQL" ]; then + export REDMINE_DB_MYSQL='mysql' + elif [ "$POSTGRES_PORT_5432_TCP" ] && [ -z "$REDMINE_DB_POSTGRES" ]; then + export REDMINE_DB_POSTGRES='postgres' + fi + + if [ "$REDMINE_DB_MYSQL" ]; then + adapter='mysql2' + host="$REDMINE_DB_MYSQL" + file_env 'REDMINE_DB_PORT' '3306' + file_env 'REDMINE_DB_USERNAME' "${MYSQL_ENV_MYSQL_USER:-root}" + file_env 'REDMINE_DB_PASSWORD' "${MYSQL_ENV_MYSQL_PASSWORD:-${MYSQL_ENV_MYSQL_ROOT_PASSWORD:-}}" + file_env 'REDMINE_DB_DATABASE' "${MYSQL_ENV_MYSQL_DATABASE:-${MYSQL_ENV_MYSQL_USER:-redmine}}" + file_env 'REDMINE_DB_ENCODING' '' + elif [ "$REDMINE_DB_POSTGRES" ]; then + adapter='postgresql' + host="$REDMINE_DB_POSTGRES" + file_env 'REDMINE_DB_PORT' '5432' + file_env 'REDMINE_DB_USERNAME' "${POSTGRES_ENV_POSTGRES_USER:-postgres}" + file_env 'REDMINE_DB_PASSWORD' "${POSTGRES_ENV_POSTGRES_PASSWORD}" + file_env 'REDMINE_DB_DATABASE' "${POSTGRES_ENV_POSTGRES_DB:-${REDMINE_DB_USERNAME:-}}" + file_env 'REDMINE_DB_ENCODING' 'utf8' + elif [ "$REDMINE_DB_SQLSERVER" ]; then + adapter='sqlserver' + host="$REDMINE_DB_SQLSERVER" + file_env 'REDMINE_DB_PORT' '1433' + file_env 'REDMINE_DB_USERNAME' '' + file_env 'REDMINE_DB_PASSWORD' '' + file_env 'REDMINE_DB_DATABASE' '' + file_env 'REDMINE_DB_ENCODING' '' + else + echo >&2 + echo >&2 'warning: missing REDMINE_DB_MYSQL, REDMINE_DB_POSTGRES, or REDMINE_DB_SQLSERVER environment variables' + echo >&2 + echo >&2 '*** Using sqlite3 as fallback. ***' + echo >&2 + + adapter='sqlite3' + host='localhost' + file_env 'REDMINE_DB_PORT' '' + file_env 'REDMINE_DB_USERNAME' 'redmine' + file_env 'REDMINE_DB_PASSWORD' '' + file_env 'REDMINE_DB_DATABASE' 'sqlite/redmine.db' + file_env 'REDMINE_DB_ENCODING' 'utf8' + + mkdir -p "$(dirname "$REDMINE_DB_DATABASE")" + if [ "$(id -u)" = '0' ]; then + find "$(dirname "$REDMINE_DB_DATABASE")" \! -user redmine -exec chown redmine '{}' + + fi + fi + + REDMINE_DB_ADAPTER="$adapter" + REDMINE_DB_HOST="$host" + echo "$RAILS_ENV:" > config/database.yml + for var in \ + adapter \ + host \ + port \ + username \ + password \ + database \ + encoding \ + ; do + env="REDMINE_DB_${var^^}" + val="${!env}" + [ -n "$val" ] || continue + if [ "$var" != 'adapter' ]; then + # https://github.com/docker-library/redmine/issues/353 🙃 + val='"'"$val"'"' + # (only add double quotes to every value *except* `adapter: xxx`) + fi + echo " $var: $val" >> config/database.yml + done + fi + + # install additional gems for Gemfile.local and plugins + bundle check || bundle install + + file_env 'REDMINE_SECRET_KEY_BASE' + # just use the rails variable rather than trying to put it into a yml file + # https://github.com/rails/rails/blob/6-1-stable/railties/lib/rails/application.rb#L438 + # https://github.com/rails/rails/blob/1aa9987169213ce5ce43c20b2643bc64c235e792/railties/lib/rails/application.rb#L484 (rails 7.1-stable) + if [ -n "${SECRET_KEY_BASE:-}" ] && [ -n "${REDMINE_SECRET_KEY_BASE:-}" ]; then + echo >&2 + echo >&2 'warning: both SECRET_KEY_BASE and REDMINE_SECRET_KEY_BASE{_FILE} set, only SECRET_KEY_BASE will apply' + echo >&2 + fi + : "${SECRET_KEY_BASE:=${REDMINE_SECRET_KEY_BASE:-}}" + export SECRET_KEY_BASE + if [ -z "$SECRET_KEY_BASE" ]; then + # https://github.com/docker-library/redmine/issues/397 + # empty string is truthy in ruby and so masks the generated fallback config + # https://github.com/rails/rails/blob/1aa9987169213ce5ce43c20b2643bc64c235e792/railties/lib/rails/application.rb#L454 + unset SECRET_KEY_BASE + # generate SECRET_KEY_BASE in-file since it is not set or empty; this is not recommended unless the secret_token.rb is saved when container is recreated + if [ ! -f config/initializers/secret_token.rb ]; then + echo >&2 + echo >&2 'warning: no *SECRET_KEY_BASE set; running `rake generate_secret_token` to create one in "config/initializers/secret_token.rb"' + echo >&2 + rake generate_secret_token + fi + fi + + if [ "$1" != 'rake' -a -z "$REDMINE_NO_DB_MIGRATE" ]; then + rake db:migrate + fi + + if [ "$1" != 'rake' -a -n "$REDMINE_PLUGINS_MIGRATE" ]; then + rake redmine:plugins:migrate + fi + + # remove PID file to enable restarting the container + rm -f tmp/pids/server.pid +fi + +exec "$@" diff --git a/Dockerfile.template b/Dockerfile.template index cf1fde9..c9344cb 100644 --- a/Dockerfile.template +++ b/Dockerfile.template @@ -43,7 +43,7 @@ RUN groupadd -r -g 999 redmine && useradd -r -g redmine -u 999 redmine "gsfonts", # for generating PNGs of Gantt charts # redmine needs a rails update that includes https://github.com/rails/rails/pull/57266 # before it will handle the renamed timezones (they just fail to show in the redmine selector) - if env.variant == "trixie" and IN(env.version; "6.0", "6.1") then + if env.variant == "trixie" and IN(env.version; "6.0", "6.1", "7.0") then "tzdata-legacy" else empty end, empty @@ -200,9 +200,10 @@ ENV BUNDLE_FORCE_RUBY_PLATFORM 1 "libyaml-dev", "pkgconf", "xz-utils", - if IN(env.version; "6.0") then empty else + if IN(env.version; "6.0") or env.variant == "bookworm" then empty else # 6.1+ needs to compile some extensions on non-amd64-non-arm64 architectures - "cargo", + # bookworm is amd64 & arm64 only + if IN(env.version; "6.1") then "cargo" else empty end, # 7.0+ needs cargo from backports "libclang-dev", empty end, @@ -225,6 +226,15 @@ RUN set -eux; \ {{ . }} \ {{ ) | add -}} ; \ +{{ if IN(env.version; "7.0") and env.variant == "trixie" then ( -}} +# error: rustc 1.85.0 is not supported by the following packages: ... requires rustc 1.88.0 +# install cargo/rust from backports so that it is new enough +# https://packages.debian.org/trixie-backports/cargo + echo 'deb http://deb.debian.org/debian trixie-backports main' > /etc/apt/sources.list.d/backports.list; \ + apt-get update; \ + apt-get install -y --no-install-recommends cargo/trixie-backports rustc/trixie-backports; \ + rm -f /etc/apt/sources.list.d/backports.list; \ +{{ ) else "" end -}} rm -rf /var/lib/apt/lists/*; \ {{ ) end -}} {{ ) -}} @@ -262,16 +272,45 @@ RUN set -eux; \ rm /usr/local/bundle/gems/rbpdf-font-1.19.*/lib/fonts/ttf2ufm/ttf2ufm; \ \ runDeps="$( \ - scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/bundle/gems \ - | tr ',' '\n' \ - | sort -u \ - | awk ' \ + scanelf --needed --nobanner --rpath --format '%n|%r|%F' --recursive /usr/local/bundle/gems \ + | awk -F '|' '{ \ + split($1, libs, ","); \ + split($2, rpaths, ":"); \ + \ + # $3 is the full file path. Calculate $ORIGIN by stripping the filename. + origin = $3; \ + sub("/[^/]+$", "", origin); \ + \ + for (i in libs) { \ + lib = libs[i]; \ {{ if IN(env.version; "6.0") then "" else ( -}} - $1 == "libc.so" { next } \ + if (lib == "libc.so") continue; \ {{ ) end -}} - system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } \ - { print "so:" $1 } \ - ' \ + \ + # 1. Skip if it exists in /usr/local/lib + if (!system("[ -e \"/usr/local/lib/" lib "\" ]")) continue; \ + \ + # 2. Check if satisfied by a bundled (relative) rpath + found = 0; \ + for (j in rpaths) { \ + rpath = rpaths[j]; \ + \ + # Only resolve $ORIGIN-relative rpaths. + if (sub(/^\$[{]?ORIGIN[}]?/, origin, rpath)) { \ + if (!system("[ -e \"" rpath "/" lib "\" ]")) { \ + found = 1; \ + break; \ + } \ + } \ + } \ + \ + # 3. If missing globally and not bundled locally, keep it as a system dep + if (!found) { \ + print "so:" lib; \ + } \ + } \ + }' \ + | sort -u \ )"; \ apk add --no-network --virtual .redmine-rundeps $runDeps; \ apk del --no-network .build-deps; \ diff --git a/generate-stackbrew-library.sh b/generate-stackbrew-library.sh index a3a6344..258c901 100755 --- a/generate-stackbrew-library.sh +++ b/generate-stackbrew-library.sh @@ -2,7 +2,8 @@ set -Eeuo pipefail declare -A aliases=( - [6.1]='6 latest' + [7.0]='7 latest' + [6.1]='6' ) self="$(basename "$BASH_SOURCE")" @@ -109,12 +110,25 @@ for version; do fi if [[ "$variant" = 'alpine'* ]] && [ "$version" != '6.0' ]; then # 6.1 alpine fails to build in a reasonable time on arm32v6 + # https://github.com/docker-library/redmine/pull/395 variantArches="$(jq <<<"$variantArches" --raw-input --raw-output ' - split(" ") - | map(select(IN("arm32v6") | not)) + split(" ") - [ "arm32v6" ] | join(" ") ')" fi + if [ "$version" != '6.0' ] && [ "$version" != '6.1' ]; then + # 7.0+ rust deps of magnus gem don't compile on i386 or arm32vX alpine + # /home/redmine/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/magnus-0.8.2/src/time.rs:141:9 + # ... + # error[E0063]: missing fields `_bitfield_1` and `_bitfield_align_1` in + # initializer of `timespec` + if [[ "$variant" = 'alpine'* ]]; then + variantArches="$(jq <<<"$variantArches" --raw-input --raw-output ' + split(" ") - [ "i386", "arm32v7", "arm32v6" ] + | join(" ") + ')" + fi + fi variantAliases=( "${versionAliases[@]/%/-$variant}" ) variantAliases=( "${variantAliases[@]//latest-/}" ) diff --git a/versions.json b/versions.json index b7a4c6d..360ab1e 100644 --- a/versions.json +++ b/versions.json @@ -30,5 +30,21 @@ "alpine3.23" ], "version": "6.1.3" + }, + "7.0": { + "alpine": "3.24", + "debian": "trixie", + "downloadUrl": "https://www.redmine.org/releases/redmine-7.0.0.tar.gz", + "ruby": { + "version": "4.0" + }, + "sha256": "857e9f8860c31e4c531389e5d93eea26488dba69830484a3b0aa904be615e90a", + "variants": [ + "trixie", + "bookworm", + "alpine3.24", + "alpine3.23" + ], + "version": "7.0.0" } } diff --git a/versions.sh b/versions.sh index eb27f92..3596852 100755 --- a/versions.sh +++ b/versions.sh @@ -19,11 +19,11 @@ declare -A alpineVersions=( #[5.0]='3.16' ) # see https://www.redmine.org/projects/redmine/wiki/redmineinstall -defaultRubyVersion='3.4' +# https://www.redmine.org/news/161 (redmine 7.0: ruby 4.0.4+) +defaultRubyVersion='4.0' declare -A rubyVersions=( + [6.1]='3.4' [6.0]='3.3' - #[5.1]='3.2' - #[5.0]='3.1' ) cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"