From b398ee1c1a1c836ed57e5b35ea6c1a714791b974 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:28:02 -0300 Subject: [PATCH 01/20] build: add nginx port selector image Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/Dockerfile.nginx-port-selector | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .docker/Dockerfile.nginx-port-selector diff --git a/.docker/Dockerfile.nginx-port-selector b/.docker/Dockerfile.nginx-port-selector new file mode 100644 index 0000000..d309795 --- /dev/null +++ b/.docker/Dockerfile.nginx-port-selector @@ -0,0 +1,9 @@ +FROM docker:27-cli + +RUN apk add --no-cache docker-cli-compose + +COPY scripts/nginx-port-selector.sh /usr/local/bin/nginx-port-selector.sh + +RUN chmod +x /usr/local/bin/nginx-port-selector.sh + +ENTRYPOINT ["/usr/local/bin/nginx-port-selector.sh"] From 64060a2f6e553f2632d6dd614389848d5d402b78 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:28:06 -0300 Subject: [PATCH 02/20] feat: add deterministic nginx port selector Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/nginx-port-selector.sh | 98 ++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 .docker/scripts/nginx-port-selector.sh diff --git a/.docker/scripts/nginx-port-selector.sh b/.docker/scripts/nginx-port-selector.sh new file mode 100644 index 0000000..f99aa4f --- /dev/null +++ b/.docker/scripts/nginx-port-selector.sh @@ -0,0 +1,98 @@ +#!/bin/sh + +set -eu + +compose_project() { + docker inspect --format '{{ index .Config.Labels "com.docker.compose.project" }}' "$HOSTNAME" +} + +project="$(compose_project)" +if [ -z "$project" ]; then + echo 'Could not determine the Compose project from the selector container.' >&2 + exit 1 +fi + +if [ -z "${PROJECT_DIR:-}" ]; then + echo 'The host project directory was not provided to the selector.' >&2 + exit 1 +fi + +compose() { + docker compose \ + --project-name "$project" \ + --project-directory "$PROJECT_DIR" \ + --file "$PROJECT_DIR/docker-compose.yml" \ + "$@" +} + +echo "Validating Compose project ${project} at ${PROJECT_DIR}." +compose config --quiet + +cleanup_nginx() { + compose rm --force --stop nginx || true +} + +trap cleanup_nginx INT TERM + +start_nginx() { + HTTP_PORT="$1" HTTPS_PORT="$2" IP_BIND="${IP_BIND:-127.0.0.1}" \ + compose up --detach --no-deps --scale nginx=1 nginx +} + +report_urls() { + http_url="http://localhost" + https_url="https://localhost" + [ "$1" -eq 80 ] || http_url="${http_url}:$1" + [ "$2" -eq 443 ] || https_url="${https_url}:$2" + echo "Nextcloud: ${http_url}" + echo "HTTPS: ${https_url}" +} + +if [ "${HTTP_PORT+x}" = x ] || [ "${HTTPS_PORT+x}" = x ]; then + echo "Explicit ports requested: HTTP ${HTTP_PORT:-80}, HTTPS ${HTTPS_PORT:-443}." + set +e + output="$(start_nginx "${HTTP_PORT:-80}" "${HTTPS_PORT:-443}" 2>&1)" + status=$? + set -e + printf '%s\n' "$output" + if [ "$status" -ne 0 ]; then + cleanup_nginx + exit "$status" + fi + echo 'nginx started with explicit ports.' + report_urls "${HTTP_PORT:-80}" "${HTTPS_PORT:-443}" + exit 0 +fi + +offset=0 +while [ "$offset" -le 19 ]; do + http_port=$((80 + offset)) + https_port=$((443 + offset)) + echo "Trying nginx ports HTTP ${http_port} / HTTPS ${https_port}." + + set +e + output="$(start_nginx "$http_port" "$https_port" 2>&1)" + status=$? + set -e + printf '%s\n' "$output" + + if [ "$status" -eq 0 ]; then + echo "nginx started with HTTP ${http_port} / HTTPS ${https_port}." + report_urls "$http_port" "$https_port" + exit 0 + fi + + if ! printf '%s\n' "$output" | grep -Eiq \ + 'address already in use|port is already allocated|port is already in use|failed to bind host port|cannot bind .* port'; then + echo 'nginx failed for a reason unrelated to a port conflict; stopping.' >&2 + cleanup_nginx + exit "$status" + fi + + echo "Ports ${http_port}/${https_port} are unavailable; trying the next pair." >&2 + cleanup_nginx + offset=$((offset + 1)) +done + +echo 'No available nginx port pair found in HTTP 80-99 / HTTPS 443-462.' >&2 +exit 1 From a47d47ed13785dd4c997c911cbd3bb93f04c1e2c Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 11:28:10 -0300 Subject: [PATCH 03/20] feat: wire deterministic nginx port selection Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- docker-compose.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e6af1f3..b67759b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,6 +48,7 @@ services: - host.docker.internal:host-gateway nginx: image: ghcr.io/librecodecoop/nextcloud-dev-nginx:latest + scale: 0 # build: # context: .docker/ # dockerfile: Dockerfile.nginx @@ -60,11 +61,27 @@ services: - ./volumes/nginx/certs:/certs ports: - target: 80 - published: "${HTTP_PORT:-80-99}" + published: "${HTTP_PORT:-80}" host_ip: ${IP_BIND:-127.0.0.1} - target: 443 - published: "${HTTPS_PORT:-443-462}" + published: "${HTTPS_PORT:-443}" host_ip: ${IP_BIND:-127.0.0.1} + nginx-port-selector: + build: + context: .docker/ + dockerfile: Dockerfile.nginx-port-selector + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - .:${PWD}:ro + working_dir: ${PWD} + environment: + - HTTP_PORT + - HTTPS_PORT + - IP_BIND + - PROJECT_DIR=${PWD} + restart: "no" + depends_on: + - nextcloud mailpit: image: axllent/mailpit ports: From 20e6bf76df4cf8b4fb04cae91ea1ea2643d50ed8 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:25:00 -0300 Subject: [PATCH 04/20] build: add Nextcloud banner image Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/Dockerfile.nextcloud-banner | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .docker/Dockerfile.nextcloud-banner diff --git a/.docker/Dockerfile.nextcloud-banner b/.docker/Dockerfile.nextcloud-banner new file mode 100644 index 0000000..806efb5 --- /dev/null +++ b/.docker/Dockerfile.nextcloud-banner @@ -0,0 +1,7 @@ +ARG PHP_VERSION=83 +FROM ghcr.io/librecodecoop/nextcloud-dev-php${PHP_VERSION}:latest + +COPY scripts/report-environment-ready /usr/local/bin/report-environment-ready +COPY scripts/nextcloud-entrypoint.sh /var/www/scripts/nextcloud-entrypoint.sh + +RUN chmod +x /usr/local/bin/report-environment-ready \ No newline at end of file From d32106f0495e556e5505d3ffc903a1b75dc7a9e3 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:25:04 -0300 Subject: [PATCH 05/20] feat: add environment banner reporter Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/report-environment-ready | 86 ++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 .docker/scripts/report-environment-ready diff --git a/.docker/scripts/report-environment-ready b/.docker/scripts/report-environment-ready new file mode 100644 index 0000000..1ce43e1 --- /dev/null +++ b/.docker/scripts/report-environment-ready @@ -0,0 +1,86 @@ +#!/bin/sh + +set -eu + +banner_file=/tmp/nextcloud-environment-ready +fpm_marker=/tmp/nextcloud-fpm-started + +load_banner() { + [ -f "$banner_file" ] || return 1 + # The file is created by this script from numeric Docker port mappings. + . "$banner_file" +} + +url() { + port="$1" + base="$2" + if [ "$port" = "80" ] || [ "$port" = "443" ]; then + printf '%s' "$base" + else + printf '%s:%s' "$base" "$port" + fi +} + +emit_banner() { + load_banner || return 1 + http_port="${ENV_HTTP_PORT:-}" + https_port="${ENV_HTTPS_PORT:-}" + + { + echo + echo '💙 Nextcloud development environment is ready!' + echo + + [ -z "$http_port" ] || printf 'Nextcloud: %s\n' "$(url "$http_port" http://localhost)" + [ -z "$https_port" ] || printf 'HTTPS: %s\n' "$(url "$https_port" https://localhost)" + [ -z "${ENV_MAILPIT_PORT:-}" ] || printf 'Mailpit: http://localhost:%s\n' "$ENV_MAILPIT_PORT" + [ -z "${ENV_EUROOFFICE_PORT:-}" ] || printf 'EuroOffice: http://localhost:%s\n' "$ENV_EUROOFFICE_PORT" + [ -z "${ENV_PLAYWRIGHT_PORT:-}" ] || printf 'Playwright: http://localhost:%s\n' "$ENV_PLAYWRIGHT_PORT" + [ -z "${ENV_SIGNAL_PORT:-}" ] || printf 'Signal: http://localhost:%s\n' "$ENV_SIGNAL_PORT" + } +} + +prepare_banner() { + temporary_file="${banner_file}.tmp.$$" + umask 077 + { + printf 'ENV_HTTP_PORT=%s\n' "${ENV_HTTP_PORT:-}" + printf 'ENV_HTTPS_PORT=%s\n' "${ENV_HTTPS_PORT:-}" + printf 'ENV_MAILPIT_PORT=%s\n' "${ENV_MAILPIT_PORT:-}" + printf 'ENV_EUROOFFICE_PORT=%s\n' "${ENV_EUROOFFICE_PORT:-}" + printf 'ENV_PLAYWRIGHT_PORT=%s\n' "${ENV_PLAYWRIGHT_PORT:-}" + printf 'ENV_SIGNAL_PORT=%s\n' "${ENV_SIGNAL_PORT:-}" + } > "$temporary_file" + mv "$temporary_file" "$banner_file" + + # On a repeated `docker compose up`, PHP-FPM is already PID 1. + if [ -f "$fpm_marker" ]; then + emit_banner > /proc/1/fd/2 + fi +} + +wait_and_emit() { + deadline=$(( $(date +%s) + 60 )) + while ! [ -f "$banner_file" ]; do + if [ "$(date +%s)" -ge "$deadline" ]; then + echo 'Environment banner data was not prepared; continuing without banner.' >&2 + return 0 + fi + sleep 1 + done + emit_banner +} + +case "${1:-}" in + --prepare) + prepare_banner + ;; + --wait-and-emit) + wait_and_emit + touch "$fpm_marker" + ;; + *) + echo "Usage: $0 --prepare|--wait-and-emit" >&2 + exit 2 + ;; +esac \ No newline at end of file From dbb9c3829ab85fb93a8f0109cd197f235c35b54c Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:25:07 -0300 Subject: [PATCH 06/20] feat: emit banner before PHP-FPM Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/nextcloud-entrypoint.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.docker/scripts/nextcloud-entrypoint.sh b/.docker/scripts/nextcloud-entrypoint.sh index 3fbccb5..c4f1479 100644 --- a/.docker/scripts/nextcloud-entrypoint.sh +++ b/.docker/scripts/nextcloud-entrypoint.sh @@ -146,5 +146,6 @@ exec busybox crond -f -l 0 -L /dev/stdout > /dev/null 2>&1 & runuser -u www-data -- php -f /var/www/html/cron.php # Start PHP-FPM -echo "💙 Nextcloud is up!" +/usr/local/bin/report-environment-ready --wait-and-emit +echo "Starting PHP-FPM..." exec "$@" From b17d70d9d99f94dde26f1f09d4f3fa70d2d8dd6c Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:25:12 -0300 Subject: [PATCH 07/20] feat: prepare banner after nginx selection Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/nginx-port-selector.sh | 37 +++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/.docker/scripts/nginx-port-selector.sh b/.docker/scripts/nginx-port-selector.sh index f99aa4f..10629bd 100644 --- a/.docker/scripts/nginx-port-selector.sh +++ b/.docker/scripts/nginx-port-selector.sh @@ -39,13 +39,28 @@ start_nginx() { compose up --detach --no-deps --scale nginx=1 nginx } -report_urls() { - http_url="http://localhost" - https_url="https://localhost" - [ "$1" -eq 80 ] || http_url="${http_url}:$1" - [ "$2" -eq 443 ] || https_url="${https_url}:$2" - echo "Nextcloud: ${http_url}" - echo "HTTPS: ${https_url}" +published_port() { + mapping="$(compose port "$1" "$2" 2>/dev/null || true)" + [ -n "$mapping" ] || return 0 + printf '%s\n' "${mapping##*:}" +} + +prepare_environment_banner() { + selector_http_port="$1" + selector_https_port="$2" + mailpit_port="$(published_port mailpit 8025)" + eurooffice_port="$(published_port eurooffice 80)" + playwright_port="$(published_port playwright 9323)" + signal_port="$(published_port signal-gateway 8080)" + + compose exec -T \ + -e ENV_HTTP_PORT="$selector_http_port" \ + -e ENV_HTTPS_PORT="$selector_https_port" \ + -e ENV_MAILPIT_PORT="$mailpit_port" \ + -e ENV_EUROOFFICE_PORT="$eurooffice_port" \ + -e ENV_PLAYWRIGHT_PORT="$playwright_port" \ + -e ENV_SIGNAL_PORT="$signal_port" \ + nextcloud /usr/local/bin/report-environment-ready --prepare } if [ "${HTTP_PORT+x}" = x ] || [ "${HTTPS_PORT+x}" = x ]; then @@ -60,7 +75,9 @@ if [ "${HTTP_PORT+x}" = x ] || [ "${HTTPS_PORT+x}" = x ]; then exit "$status" fi echo 'nginx started with explicit ports.' - report_urls "${HTTP_PORT:-80}" "${HTTPS_PORT:-443}" + if ! prepare_environment_banner "${HTTP_PORT:-80}" "${HTTPS_PORT:-443}"; then + echo 'Could not prepare environment banner.' >&2 + fi exit 0 fi @@ -78,7 +95,9 @@ while [ "$offset" -le 19 ]; do if [ "$status" -eq 0 ]; then echo "nginx started with HTTP ${http_port} / HTTPS ${https_port}." - report_urls "$http_port" "$https_port" + if ! prepare_environment_banner "$http_port" "$https_port"; then + echo 'Could not prepare environment banner.' >&2 + fi exit 0 fi From e93f0e311074301f0497ba4fb4ae12d8aa96f310 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:25:16 -0300 Subject: [PATCH 08/20] feat: enable Nextcloud banner image Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- docker-compose.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index b67759b..18ce2c4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,10 +8,11 @@ services: aliases: - ${DB_HOST:-mysql} nextcloud: - image: ghcr.io/librecodecoop/nextcloud-dev-php${PHP_VERSION:-83}:latest - # build: - # context: .docker/ - # dockerfile: Dockerfile.php${PHP_VERSION:-83} + build: + context: .docker/ + dockerfile: Dockerfile.nextcloud-banner + args: + PHP_VERSION: ${PHP_VERSION:-83} volumes: - ./.docker/scripts/wait-for-db.php:/var/www/scripts/wait-for-db.php - ./volumes/php:/var/www/php-config @@ -71,7 +72,7 @@ services: context: .docker/ dockerfile: Dockerfile.nginx-port-selector volumes: - - /var/run/docker.sock:/var/run/docker.sock + - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock - .:${PWD}:ro working_dir: ${PWD} environment: From 2531fc8af21a3dae78d62adedfd93cface22f93a Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:49:18 -0300 Subject: [PATCH 09/20] chore: remove obsolete banner Dockerfile Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/Dockerfile.nextcloud-banner | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .docker/Dockerfile.nextcloud-banner diff --git a/.docker/Dockerfile.nextcloud-banner b/.docker/Dockerfile.nextcloud-banner deleted file mode 100644 index 806efb5..0000000 --- a/.docker/Dockerfile.nextcloud-banner +++ /dev/null @@ -1,7 +0,0 @@ -ARG PHP_VERSION=83 -FROM ghcr.io/librecodecoop/nextcloud-dev-php${PHP_VERSION}:latest - -COPY scripts/report-environment-ready /usr/local/bin/report-environment-ready -COPY scripts/nextcloud-entrypoint.sh /var/www/scripts/nextcloud-entrypoint.sh - -RUN chmod +x /usr/local/bin/report-environment-ready \ No newline at end of file From 08c2c00be6e84bc8465612ba27fed00c61c088e7 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:49:21 -0300 Subject: [PATCH 10/20] fix: run banner reporter before PHP-FPM Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/nextcloud-entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.docker/scripts/nextcloud-entrypoint.sh b/.docker/scripts/nextcloud-entrypoint.sh index c4f1479..09f81d0 100644 --- a/.docker/scripts/nextcloud-entrypoint.sh +++ b/.docker/scripts/nextcloud-entrypoint.sh @@ -146,6 +146,6 @@ exec busybox crond -f -l 0 -L /dev/stdout > /dev/null 2>&1 & runuser -u www-data -- php -f /var/www/html/cron.php # Start PHP-FPM -/usr/local/bin/report-environment-ready --wait-and-emit +sh /var/www/scripts/report-environment-ready --wait-and-emit echo "Starting PHP-FPM..." exec "$@" From a7dd64f086b18bc55ebd25e3989352c5d0d8b07c Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:49:25 -0300 Subject: [PATCH 11/20] fix: invoke banner reporter from scripts path Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/nginx-port-selector.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.docker/scripts/nginx-port-selector.sh b/.docker/scripts/nginx-port-selector.sh index 10629bd..54efb99 100644 --- a/.docker/scripts/nginx-port-selector.sh +++ b/.docker/scripts/nginx-port-selector.sh @@ -3,7 +3,7 @@ set -eu compose_project() { - docker inspect --format '{{ index .Config.Labels "com.docker.compose.project" }}' "$HOSTNAME" + docker inspect --format '{{ index .Config.Labels "com.docker.compose.project" }}' "$(hostname)" } project="$(compose_project)" @@ -60,7 +60,7 @@ prepare_environment_banner() { -e ENV_EUROOFFICE_PORT="$eurooffice_port" \ -e ENV_PLAYWRIGHT_PORT="$playwright_port" \ -e ENV_SIGNAL_PORT="$signal_port" \ - nextcloud /usr/local/bin/report-environment-ready --prepare + nextcloud sh /var/www/scripts/report-environment-ready --prepare } if [ "${HTTP_PORT+x}" = x ] || [ "${HTTPS_PORT+x}" = x ]; then From fbfd5c252befa987996ee55477ea102a60ecad25 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:49:28 -0300 Subject: [PATCH 12/20] fix: parse banner ports without duplicate install Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/report-environment-ready | 27 +++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/.docker/scripts/report-environment-ready b/.docker/scripts/report-environment-ready index 1ce43e1..5a16d74 100644 --- a/.docker/scripts/report-environment-ready +++ b/.docker/scripts/report-environment-ready @@ -7,8 +7,29 @@ fpm_marker=/tmp/nextcloud-fpm-started load_banner() { [ -f "$banner_file" ] || return 1 - # The file is created by this script from numeric Docker port mappings. - . "$banner_file" + ENV_HTTP_PORT='' + ENV_HTTPS_PORT='' + ENV_MAILPIT_PORT='' + ENV_EUROOFFICE_PORT='' + ENV_PLAYWRIGHT_PORT='' + ENV_SIGNAL_PORT='' + while IFS='=' read -r name value; do + case "$name" in + ENV_HTTP_PORT|ENV_HTTPS_PORT|ENV_MAILPIT_PORT|ENV_EUROOFFICE_PORT|ENV_PLAYWRIGHT_PORT|ENV_SIGNAL_PORT) + case "$value" in + ''|*[!0-9]*) [ -z "$value" ] || return 1 ;; + esac + case "$name" in + ENV_HTTP_PORT) ENV_HTTP_PORT="$value" ;; + ENV_HTTPS_PORT) ENV_HTTPS_PORT="$value" ;; + ENV_MAILPIT_PORT) ENV_MAILPIT_PORT="$value" ;; + ENV_EUROOFFICE_PORT) ENV_EUROOFFICE_PORT="$value" ;; + ENV_PLAYWRIGHT_PORT) ENV_PLAYWRIGHT_PORT="$value" ;; + ENV_SIGNAL_PORT) ENV_SIGNAL_PORT="$value" ;; + esac + ;; + esac + done < "$banner_file" } url() { @@ -83,4 +104,4 @@ case "${1:-}" in echo "Usage: $0 --prepare|--wait-and-emit" >&2 exit 2 ;; -esac \ No newline at end of file +esac From fd3839a45e1f46a7ce1f92414958ed733212e5eb Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:49:31 -0300 Subject: [PATCH 13/20] fix: use published PHP images for banner Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- docker-compose.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 18ce2c4..fc8f629 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,11 +8,10 @@ services: aliases: - ${DB_HOST:-mysql} nextcloud: - build: - context: .docker/ - dockerfile: Dockerfile.nextcloud-banner - args: - PHP_VERSION: ${PHP_VERSION:-83} + image: ghcr.io/librecodecoop/nextcloud-dev-php${PHP_VERSION:-83}:latest + # build: + # context: .docker/ + # dockerfile: Dockerfile.php${PHP_VERSION:-83} volumes: - ./.docker/scripts/wait-for-db.php:/var/www/scripts/wait-for-db.php - ./volumes/php:/var/www/php-config From 5aeb685cfbbf7c1250705c195f1bf7a751584ec9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:01:52 -0300 Subject: [PATCH 14/20] fix: let PHP-FPM start independently Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/nextcloud-entrypoint.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/.docker/scripts/nextcloud-entrypoint.sh b/.docker/scripts/nextcloud-entrypoint.sh index 09f81d0..80d34c3 100644 --- a/.docker/scripts/nextcloud-entrypoint.sh +++ b/.docker/scripts/nextcloud-entrypoint.sh @@ -146,6 +146,5 @@ exec busybox crond -f -l 0 -L /dev/stdout > /dev/null 2>&1 & runuser -u www-data -- php -f /var/www/html/cron.php # Start PHP-FPM -sh /var/www/scripts/report-environment-ready --wait-and-emit echo "Starting PHP-FPM..." exec "$@" From a18d74d842e87e556b68f145783447861dd5e27f Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:01:56 -0300 Subject: [PATCH 15/20] fix: trigger banner after nginx selection Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/nginx-port-selector.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.docker/scripts/nginx-port-selector.sh b/.docker/scripts/nginx-port-selector.sh index 54efb99..a30ce4f 100644 --- a/.docker/scripts/nginx-port-selector.sh +++ b/.docker/scripts/nginx-port-selector.sh @@ -45,7 +45,7 @@ published_port() { printf '%s\n' "${mapping##*:}" } -prepare_environment_banner() { +report_environment_ready() { selector_http_port="$1" selector_https_port="$2" mailpit_port="$(published_port mailpit 8025)" @@ -60,7 +60,7 @@ prepare_environment_banner() { -e ENV_EUROOFFICE_PORT="$eurooffice_port" \ -e ENV_PLAYWRIGHT_PORT="$playwright_port" \ -e ENV_SIGNAL_PORT="$signal_port" \ - nextcloud sh /var/www/scripts/report-environment-ready --prepare + nextcloud sh /var/www/scripts/report-environment-ready } if [ "${HTTP_PORT+x}" = x ] || [ "${HTTPS_PORT+x}" = x ]; then @@ -75,8 +75,8 @@ if [ "${HTTP_PORT+x}" = x ] || [ "${HTTPS_PORT+x}" = x ]; then exit "$status" fi echo 'nginx started with explicit ports.' - if ! prepare_environment_banner "${HTTP_PORT:-80}" "${HTTPS_PORT:-443}"; then - echo 'Could not prepare environment banner.' >&2 + if ! report_environment_ready "${HTTP_PORT:-80}" "${HTTPS_PORT:-443}"; then + echo 'Could not print environment banner.' >&2 fi exit 0 fi @@ -95,8 +95,8 @@ while [ "$offset" -le 19 ]; do if [ "$status" -eq 0 ]; then echo "nginx started with HTTP ${http_port} / HTTPS ${https_port}." - if ! prepare_environment_banner "$http_port" "$https_port"; then - echo 'Could not prepare environment banner.' >&2 + if ! report_environment_ready "$http_port" "$https_port"; then + echo 'Could not print environment banner.' >&2 fi exit 0 fi From 6e182d01d407e4bb7df7f729e39c40b5616baea1 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:02:01 -0300 Subject: [PATCH 16/20] fix: wait for PHP-FPM before banner Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/report-environment-ready | 102 ++++++----------------- 1 file changed, 24 insertions(+), 78 deletions(-) diff --git a/.docker/scripts/report-environment-ready b/.docker/scripts/report-environment-ready index 5a16d74..2372929 100644 --- a/.docker/scripts/report-environment-ready +++ b/.docker/scripts/report-environment-ready @@ -2,34 +2,22 @@ set -eu -banner_file=/tmp/nextcloud-environment-ready -fpm_marker=/tmp/nextcloud-fpm-started - -load_banner() { - [ -f "$banner_file" ] || return 1 - ENV_HTTP_PORT='' - ENV_HTTPS_PORT='' - ENV_MAILPIT_PORT='' - ENV_EUROOFFICE_PORT='' - ENV_PLAYWRIGHT_PORT='' - ENV_SIGNAL_PORT='' - while IFS='=' read -r name value; do - case "$name" in - ENV_HTTP_PORT|ENV_HTTPS_PORT|ENV_MAILPIT_PORT|ENV_EUROOFFICE_PORT|ENV_PLAYWRIGHT_PORT|ENV_SIGNAL_PORT) - case "$value" in - ''|*[!0-9]*) [ -z "$value" ] || return 1 ;; - esac - case "$name" in - ENV_HTTP_PORT) ENV_HTTP_PORT="$value" ;; - ENV_HTTPS_PORT) ENV_HTTPS_PORT="$value" ;; - ENV_MAILPIT_PORT) ENV_MAILPIT_PORT="$value" ;; - ENV_EUROOFFICE_PORT) ENV_EUROOFFICE_PORT="$value" ;; - ENV_PLAYWRIGHT_PORT) ENV_PLAYWRIGHT_PORT="$value" ;; - ENV_SIGNAL_PORT) ENV_SIGNAL_PORT="$value" ;; - esac - ;; - esac - done < "$banner_file" +wait_for_php_fpm() { + attempt=0 + while [ "$attempt" -lt 100 ]; do + if php -r ' + $socket = @fsockopen("127.0.0.1", 9000, $errno, $errstr, 0.1); + if ($socket === false) { + exit(1); + } + fclose($socket); + ' >/dev/null 2>&1; then + return 0 + fi + attempt=$((attempt + 1)) + sleep 0.1 + done + return 1 } url() { @@ -43,65 +31,23 @@ url() { } emit_banner() { - load_banner || return 1 - http_port="${ENV_HTTP_PORT:-}" - https_port="${ENV_HTTPS_PORT:-}" - { echo echo '💙 Nextcloud development environment is ready!' echo - [ -z "$http_port" ] || printf 'Nextcloud: %s\n' "$(url "$http_port" http://localhost)" - [ -z "$https_port" ] || printf 'HTTPS: %s\n' "$(url "$https_port" https://localhost)" + [ -z "${ENV_HTTP_PORT:-}" ] || printf 'Nextcloud HTTP: %s\n' "$(url "$ENV_HTTP_PORT" http://localhost)" + [ -z "${ENV_HTTPS_PORT:-}" ] || printf 'Nextcloud HTTPS: %s\n' "$(url "$ENV_HTTPS_PORT" https://localhost)" [ -z "${ENV_MAILPIT_PORT:-}" ] || printf 'Mailpit: http://localhost:%s\n' "$ENV_MAILPIT_PORT" [ -z "${ENV_EUROOFFICE_PORT:-}" ] || printf 'EuroOffice: http://localhost:%s\n' "$ENV_EUROOFFICE_PORT" [ -z "${ENV_PLAYWRIGHT_PORT:-}" ] || printf 'Playwright: http://localhost:%s\n' "$ENV_PLAYWRIGHT_PORT" [ -z "${ENV_SIGNAL_PORT:-}" ] || printf 'Signal: http://localhost:%s\n' "$ENV_SIGNAL_PORT" - } -} - -prepare_banner() { - temporary_file="${banner_file}.tmp.$$" - umask 077 - { - printf 'ENV_HTTP_PORT=%s\n' "${ENV_HTTP_PORT:-}" - printf 'ENV_HTTPS_PORT=%s\n' "${ENV_HTTPS_PORT:-}" - printf 'ENV_MAILPIT_PORT=%s\n' "${ENV_MAILPIT_PORT:-}" - printf 'ENV_EUROOFFICE_PORT=%s\n' "${ENV_EUROOFFICE_PORT:-}" - printf 'ENV_PLAYWRIGHT_PORT=%s\n' "${ENV_PLAYWRIGHT_PORT:-}" - printf 'ENV_SIGNAL_PORT=%s\n' "${ENV_SIGNAL_PORT:-}" - } > "$temporary_file" - mv "$temporary_file" "$banner_file" - - # On a repeated `docker compose up`, PHP-FPM is already PID 1. - if [ -f "$fpm_marker" ]; then - emit_banner > /proc/1/fd/2 - fi + } > /proc/1/fd/2 } -wait_and_emit() { - deadline=$(( $(date +%s) + 60 )) - while ! [ -f "$banner_file" ]; do - if [ "$(date +%s)" -ge "$deadline" ]; then - echo 'Environment banner data was not prepared; continuing without banner.' >&2 - return 0 - fi - sleep 1 - done - emit_banner -} +if ! wait_for_php_fpm; then + echo 'Could not confirm PHP-FPM readiness; skipping environment banner.' >&2 + exit 0 +fi -case "${1:-}" in - --prepare) - prepare_banner - ;; - --wait-and-emit) - wait_and_emit - touch "$fpm_marker" - ;; - *) - echo "Usage: $0 --prepare|--wait-and-emit" >&2 - exit 2 - ;; -esac +emit_banner From 7fd40f63d8586a301db7e178fb40b10fa8ed308f Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:16:12 -0300 Subject: [PATCH 17/20] style: improve environment ready banner Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/report-environment-ready | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.docker/scripts/report-environment-ready b/.docker/scripts/report-environment-ready index 2372929..ae896ca 100644 --- a/.docker/scripts/report-environment-ready +++ b/.docker/scripts/report-environment-ready @@ -32,16 +32,17 @@ url() { emit_banner() { { - echo - echo '💙 Nextcloud development environment is ready!' - echo - - [ -z "${ENV_HTTP_PORT:-}" ] || printf 'Nextcloud HTTP: %s\n' "$(url "$ENV_HTTP_PORT" http://localhost)" - [ -z "${ENV_HTTPS_PORT:-}" ] || printf 'Nextcloud HTTPS: %s\n' "$(url "$ENV_HTTPS_PORT" https://localhost)" - [ -z "${ENV_MAILPIT_PORT:-}" ] || printf 'Mailpit: http://localhost:%s\n' "$ENV_MAILPIT_PORT" - [ -z "${ENV_EUROOFFICE_PORT:-}" ] || printf 'EuroOffice: http://localhost:%s\n' "$ENV_EUROOFFICE_PORT" - [ -z "${ENV_PLAYWRIGHT_PORT:-}" ] || printf 'Playwright: http://localhost:%s\n' "$ENV_PLAYWRIGHT_PORT" - [ -z "${ENV_SIGNAL_PORT:-}" ] || printf 'Signal: http://localhost:%s\n' "$ENV_SIGNAL_PORT" + printf '\n' + printf '┌─ 💙 Environment ready ─────────────────────\n' + printf '│\n' + [ -z "${ENV_HTTP_PORT:-}" ] || printf '│ %-15s %s\n' 'Nextcloud HTTP' "$(url "$ENV_HTTP_PORT" http://localhost)" + [ -z "${ENV_HTTPS_PORT:-}" ] || printf '│ %-15s %s\n' 'Nextcloud HTTPS' "$(url "$ENV_HTTPS_PORT" https://localhost)" + [ -z "${ENV_MAILPIT_PORT:-}" ] || printf '│ %-15s %s\n' 'Mailpit' "http://localhost:$ENV_MAILPIT_PORT" + [ -z "${ENV_EUROOFFICE_PORT:-}" ] || printf '│ %-15s %s\n' 'EuroOffice' "http://localhost:$ENV_EUROOFFICE_PORT" + [ -z "${ENV_PLAYWRIGHT_PORT:-}" ] || printf '│ %-15s %s\n' 'Playwright' "http://localhost:$ENV_PLAYWRIGHT_PORT" + [ -z "${ENV_SIGNAL_PORT:-}" ] || printf '│ %-15s %s\n' 'Signal' "http://localhost:$ENV_SIGNAL_PORT" + printf '│\n' + printf '└────────────────────────────────────────────\n' } > /proc/1/fd/2 } From fa5132d99d9924512370147d7bcc3828a8c212a9 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:23:19 -0300 Subject: [PATCH 18/20] feat: pass environment details to banner Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/nginx-port-selector.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.docker/scripts/nginx-port-selector.sh b/.docker/scripts/nginx-port-selector.sh index a30ce4f..d9b99c1 100644 --- a/.docker/scripts/nginx-port-selector.sh +++ b/.docker/scripts/nginx-port-selector.sh @@ -60,9 +60,17 @@ report_environment_ready() { -e ENV_EUROOFFICE_PORT="$eurooffice_port" \ -e ENV_PLAYWRIGHT_PORT="$playwright_port" \ -e ENV_SIGNAL_PORT="$signal_port" \ + -e ENV_ADMIN_USER="$NEXTCLOUD_ADMIN_USER" \ + -e ENV_ADMIN_PASSWORD="$NEXTCLOUD_ADMIN_PASSWORD" \ + -e ENV_NEXTCLOUD_BRANCH="$VERSION_NEXTCLOUD" \ nextcloud sh /var/www/scripts/report-environment-ready } +exit_successfully() { + echo '✅ Port selection complete. Exiting normally.' + exit 0 +} + if [ "${HTTP_PORT+x}" = x ] || [ "${HTTPS_PORT+x}" = x ]; then echo "Explicit ports requested: HTTP ${HTTP_PORT:-80}, HTTPS ${HTTPS_PORT:-443}." set +e @@ -78,7 +86,7 @@ if [ "${HTTP_PORT+x}" = x ] || [ "${HTTPS_PORT+x}" = x ]; then if ! report_environment_ready "${HTTP_PORT:-80}" "${HTTPS_PORT:-443}"; then echo 'Could not print environment banner.' >&2 fi - exit 0 + exit_successfully fi offset=0 @@ -98,7 +106,7 @@ while [ "$offset" -le 19 ]; do if ! report_environment_ready "$http_port" "$https_port"; then echo 'Could not print environment banner.' >&2 fi - exit 0 + exit_successfully fi if ! printf '%s\n' "$output" | grep -Eiq \ From 3322b64f87ef122429cf2b0f9c7e22860a3bbde4 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:23:23 -0300 Subject: [PATCH 19/20] feat: show environment credentials in banner Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- .docker/scripts/report-environment-ready | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.docker/scripts/report-environment-ready b/.docker/scripts/report-environment-ready index ae896ca..2c5a402 100644 --- a/.docker/scripts/report-environment-ready +++ b/.docker/scripts/report-environment-ready @@ -35,12 +35,16 @@ emit_banner() { printf '\n' printf '┌─ 💙 Environment ready ─────────────────────\n' printf '│\n' - [ -z "${ENV_HTTP_PORT:-}" ] || printf '│ %-15s %s\n' 'Nextcloud HTTP' "$(url "$ENV_HTTP_PORT" http://localhost)" - [ -z "${ENV_HTTPS_PORT:-}" ] || printf '│ %-15s %s\n' 'Nextcloud HTTPS' "$(url "$ENV_HTTPS_PORT" https://localhost)" - [ -z "${ENV_MAILPIT_PORT:-}" ] || printf '│ %-15s %s\n' 'Mailpit' "http://localhost:$ENV_MAILPIT_PORT" - [ -z "${ENV_EUROOFFICE_PORT:-}" ] || printf '│ %-15s %s\n' 'EuroOffice' "http://localhost:$ENV_EUROOFFICE_PORT" - [ -z "${ENV_PLAYWRIGHT_PORT:-}" ] || printf '│ %-15s %s\n' 'Playwright' "http://localhost:$ENV_PLAYWRIGHT_PORT" - [ -z "${ENV_SIGNAL_PORT:-}" ] || printf '│ %-15s %s\n' 'Signal' "http://localhost:$ENV_SIGNAL_PORT" + [ -z "${ENV_HTTP_PORT:-}" ] || printf '│ %-16s %s\n' 'Nextcloud HTTP' "$(url "$ENV_HTTP_PORT" http://localhost)" + [ -z "${ENV_HTTPS_PORT:-}" ] || printf '│ %-16s %s\n' 'Nextcloud HTTPS' "$(url "$ENV_HTTPS_PORT" https://localhost)" + [ -z "${ENV_MAILPIT_PORT:-}" ] || printf '│ %-16s %s\n' 'Mailpit' "http://localhost:$ENV_MAILPIT_PORT" + [ -z "${ENV_EUROOFFICE_PORT:-}" ] || printf '│ %-16s %s\n' 'EuroOffice' "http://localhost:$ENV_EUROOFFICE_PORT" + [ -z "${ENV_PLAYWRIGHT_PORT:-}" ] || printf '│ %-16s %s\n' 'Playwright' "http://localhost:$ENV_PLAYWRIGHT_PORT" + [ -z "${ENV_SIGNAL_PORT:-}" ] || printf '│ %-16s %s\n' 'Signal' "http://localhost:$ENV_SIGNAL_PORT" + printf '│\n' + [ -z "${ENV_NEXTCLOUD_BRANCH:-}" ] || printf '│ %-16s %s\n' 'Nextcloud branch' "$ENV_NEXTCLOUD_BRANCH" + [ -z "${ENV_ADMIN_USER:-}" ] || printf '│ %-16s %s\n' 'Admin user' "$ENV_ADMIN_USER" + [ -z "${ENV_ADMIN_PASSWORD:-}" ] || printf '│ %-16s %s\n' 'Admin password' "$ENV_ADMIN_PASSWORD" printf '│\n' printf '└────────────────────────────────────────────\n' } > /proc/1/fd/2 From 143a2951d12911ba467237661f9568cf1d99e107 Mon Sep 17 00:00:00 2001 From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:23:27 -0300 Subject: [PATCH 20/20] feat: expose banner environment values to selector Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com> --- docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index fc8f629..29567fd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -79,6 +79,9 @@ services: - HTTPS_PORT - IP_BIND - PROJECT_DIR=${PWD} + - NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER:-admin} + - NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD:-admin} + - VERSION_NEXTCLOUD=${VERSION_NEXTCLOUD:-master} restart: "no" depends_on: - nextcloud