Skip to content

Commit e014019

Browse files
authored
Merge pull request #118 from LibreCodeCoop/fix/nginx-port-selector
fix: select deterministic ports for nginx
2 parents 522fd4a + 143a295 commit e014019

5 files changed

Lines changed: 215 additions & 3 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FROM docker:27-cli
2+
3+
RUN apk add --no-cache docker-cli-compose
4+
5+
COPY scripts/nginx-port-selector.sh /usr/local/bin/nginx-port-selector.sh
6+
7+
RUN chmod +x /usr/local/bin/nginx-port-selector.sh
8+
9+
ENTRYPOINT ["/usr/local/bin/nginx-port-selector.sh"]

.docker/scripts/nextcloud-entrypoint.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,5 +146,5 @@ exec busybox crond -f -l 0 -L /dev/stdout > /dev/null 2>&1 &
146146
runuser -u www-data -- php -f /var/www/html/cron.php
147147

148148
# Start PHP-FPM
149-
echo "💙 Nextcloud is up!"
149+
echo "Starting PHP-FPM..."
150150
exec "$@"
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/bin/sh
2+
3+
set -eu
4+
5+
compose_project() {
6+
docker inspect --format '{{ index .Config.Labels "com.docker.compose.project" }}' "$(hostname)"
7+
}
8+
9+
project="$(compose_project)"
10+
if [ -z "$project" ]; then
11+
echo 'Could not determine the Compose project from the selector container.' >&2
12+
exit 1
13+
fi
14+
15+
if [ -z "${PROJECT_DIR:-}" ]; then
16+
echo 'The host project directory was not provided to the selector.' >&2
17+
exit 1
18+
fi
19+
20+
compose() {
21+
docker compose \
22+
--project-name "$project" \
23+
--project-directory "$PROJECT_DIR" \
24+
--file "$PROJECT_DIR/docker-compose.yml" \
25+
"$@"
26+
}
27+
28+
echo "Validating Compose project ${project} at ${PROJECT_DIR}."
29+
compose config --quiet
30+
31+
cleanup_nginx() {
32+
compose rm --force --stop nginx || true
33+
}
34+
35+
trap cleanup_nginx INT TERM
36+
37+
start_nginx() {
38+
HTTP_PORT="$1" HTTPS_PORT="$2" IP_BIND="${IP_BIND:-127.0.0.1}" \
39+
compose up --detach --no-deps --scale nginx=1 nginx
40+
}
41+
42+
published_port() {
43+
mapping="$(compose port "$1" "$2" 2>/dev/null || true)"
44+
[ -n "$mapping" ] || return 0
45+
printf '%s\n' "${mapping##*:}"
46+
}
47+
48+
report_environment_ready() {
49+
selector_http_port="$1"
50+
selector_https_port="$2"
51+
mailpit_port="$(published_port mailpit 8025)"
52+
eurooffice_port="$(published_port eurooffice 80)"
53+
playwright_port="$(published_port playwright 9323)"
54+
signal_port="$(published_port signal-gateway 8080)"
55+
56+
compose exec -T \
57+
-e ENV_HTTP_PORT="$selector_http_port" \
58+
-e ENV_HTTPS_PORT="$selector_https_port" \
59+
-e ENV_MAILPIT_PORT="$mailpit_port" \
60+
-e ENV_EUROOFFICE_PORT="$eurooffice_port" \
61+
-e ENV_PLAYWRIGHT_PORT="$playwright_port" \
62+
-e ENV_SIGNAL_PORT="$signal_port" \
63+
-e ENV_ADMIN_USER="$NEXTCLOUD_ADMIN_USER" \
64+
-e ENV_ADMIN_PASSWORD="$NEXTCLOUD_ADMIN_PASSWORD" \
65+
-e ENV_NEXTCLOUD_BRANCH="$VERSION_NEXTCLOUD" \
66+
nextcloud sh /var/www/scripts/report-environment-ready
67+
}
68+
69+
exit_successfully() {
70+
echo '✅ Port selection complete. Exiting normally.'
71+
exit 0
72+
}
73+
74+
if [ "${HTTP_PORT+x}" = x ] || [ "${HTTPS_PORT+x}" = x ]; then
75+
echo "Explicit ports requested: HTTP ${HTTP_PORT:-80}, HTTPS ${HTTPS_PORT:-443}."
76+
set +e
77+
output="$(start_nginx "${HTTP_PORT:-80}" "${HTTPS_PORT:-443}" 2>&1)"
78+
status=$?
79+
set -e
80+
printf '%s\n' "$output"
81+
if [ "$status" -ne 0 ]; then
82+
cleanup_nginx
83+
exit "$status"
84+
fi
85+
echo 'nginx started with explicit ports.'
86+
if ! report_environment_ready "${HTTP_PORT:-80}" "${HTTPS_PORT:-443}"; then
87+
echo 'Could not print environment banner.' >&2
88+
fi
89+
exit_successfully
90+
fi
91+
92+
offset=0
93+
while [ "$offset" -le 19 ]; do
94+
http_port=$((80 + offset))
95+
https_port=$((443 + offset))
96+
echo "Trying nginx ports HTTP ${http_port} / HTTPS ${https_port}."
97+
98+
set +e
99+
output="$(start_nginx "$http_port" "$https_port" 2>&1)"
100+
status=$?
101+
set -e
102+
printf '%s\n' "$output"
103+
104+
if [ "$status" -eq 0 ]; then
105+
echo "nginx started with HTTP ${http_port} / HTTPS ${https_port}."
106+
if ! report_environment_ready "$http_port" "$https_port"; then
107+
echo 'Could not print environment banner.' >&2
108+
fi
109+
exit_successfully
110+
fi
111+
112+
if ! printf '%s\n' "$output" | grep -Eiq \
113+
'address already in use|port is already allocated|port is already in use|failed to bind host port|cannot bind .* port'; then
114+
echo 'nginx failed for a reason unrelated to a port conflict; stopping.' >&2
115+
cleanup_nginx
116+
exit "$status"
117+
fi
118+
119+
echo "Ports ${http_port}/${https_port} are unavailable; trying the next pair." >&2
120+
cleanup_nginx
121+
offset=$((offset + 1))
122+
done
123+
124+
echo 'No available nginx port pair found in HTTP 80-99 / HTTPS 443-462.' >&2
125+
exit 1
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/bin/sh
2+
3+
set -eu
4+
5+
wait_for_php_fpm() {
6+
attempt=0
7+
while [ "$attempt" -lt 100 ]; do
8+
if php -r '
9+
$socket = @fsockopen("127.0.0.1", 9000, $errno, $errstr, 0.1);
10+
if ($socket === false) {
11+
exit(1);
12+
}
13+
fclose($socket);
14+
' >/dev/null 2>&1; then
15+
return 0
16+
fi
17+
attempt=$((attempt + 1))
18+
sleep 0.1
19+
done
20+
return 1
21+
}
22+
23+
url() {
24+
port="$1"
25+
base="$2"
26+
if [ "$port" = "80" ] || [ "$port" = "443" ]; then
27+
printf '%s' "$base"
28+
else
29+
printf '%s:%s' "$base" "$port"
30+
fi
31+
}
32+
33+
emit_banner() {
34+
{
35+
printf '\n'
36+
printf '┌─ 💙 Environment ready ─────────────────────\n'
37+
printf '│\n'
38+
[ -z "${ENV_HTTP_PORT:-}" ] || printf '│ %-16s %s\n' 'Nextcloud HTTP' "$(url "$ENV_HTTP_PORT" http://localhost)"
39+
[ -z "${ENV_HTTPS_PORT:-}" ] || printf '│ %-16s %s\n' 'Nextcloud HTTPS' "$(url "$ENV_HTTPS_PORT" https://localhost)"
40+
[ -z "${ENV_MAILPIT_PORT:-}" ] || printf '│ %-16s %s\n' 'Mailpit' "http://localhost:$ENV_MAILPIT_PORT"
41+
[ -z "${ENV_EUROOFFICE_PORT:-}" ] || printf '│ %-16s %s\n' 'EuroOffice' "http://localhost:$ENV_EUROOFFICE_PORT"
42+
[ -z "${ENV_PLAYWRIGHT_PORT:-}" ] || printf '│ %-16s %s\n' 'Playwright' "http://localhost:$ENV_PLAYWRIGHT_PORT"
43+
[ -z "${ENV_SIGNAL_PORT:-}" ] || printf '│ %-16s %s\n' 'Signal' "http://localhost:$ENV_SIGNAL_PORT"
44+
printf '│\n'
45+
[ -z "${ENV_NEXTCLOUD_BRANCH:-}" ] || printf '│ %-16s %s\n' 'Nextcloud branch' "$ENV_NEXTCLOUD_BRANCH"
46+
[ -z "${ENV_ADMIN_USER:-}" ] || printf '│ %-16s %s\n' 'Admin user' "$ENV_ADMIN_USER"
47+
[ -z "${ENV_ADMIN_PASSWORD:-}" ] || printf '│ %-16s %s\n' 'Admin password' "$ENV_ADMIN_PASSWORD"
48+
printf '│\n'
49+
printf '└────────────────────────────────────────────\n'
50+
} > /proc/1/fd/2
51+
}
52+
53+
if ! wait_for_php_fpm; then
54+
echo 'Could not confirm PHP-FPM readiness; skipping environment banner.' >&2
55+
exit 0
56+
fi
57+
58+
emit_banner

