From de0c4ab71e84df747c6e5af8e0bd8ad361d7b41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BCrk?= Date: Fri, 31 Jul 2026 17:09:26 +0200 Subject: [PATCH 1/2] [TASK] DPL-204: Own the sqlite tmpfs from the host "${USERSET}" passes "--user ${HOST_UID}" without a group, so a docker container runs as "uid=${HOST_UID} gid=0(root)". A "--tmpfs" is created "root:root" and inherits the mode of its host mountpoint, which is 0755 at a CI umask of 0022 - group 0 gets "r-x" only, and the functional sqlite suite fails with "unable to open database file". The docker adoption worked around this by mounting the sqlite tmpfs "mode=1777". That is correct and umask independent, but it treats the one mount rather than the missing group. The mount options move into "TMPFS_MOUNT_OPTIONS", assigned next to the container parameters they belong to, so the two container binaries can state what they actually need: docker adds "uid" and "gid" and keeps "mode=1777", rootless podman maps the container root to the host user and needs neither, keeping "mode=1777" for the rootful case. "${USERSET}" is deliberately left alone. It is evaluated for both container binaries, and appending a group there would change which host group rootless podman maps the container to, which is a different question from who owns a tmpfs. The comment moves along with the options and now names the umask that makes this visible: at the 0002 of a typical workstation the mountpoint comes up 0775 and the defect cannot be reproduced at all. --- Build/Scripts/runTests.sh | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh index 66ffba4..b561d49 100755 --- a/Build/Scripts/runTests.sh +++ b/Build/Scripts/runTests.sh @@ -395,6 +395,7 @@ handleDbmsOptions COMPOSER_ROOT_VERSION="1.0.5-dev" CONTAINER_INTERACTIVE="-it --init" HOST_UID=$(id -u) +HOST_GID=$(id -g) USERSET="" if [ $(uname) != "Darwin" ]; then USERSET="--user $HOST_UID" @@ -448,9 +449,25 @@ if [ "${CONTAINER_BIN}" == "docker" ]; then CONTAINER_COMMON_PARAMS="${CONTAINER_INTERACTIVE} --rm --network ${NETWORK} --add-host ${CONTAINER_HOST}:host-gateway ${USERSET} -v ${ROOT_DIR}:${ROOT_DIR} -w ${ROOT_DIR}" CONTAINER_SIMPLE_PARAMS="${CONTAINER_INTERACTIVE} --rm --network ${NETWORK} --add-host ${CONTAINER_HOST}:host-gateway ${USERSET} -v ${ROOT_DIR}:${ROOT_DIR} -w ${ROOT_DIR}" DOCUMENTATION_COMMON_PARAMS="${CONTAINER_INTERACTIVE} --rm ${USERSET} -v ${ROOT_DIR}:/project" + # docker creates a tmpfs owned by "root:root", inheriting the mode of its host + # mountpoint, while "${USERSET}" above passes a uid but no group and therefore runs + # the container as "uid=${HOST_UID} gid=0". At a CI umask of 0022 the mountpoint is + # 0755, so group 0 gets "r-x" and no test database can be created. + # + # "uid"/"gid" address that at the source: the mount is owned by the user the container + # runs as, whatever the umask of the host mountpoint. "mode=1777" is the workaround the + # docker adoption introduced instead, and is kept next to them - it is what has been + # proven on a GitHub hosted runner, and it costs nothing to leave in place. + # + # None of this reproduces at the 0002 umask of a typical workstation, where the + # mountpoint comes up 0775 and the group bit already grants access. Use "umask 0022". + TMPFS_MOUNT_OPTIONS="rw,noexec,nosuid,uid=${HOST_UID},gid=${HOST_GID},mode=1777" else # podman CONTAINER_HOST="host.containers.internal" + # Rootless podman maps the container root to the host user, so the tmpfs is writable + # without an explicit owner. "mode=1777" is kept for the rootful case. + TMPFS_MOUNT_OPTIONS="rw,noexec,nosuid,mode=1777" CONTAINER_COMMON_PARAMS="${CONTAINER_INTERACTIVE} ${CI_PARAMS} --rm --network ${NETWORK} -v ${ROOT_DIR}:${ROOT_DIR} -w ${ROOT_DIR}" CONTAINER_SIMPLE_PARAMS="${CONTAINER_INTERACTIVE} ${CI_PARAMS} --rm -v ${ROOT_DIR}:${ROOT_DIR} -w ${ROOT_DIR}" DOCUMENTATION_COMMON_PARAMS="${CONTAINER_INTERACTIVE} ${CI_PARAMS} --rm -v ${ROOT_DIR}:${ROOT_DIR} -v ${ROOT_DIR}:/project" @@ -557,13 +574,11 @@ case ${TEST_SUITE} in sqlite) # create sqlite tmpfs mount typo3temp/var/tests/functional-sqlite-dbs/ to avoid permission issues mkdir -p "${ROOT_DIR}/.Build/Web/typo3temp/var/tests/functional-sqlite-dbs/" - # "mode=1777" is required for docker and harmless for podman: docker runs - # the container as "--user $HOST_UID" with group 0, while the tmpfs comes - # up owned by root with mode 0755, so the test databases cannot be created - # and every test fails with "unable to open database file". Rootless podman - # passes no "--user" (it is root inside its user namespace), which is why - # this only shows with docker. - CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_sqlite --tmpfs ${ROOT_DIR}/.Build/Web/typo3temp/var/tests/functional-sqlite-dbs/:rw,noexec,nosuid,mode=1777 -e DEEPL_API_KEY=mock_server -e DEEPL_HOST=deepl-func-${SUFFIX} -e DEEPL_PORT=3000 -e DEEPL_SERVER_URL=deepl-func-${SUFFIX}:3000 -e DEEPL_MOCK_SERVER_PORT=3000 -e DEEPL_SCHEME=http -e DEEPL_MOCKSERVER_USED=1" + # "${TMPFS_MOUNT_OPTIONS}" carries the owner and mode the mount needs, which + # differ per container binary - see where it is assigned. Without them the + # test databases cannot be created and every test fails with "unable to open + # database file". + CONTAINERPARAMS="-e typo3DatabaseDriver=pdo_sqlite --tmpfs ${ROOT_DIR}/.Build/Web/typo3temp/var/tests/functional-sqlite-dbs/:${TMPFS_MOUNT_OPTIONS} -e DEEPL_API_KEY=mock_server -e DEEPL_HOST=deepl-func-${SUFFIX} -e DEEPL_PORT=3000 -e DEEPL_SERVER_URL=deepl-func-${SUFFIX}:3000 -e DEEPL_MOCK_SERVER_PORT=3000 -e DEEPL_SCHEME=http -e DEEPL_MOCKSERVER_USED=1" ${CONTAINER_BIN} run ${CONTAINER_COMMON_PARAMS} --name functional-${SUFFIX} ${XDEBUG_MODE} -e XDEBUG_CONFIG="${XDEBUG_CONFIG}" ${CONTAINERPARAMS} ${IMAGE_PHP} "${COMMAND[@]}" SUITE_EXIT_CODE=$? ;; From 65383c59aec515c56d25d7cba9e9784c6628943c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BCrk?= Date: Fri, 31 Jul 2026 17:10:00 +0200 Subject: [PATCH 2/2] [TASK] DPL-204: Revive two unused harness variables Two variables were referenced or built but never took effect, and both misled anyone reading the script. "CI_PARAMS" is expanded into the podman container parameters and into the mock server container - four sites, and the mock server one is unconditional, so it also applies under docker - but was never assigned. It is not a leftover though: the harnesses this one is modelled on assign it as "CI_PARAMS="${CI_PARAMS:-}"" and treat it as an escape hatch a caller can export to inject additional container flags. Only that assignment was missing here, so it is added rather than the references removed. "DOCUMENTATION_COMMON_PARAMS" was assigned for both container binaries and then never used. "renderDocumentation" built its own line instead, inlining "${CONTAINER_INTERACTIVE}" and repeating the bind mount, and so ran without "--rm" and, under docker, as root rather than as the host user - which is why rendering can leave root owned files behind in "Documentation-GENERATED-temp". It now uses the parameters that were built for it. They carry the same "${CONTAINER_INTERACTIVE}" the line had, add the "--rm" and the "${USERSET}" it was missing, and provide the "/project" mount, so the mount is dropped from the line. "--pull always" and the image arguments are unchanged. This also brings the call in line with "main", where the same variable is wired up. --- Build/Scripts/runTests.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Build/Scripts/runTests.sh b/Build/Scripts/runTests.sh index b561d49..413f108 100755 --- a/Build/Scripts/runTests.sh +++ b/Build/Scripts/runTests.sh @@ -396,6 +396,9 @@ COMPOSER_ROOT_VERSION="1.0.5-dev" CONTAINER_INTERACTIVE="-it --init" HOST_UID=$(id -u) HOST_GID=$(id -g) +# Additional container parameters, provided by the environment. Empty unless the caller +# exports it, which is how the portfolio harnesses inject CI specific flags. +CI_PARAMS="${CI_PARAMS:-}" USERSET="" if [ $(uname) != "Darwin" ]; then USERSET="--user $HOST_UID" @@ -595,7 +598,7 @@ case ${TEST_SUITE} in SUITE_EXIT_CODE=$? ;; renderDocumentation) - ${CONTAINER_BIN} run ${CONTAINER_INTERACTIVE} --pull always -v ${ROOT_DIR}:/project ${IMAGE_RSTRENDERING} --config=Documentation + ${CONTAINER_BIN} run ${DOCUMENTATION_COMMON_PARAMS} --pull always ${IMAGE_RSTRENDERING} --config=Documentation SUITE_EXIT_CODE=$? ;; phpstan)