Pre-setup and post-setup tasks
authorPablo Zmdl <redacted>
Thu, 2 Oct 2025 16:38:10 +0000 (18:38 +0200)
committerPablo Zmdl <redacted>
Mon, 3 Nov 2025 13:19:08 +0000 (14:19 +0100)
All executable files present in /entrypoint-tasks/pre-setup/ and
/entrypoint-tasks/post-setup/ are run at the beginning
or at the end, respectively, of the actual entrypoint-script.

Each of the executed files receive the CMD given to the container as their
arguments. This allows e.g. to change a plugin's config, or install a
technical requirement of a plugin that is to be installed.

This feature is not implemented in the images for Roundcube v1.5, since
they are not well maintained and will be dropped soon, anyways.

13 files changed:
.github/workflows/test-1.5.yml
README.md
apache/docker-entrypoint.sh
fpm-alpine/docker-entrypoint.sh
fpm/docker-entrypoint.sh
templates/docker-entrypoint.sh
tests/docker-compose.test-apache-postgres.yml
tests/docker-compose.test-fpm-postgres.yml
tests/post-setup/script.sh [new file with mode: 0755]
tests/pre-setup/a-script [new file with mode: 0755]
tests/pre-setup/b-not [new file with mode: 0644]
tests/pre-setup/c-check [new file with mode: 0755]
tests/run.sh

index 4385893bbd27cadb53c87aa675889453b86a236b..de56497a9397ebeac46cbe4a276b9a9fcd3dd9c4 100644 (file)
@@ -53,6 +53,7 @@ jobs:
           # Set these here so the values are visible in the logs for debugging.
           export ROUNDCUBEMAIL_TEST_IMAGE="${{ matrix.docker-tag }}"
           export HTTP_PORT="${{ matrix.http-port || '80' }}"
+          export SKIP_POST_SETUP_SCRIPT_TEST="yes"
           for testFile in ${{ join(matrix.test-files, ' ') }};
           do
             docker compose -f ./tests/docker-compose.test-${testFile}.yml \