docker-compose.yml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ services:
4848
- host.docker.internal:host-gateway
4949
nginx:
5050
image: ghcr.io/librecodecoop/nextcloud-dev-nginx:latest
51+
scale: 0
5152
# build:
5253
# context: .docker/
5354
# dockerfile: Dockerfile.nginx
@@ -60,11 +61,30 @@ services:
6061
- ./volumes/nginx/certs:/certs
6162
ports:
6263
- target: 80
63-
published: "${HTTP_PORT:-80-99}"
64+
published: "${HTTP_PORT:-80}"
6465
host_ip: ${IP_BIND:-127.0.0.1}
6566
- target: 443
66-
published: "${HTTPS_PORT:-443-462}"
67+
published: "${HTTPS_PORT:-443}"
6768
host_ip: ${IP_BIND:-127.0.0.1}
69+
nginx-port-selector:
70+
build:
71+
context: .docker/
72+
dockerfile: Dockerfile.nginx-port-selector
73+
volumes:
74+
- ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock
75+
- .:${PWD}:ro
76+
working_dir: ${PWD}
77+
environment:
78+
- HTTP_PORT
79+
- HTTPS_PORT
80+
- IP_BIND
81+
- PROJECT_DIR=${PWD}
82+
- NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER:-admin}
83+
- NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD:-admin}
84+
- VERSION_NEXTCLOUD=${VERSION_NEXTCLOUD:-master}
85+
restart: "no"
86+
depends_on:
87+
- nextcloud
6888
mailpit:
6989
image: axllent/mailpit
7090
ports:

0 commit comments

Comments
 (0)