index dbdb3ff7a2ecffa5c8afe7df6ae69db26191da47..ae1fadfdf6778ef6bfa4c751d39b7aafa33eaa05 100644 (file)
--- a/README.md
+++ b/README.md
@@ -149,6 +149,19 @@ For example:
   ROUNDCUBEMAIL_PLUGINS: thunderbird_labels, show_folder_size, tls_icon
 ```
 
+To overwrite the default config of a plugin you might need to use a post-setup script (see below) that moves a custom config file into the plugin's directory.
+
+## Pre-setup and post-setup tasks
+
+In order to execute custom tasks before or after Roundcubemail is set up in the container, you can bind-mount directories to `/entrypoint-tasks/pre-setup/` and `/entrypoint-tasks/post-setup/`. Then all executable files in those directories are executed at the beginning or the end of the actual entrypoint-script, respectively. If an executable exits with a code > 1, the entrypoint script exits, too.
+
+Each executable receives the container's `CMD` as arguments.
+
+They are executed in alphabetical order (the way `bash` understands it in `en_US` locale).
+
+If the Roundcubemail-setup is skipped due to a custom `CMD`, these tasks are skipped as well.
+
+
 ## HTTPS
 
 Currently all images are configured to speak HTTP. To provide HTTPS please run an additional reverse proxy in front of them, which handles certificates and terminates TLS. Alternatively you could derive from our images (or use the advanced configuration methods) to make Apache or nginx provide HTTPS – but please refrain from opening issues asking for support with such a setup.
index cb2fc38f3f3608aa12d27179aeafe6e1a44b98b8..600d0f04bd734f5b79fab4e66e1e47034cac2541 100755 (executable)
@@ -3,7 +3,36 @@
 
 # PWD=`pwd`
 
+run_entrypoint_tasks() {
+    phase="$1"
+    shift
+    shopt -s nullglob
+    echo "Running $phase-setup tasks:"
+    for file in /entrypoint-tasks/"$phase-setup"/*; do
+        if test ! -f "$file"; then
+            echo "Ignoring $file because it is not a regular file."
+            continue;
+        fi
+        if test ! -x "$file"; then
+            echo "Ignoring $file because it is not executable."
+            continue;
+        fi
+        echo "Running $phase-setup task $file:"
+        "$file" "$@"
+        # Exit in case of an error in an executable.
+        exit_code=$?
+        if test $exit_code -ne 0; then
+            echo "The task exited with code $exit_code, thus the entrypoint script is exiting, too!"
+            exit $exit_code
+        fi
+        echo 'Done.'
+    done
+    shopt -u nullglob
+}
+
 if  [[ "$1" == apache2* || "$1" == php-fpm || "$1" == bin* ]]; then
+  run_entrypoint_tasks pre "$@"
+
   INSTALLDIR=`pwd`
   # docroot is empty
   if ! [ -e index.php -a -e bin/installto.sh ]; then
@@ -207,6 +236,7 @@ if  [[ "$1" == apache2* || "$1" == php-fpm || "$1" == bin* ]]; then
     which apk && apk add --no-cache $ASPELL_PACKAGES
   fi
 
+  run_entrypoint_tasks post "$@"
 fi
 
 exec "$@"
index cb2fc38f3f3608aa12d27179aeafe6e1a44b98b8..600d0f04bd734f5b79fab4e66e1e47034cac2541 100755 (executable)
@@ -3,7 +3,36 @@
 
 # PWD=`pwd`
 
+run_entrypoint_tasks() {
+    phase="$1"
+    shift
+    shopt -s nullglob
+    echo "Running $phase-setup tasks:"
+    for file in /entrypoint-tasks/"$phase-setup"/*; do
+        if test ! -f "$file"; then
+            echo "Ignoring $file because it is not a regular file."
+            continue;
+        fi
+        if test ! -x "$file"; then
+            echo "Ignoring $file because it is not executable."
+            continue;
+        fi
+        echo "Running $phase-setup task $file:"
+        "$file" "$@"
+        # Exit in case of an error in an executable.
+        exit_code=$?
+        if test $exit_code -ne 0; then
+            echo "The task exited with code $exit_code, thus the entrypoint script is exiting, too!"
+            exit $exit_code
+        fi
+        echo 'Done.'
+    done
+    shopt -u nullglob
+}
+
 if  [[ "$1" == apache2* || "$1" == php-fpm || "$1" == bin* ]]; then
+  run_entrypoint_tasks pre "$@"
+
   INSTALLDIR=`pwd`
   # docroot is empty
   if ! [ -e index.php -a -e bin/installto.sh ]; then
@@ -207,6 +236,7 @@ if  [[ "$1" == apache2* || "$1" == php-fpm || "$1" == bin* ]]; then
     which apk && apk add --no-cache $ASPELL_PACKAGES
   fi
 
+  run_entrypoint_tasks post "$@"
 fi
 
 exec "$@"
index cb2fc38f3f3608aa12d27179aeafe6e1a44b98b8..600d0f04bd734f5b79fab4e66e1e47034cac2541 100755 (executable)
@@ -3,7 +3,36 @@
 
 # PWD=`pwd`
 
+run_entrypoint_tasks() {
+    phase="$1"
+    shift
+    shopt -s nullglob
+    echo "Running $phase-setup tasks:"
+    for file in /entrypoint-tasks/"$phase-setup"/*; do
+        if test ! -f "$file"; then
+            echo "Ignoring $file because it is not a regular file."
+            continue;
+        fi
+        if test ! -x "$file"; then
+            echo "Ignoring $file because it is not executable."
+            continue;
+        fi
+        echo "Running $phase-setup task $file:"
+        "$file" "$@"
+        # Exit in case of an error in an executable.
+        exit_code=$?
+        if test $exit_code -ne 0; then
+            echo "The task exited with code $exit_code, thus the entrypoint script is exiting, too!"
+            exit $exit_code
+        fi
+        echo 'Done.'
+    done
+    shopt -u nullglob
+}
+
 if  [[ "$1" == apache2* || "$1" == php-fpm || "$1" == bin* ]]; then
+  run_entrypoint_tasks pre "$@"
+
   INSTALLDIR=`pwd`
   # docroot is empty
   if ! [ -e index.php -a -e bin/installto.sh ]; then
@@ -207,6 +236,7 @@ if  [[ "$1" == apache2* || "$1" == php-fpm || "$1" == bin* ]]; then
     which apk && apk add --no-cache $ASPELL_PACKAGES
   fi
 
+  run_entrypoint_tasks post "$@"
 fi
 
 exec "$@"
index cb2fc38f3f3608aa12d27179aeafe6e1a44b98b8..600d0f04bd734f5b79fab4e66e1e47034cac2541 100644 (file)
@@ -3,7 +3,36 @@
 
 # PWD=`pwd`
 
+run_entrypoint_tasks() {
+    phase="$1"
+    shift
+    shopt -s nullglob
+    echo "Running $phase-setup tasks:"
+    for file in /entrypoint-tasks/"$phase-setup"/*; do
+        if test ! -f "$file"; then
+            echo "Ignoring $file because it is not a regular file."
+            continue;
+        fi
+        if test ! -x "$file"; then
+            echo "Ignoring $file because it is not executable."
+            continue;
+        fi
+        echo "Running $phase-setup task $file:"
+        "$file" "$@"
+        # Exit in case of an error in an executable.
+        exit_code=$?
+        if test $exit_code -ne 0; then
+            echo "The task exited with code $exit_code, thus the entrypoint script is exiting, too!"
+            exit $exit_code
+        fi
+        echo 'Done.'
+    done
+    shopt -u nullglob
+}
+
 if  [[ "$1" == apache2* || "$1" == php-fpm || "$1" == bin* ]]; then
+  run_entrypoint_tasks pre "$@"
+
   INSTALLDIR=`pwd`
   # docroot is empty
   if ! [ -e index.php -a -e bin/installto.sh ]; then
@@ -207,6 +236,7 @@ if  [[ "$1" == apache2* || "$1" == php-fpm || "$1" == bin* ]]; then
     which apk && apk add --no-cache $ASPELL_PACKAGES
   fi
 
+  run_entrypoint_tasks post "$@"
 fi
 
 exec "$@"
index 0967a0aa669d2631627b15c33f4ab2b01847ab94..f3d96265dc9ca85c45b0743b02cb804a89410637 100644 (file)
@@ -26,6 +26,9 @@ services:
       - ROUNDCUBEMAIL_DB_USER=roundcube # same as pgsql POSTGRES_USER env name
       - ROUNDCUBEMAIL_DB_PASSWORD=roundcube # same as pgsql POSTGRES_PASSWORD env name
       - ROUNDCUBEMAIL_SKIN=larry # Install non-default skin
+    volumes:
+        - "./pre-setup/:/entrypoint-tasks/pre-setup/"
+        - "./post-setup/:/entrypoint-tasks/post-setup/"
 
   roundcubedb:
     image: postgres:alpine
@@ -59,6 +62,7 @@ services:
     command: /tests/run.sh
     environment:
       - ROUNDCUBE_URL=http://roundcubemail:${HTTP_PORT:-80}/
+      - SKIP_POST_SETUP_SCRIPT_TEST=${SKIP_POST_SETUP_SCRIPT_TEST:-no}
     volumes:
       - ./run.sh:/tests/run.sh:ro
     working_dir: /tests
index b2a8cf61db65dda9bbc12d42427b50b40de45836..3a32b1a323108b98c080c40c42f185fa26180433 100644 (file)
@@ -21,6 +21,8 @@ services:
           - roundcubemail-fpm
     volumes:
       - www-vol:/var/www/html
+      - "./pre-setup/:/entrypoint-tasks/pre-setup/"
+      - "./post-setup/:/entrypoint-tasks/post-setup/"
     environment:
       - ROUNDCUBEMAIL_DB_TYPE=pgsql
       - ROUNDCUBEMAIL_DB_HOST=roundcubedb # same as pgsql container name
@@ -89,6 +91,7 @@ services:
     working_dir: /tests
     environment:
       ROUNDCUBE_URL: http://roundcubenginx/
+      SKIP_POST_SETUP_SCRIPT_TEST: ${SKIP_POST_SETUP_SCRIPT_TEST:-no}
 networks:
   roundcube_test_net:
 
diff --git a/tests/post-setup/script.sh b/tests/post-setup/script.sh
new file mode 100755 (executable)
index 0000000..62a2440
--- /dev/null
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+set -e
+
+# Check that the file, which a pre-setup-script should have created, is present.
+test -f /tmp/something
+
+# Leave a marker that can be checked from the outside.
+echo yes > public_html/post_setup_script.txt
diff --git a/tests/pre-setup/a-script b/tests/pre-setup/a-script
new file mode 100755 (executable)
index 0000000..86f8b95
--- /dev/null
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+set -e
+
+touch /tmp/something
+touch /tmp/anotherfile
diff --git a/tests/pre-setup/b-not b/tests/pre-setup/b-not
new file mode 100644 (file)
index 0000000..e982dfd
--- /dev/null
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+
+set -e
+
+# This script should not be run by the entrypoint-script!
+#
+# Create a regular file, so we can check that it doesn't exist in a later running script.
+touch /tmp/shouldnotexist
diff --git a/tests/pre-setup/c-check b/tests/pre-setup/c-check
new file mode 100755 (executable)
index 0000000..ebbf79e
--- /dev/null
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+set -e
+
+# Remove a file we previously created in "a-script.sh". If this fails, there's a problem in the order of script
+# execution.
+rm /tmp/anotherfile
+
+if test -f /tmp/shouldnotexist; then
+   echo "Error: File /tmp/shouldnotexist should not exist but does!"
+   exit 1
+fi
index 79678d46b888a456cfc7e04f4e66ce23bd1d64dd..51e57038c2650a8bfaba4524f37cfd5c92396b1b 100755 (executable)
@@ -31,4 +31,11 @@ findText 'Login' "${HOMEPAGE_TEXT}"
 findText 'Warning: This webmail service requires Javascript!' "${HOMEPAGE_TEXT}"
 echo 'Homepage is okay'
 
-echo 'End.'
\ No newline at end of file
+if test "$SKIP_POST_SETUP_SCRIPT_TEST" != "yes"; then
+    echo 'Checking post-setup-script marker'
+    POST_SETUP_SCRIPT_TEXT=$(curl -s --fail "${ROUNDCUBE_URL}post_setup_script.txt")
+    findText 'yes' "${POST_SETUP_SCRIPT_TEXT}"
+    echo 'post-setup-script marker is ok'
+fi
+
+echo 'End.'
git clone https://git.99rst.org/PROJECT