From e429738ece000ca5cdf969782de23bb89463fc46 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 001/153] feat: generate dynamic proxy dashboard
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy-dashboard.sh | 202 +++++++++++++++++++++++++++++
1 file changed, 202 insertions(+)
create mode 100755 .docker/scripts/proxy-dashboard.sh
diff --git a/.docker/scripts/proxy-dashboard.sh b/.docker/scripts/proxy-dashboard.sh
new file mode 100755
index 00000000..8258c065
--- /dev/null
+++ b/.docker/scripts/proxy-dashboard.sh
@@ -0,0 +1,202 @@
+#!/bin/sh
+
+set -eu
+
+html_dir="${HTML_DIR:-/html}"
+output="$html_dir/index.html"
+
+html_escape() {
+ printf '%s' "$1" |
+ sed \
+ -e 's/&/\&/g' \
+ -e 's/\</g' \
+ -e 's/>/\>/g' \
+ -e 's/"/\"/g' \
+ -e "s/'/\'/g"
+}
+
+service_title() {
+ case "$1" in
+ nginx)
+ printf 'Nextcloud'
+ ;;
+ mailpit)
+ printf 'Mailpit'
+ ;;
+ playwright)
+ printf 'Playwright'
+ ;;
+ eurooffice)
+ printf 'EuroOffice'
+ ;;
+ signal-gateway)
+ printf 'Signal Gateway'
+ ;;
+ *)
+ printf '%s' "$1"
+ ;;
+ esac
+}
+
+active_hosts() {
+ docker ps --filter network=librecode-dev-proxy --format '{{.ID}}' |
+ while IFS= read -r container; do
+ [ -n "$container" ] || continue
+
+ virtual_hosts="$(docker inspect \
+ --format '{{range .Config.Env}}{{println .}}{{end}}' \
+ "$container" |
+ sed -n 's/^VIRTUAL_HOST=//p' |
+ head -n 1)"
+
+ [ -n "$virtual_hosts" ] || continue
+
+ project="$(docker inspect \
+ --format '{{ index .Config.Labels "com.docker.compose.project" }}' \
+ "$container" 2>/dev/null || true)"
+ service="$(docker inspect \
+ --format '{{ index .Config.Labels "com.docker.compose.service" }}' \
+ "$container" 2>/dev/null || true)"
+
+ [ -n "$project" ] || project="Docker"
+ [ -n "$service" ] || service="Service"
+
+ printf '%s\n' "$virtual_hosts" |
+ tr ',' '\n' |
+ while IFS= read -r host; do
+ host="$(printf '%s' "$host" | tr -d '[:space:]')"
+ case "$host" in
+ *.localhost)
+ printf '%s|%s|%s\n' "$project" "$service" "$host"
+ ;;
+ esac
+ done
+ done |
+ sort -u
+}
+
+generate_dashboard() {
+ tmp="${output}.tmp"
+ hosts_file="$(mktemp)"
+ trap 'rm -f "$hosts_file" "$tmp"' EXIT INT TERM
+
+ active_hosts > "$hosts_file"
+
+ cat > "$tmp" <<'EOF'
+
+
+
+
+
+ LibreCode Development Proxy
+
+
+
+
+ π LibreCode Development Proxy
+
+ Environment not found
+ There is no active development service for .
+ Did you mean ?
+
+ The shared development proxy is running.
+ Active environments
+EOF
+
+ if [ ! -s "$hosts_file" ]; then
+ printf ' No development environments are active.
\n' >> "$tmp"
+ else
+ current_project=""
+ while IFS='|' read -r project service host; do
+ project_html="$(html_escape "$project")"
+ service_html="$(html_escape "$(service_title "$service")")"
+ host_html="$(html_escape "$host")"
+
+ if [ "$project" != "$current_project" ]; then
+ if [ -n "$current_project" ]; then
+ printf ' \n' >> "$tmp"
+ fi
+ printf ' %s
\n \n' "$project_html" >> "$tmp"
+ current_project="$project"
+ fi
+
+ printf ' - %s: https://%s
\n' \
+ "$service_html" "$host_html" "$host_html" "$host_html" >> "$tmp"
+ done < "$hosts_file"
+ printf '
\n' >> "$tmp"
+ fi
+
+ cat >> "$tmp" <<'EOF'
+ This page is generated from the services currently connected to the shared development proxy.
+
+
+
+
+EOF
+
+ mv "$tmp" "$output"
+ rm -f "$hosts_file"
+ trap - EXIT INT TERM
+}
+
+mkdir -p "$html_dir"
+generate_dashboard
+
+while true; do
+ docker events \
+ --filter type=container \
+ --filter event=start \
+ --filter event=stop \
+ --filter event=die \
+ --filter event=destroy \
+ --format '{{.Status}}' |
+ while IFS= read -r _event; do
+ generate_dashboard
+ done
+
+ sleep 1
+done
From abf38757a5a6b6c47d022f75552f5de4597843eb Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 002/153] feat: serve proxy dashboard and friendly not found
page
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/dashboard.conf | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
create mode 100644 .docker/nginx-proxy/dashboard.conf
diff --git a/.docker/nginx-proxy/dashboard.conf b/.docker/nginx-proxy/dashboard.conf
new file mode 100644
index 00000000..37fe983c
--- /dev/null
+++ b/.docker/nginx-proxy/dashboard.conf
@@ -0,0 +1,23 @@
+server {
+ listen 80 default_server;
+ listen [::]:80 default_server;
+
+ server_name _;
+
+ root /usr/share/nginx/html;
+ index index.html;
+
+ location = /index.html {
+ internal;
+ }
+
+ location / {
+ if ($host != localhost) {
+ return 404;
+ }
+
+ try_files /index.html =404;
+ }
+
+ error_page 404 =404 /index.html;
+}
From dbfcbd063ee34d55a83389df7fd015ac889da6fe Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 003/153] refactor: retire static localhost virtual host
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/localhost.conf | 16 +++-------------
1 file changed, 3 insertions(+), 13 deletions(-)
diff --git a/.docker/nginx-proxy/localhost.conf b/.docker/nginx-proxy/localhost.conf
index fef7b775..bf2a2824 100644
--- a/.docker/nginx-proxy/localhost.conf
+++ b/.docker/nginx-proxy/localhost.conf
@@ -1,13 +1,3 @@
-server {
- listen 80;
- listen [::]:80;
-
- server_name localhost;
-
- root /usr/share/nginx/html;
- index index.html;
-
- location / {
- try_files /index.html =404;
- }
-}
+# localhost is now provided by the proxy-dashboard service.
+# This file remains temporarily so existing coordinator installations can
+# replace the previous static server block in the shared configuration volume.
From 98734e526880e65b6fc6e0012ed35b7ab7e8d78d Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 004/153] feat: add dynamic proxy dashboard services
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/docker-compose.proxy.yml | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml
index d0767fa7..9bdd4353 100644
--- a/.docker/docker-compose.proxy.yml
+++ b/.docker/docker-compose.proxy.yml
@@ -17,6 +17,37 @@ services:
- librecode-dev-proxy-html:/usr/share/nginx/html
environment:
- ENABLE_IPV6=true
+ - DEFAULT_HOST=localhost
+ networks:
+ - proxy
+
+ proxy-dashboard:
+ image: nginx:alpine
+ container_name: librecode-dev-proxy-dashboard
+ restart: unless-stopped
+ labels:
+ coop.librecode.dev-proxy-dashboard: "true"
+ volumes:
+ - librecode-dev-proxy-html:/usr/share/nginx/html:ro
+ - ./.docker/nginx-proxy/dashboard.conf:/etc/nginx/conf.d/default.conf:ro
+ environment:
+ - VIRTUAL_HOST=localhost
+ - VIRTUAL_PORT=80
+ - SELF_SIGNED_HOST=localhost
+ networks:
+ - proxy
+
+ proxy-dashboard-updater:
+ image: docker:29-cli
+ container_name: librecode-dev-proxy-dashboard-updater
+ restart: unless-stopped
+ labels:
+ coop.librecode.dev-proxy-dashboard-updater: "true"
+ volumes:
+ - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock:ro
+ - librecode-dev-proxy-html:/html
+ - ./.docker/scripts/proxy-dashboard.sh:/usr/local/bin/proxy-dashboard.sh:ro
+ command: /bin/sh /usr/local/bin/proxy-dashboard.sh
networks:
- proxy
From 2e2a6719b06b4014bfb5b50461d22c1f7718e59e Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 005/153] fix: reconcile shared proxy services when reused
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy-coordinator.sh | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
mode change 100644 => 100755 .docker/scripts/proxy-coordinator.sh
diff --git a/.docker/scripts/proxy-coordinator.sh b/.docker/scripts/proxy-coordinator.sh
old mode 100644
new mode 100755
index 8ac711c8..d6b9d519
--- a/.docker/scripts/proxy-coordinator.sh
+++ b/.docker/scripts/proxy-coordinator.sh
@@ -234,6 +234,7 @@ compose config --quiet
install_proxy_assets
if proxy_is_ready; then
+ proxy_compose up --detach
proxy_state=reused
else
ensure_ports_available
@@ -251,4 +252,4 @@ if ! report_environment_ready; then
echo 'Could not print environment banner.' >&2
fi
-success "$proxy_state"
\ No newline at end of file
+success "$proxy_state"
From 672d7b51d13eede6aae6f2f840d51f70a46cfba0 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 006/153] fix: route unknown localhost hosts to dashboard
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/docker-compose.proxy.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml
index 9bdd4353..4f815bd8 100644
--- a/.docker/docker-compose.proxy.yml
+++ b/.docker/docker-compose.proxy.yml
@@ -17,7 +17,6 @@ services:
- librecode-dev-proxy-html:/usr/share/nginx/html
environment:
- ENABLE_IPV6=true
- - DEFAULT_HOST=localhost
networks:
- proxy
@@ -31,8 +30,9 @@ services:
- librecode-dev-proxy-html:/usr/share/nginx/html:ro
- ./.docker/nginx-proxy/dashboard.conf:/etc/nginx/conf.d/default.conf:ro
environment:
- - VIRTUAL_HOST=localhost
+ - VIRTUAL_HOST=localhost,~^.+[.]localhost$$
- VIRTUAL_PORT=80
+ - CERT_NAME=localhost
- SELF_SIGNED_HOST=localhost
networks:
- proxy
From 06b2b41acdbc2148498a0632cbe802cda508174e Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 007/153] fix: use wildcard host for proxy dashboard fallback
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/docker-compose.proxy.yml | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml
index 4f815bd8..e013d730 100644
--- a/.docker/docker-compose.proxy.yml
+++ b/.docker/docker-compose.proxy.yml
@@ -30,10 +30,9 @@ services:
- librecode-dev-proxy-html:/usr/share/nginx/html:ro
- ./.docker/nginx-proxy/dashboard.conf:/etc/nginx/conf.d/default.conf:ro
environment:
- - VIRTUAL_HOST=localhost,~^.+[.]localhost$$
+ - VIRTUAL_HOST=localhost,*.localhost
- VIRTUAL_PORT=80
- - CERT_NAME=localhost
- - SELF_SIGNED_HOST=localhost
+ - SELF_SIGNED_HOST=localhost,*.localhost
networks:
- proxy
From f379db0fd5af468d176d9766f9fb4007e79b3e33 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 008/153] fix: keep proxy dashboard routes in sync
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy-dashboard.sh | 55 +++++++++++++++++++++---------
1 file changed, 39 insertions(+), 16 deletions(-)
diff --git a/.docker/scripts/proxy-dashboard.sh b/.docker/scripts/proxy-dashboard.sh
index 8258c065..c22bb91d 100755
--- a/.docker/scripts/proxy-dashboard.sh
+++ b/.docker/scripts/proxy-dashboard.sh
@@ -4,6 +4,8 @@ set -eu
html_dir="${HTML_DIR:-/html}"
output="$html_dir/index.html"
+proxy_network="${PROXY_NETWORK:-librecode-dev-proxy}"
+proxy_project="${PROXY_PROJECT:-librecode-dev-proxy}"
html_escape() {
printf '%s' "$1" |
@@ -39,18 +41,10 @@ service_title() {
}
active_hosts() {
- docker ps --filter network=librecode-dev-proxy --format '{{.ID}}' |
+ docker ps --filter "network=$proxy_network" --format '{{.ID}}' |
while IFS= read -r container; do
[ -n "$container" ] || continue
- virtual_hosts="$(docker inspect \
- --format '{{range .Config.Env}}{{println .}}{{end}}' \
- "$container" |
- sed -n 's/^VIRTUAL_HOST=//p' |
- head -n 1)"
-
- [ -n "$virtual_hosts" ] || continue
-
project="$(docker inspect \
--format '{{ index .Config.Labels "com.docker.compose.project" }}' \
"$container" 2>/dev/null || true)"
@@ -58,6 +52,17 @@ active_hosts() {
--format '{{ index .Config.Labels "com.docker.compose.service" }}' \
"$container" 2>/dev/null || true)"
+ # The shared proxy infrastructure exposes its own routing metadata, but it
+ # is not a development environment and must not appear in the dashboard.
+ [ "$project" != "$proxy_project" ] || continue
+
+ virtual_hosts="$(docker inspect \
+ --format '{{range .Config.Env}}{{println .}}{{end}}' \
+ "$container" |
+ sed -n 's/^VIRTUAL_HOST=//p' |
+ head -n 1)"
+
+ [ -n "$virtual_hosts" ] || continue
[ -n "$project" ] || project="Docker"
[ -n "$service" ] || service="Service"
@@ -67,6 +72,11 @@ active_hosts() {
host="$(printf '%s' "$host" | tr -d '[:space:]')"
case "$host" in
*.localhost)
+ case "$host" in
+ '*.'*|'~'*)
+ continue
+ ;;
+ esac
printf '%s|%s|%s\n' "$project" "$service" "$host"
;;
esac
@@ -187,13 +197,26 @@ mkdir -p "$html_dir"
generate_dashboard
while true; do
- docker events \
- --filter type=container \
- --filter event=start \
- --filter event=stop \
- --filter event=die \
- --filter event=destroy \
- --format '{{.Status}}' |
+ {
+ docker events \
+ --filter type=container \
+ --filter event=start \
+ --filter event=stop \
+ --filter event=die \
+ --filter event=destroy \
+ --format '{{.Status}}' &
+ container_events_pid=$!
+
+ docker events \
+ --filter type=network \
+ --filter "network=$proxy_network" \
+ --filter event=connect \
+ --filter event=disconnect \
+ --format '{{.Status}}' &
+ network_events_pid=$!
+
+ wait "$container_events_pid" "$network_events_pid"
+ } |
while IFS= read -r _event; do
generate_dashboard
done
From 78b0588b016f80cdb920c024aeb9ffe5b9998bfb Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 009/153] feat: extend nginx-proxy for dynamic dashboard
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/Dockerfile | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 .docker/nginx-proxy/Dockerfile
diff --git a/.docker/nginx-proxy/Dockerfile b/.docker/nginx-proxy/Dockerfile
new file mode 100644
index 00000000..6941b92b
--- /dev/null
+++ b/.docker/nginx-proxy/Dockerfile
@@ -0,0 +1,5 @@
+FROM nginxproxy/nginx-proxy:1.11-alpine
+
+COPY Procfile /app/Procfile
+COPY dashboard.tmpl /app/dashboard.tmpl
+COPY dashboard.conf /etc/nginx/conf.d/dashboard.conf
From 22a12ccd334475cc4593b9310d3cef14dc24ed20 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 010/153] feat: watch Docker state for dashboard
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/Procfile | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 .docker/nginx-proxy/Procfile
diff --git a/.docker/nginx-proxy/Procfile b/.docker/nginx-proxy/Procfile
new file mode 100644
index 00000000..dff02fed
--- /dev/null
+++ b/.docker/nginx-proxy/Procfile
@@ -0,0 +1,3 @@
+dockergen: docker-gen -watch -wait 100ms:500ms -event-filter event=connect -event-filter event=disconnect -notify "nginx -s reload" /app/nginx.tmpl /etc/nginx/conf.d/default.conf
+dashboard: docker-gen -watch -wait 100ms:500ms -event-filter event=connect -event-filter event=disconnect /app/dashboard.tmpl /usr/share/nginx/html/index.html
+nginx: nginx -g "daemon off;"
From 2f6e956e23f38fe9f0f54e0455783b5853c9ba30 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 011/153] feat: render dashboard from Docker metadata
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/dashboard.tmpl | 147 +++++++++++++++++++++++++++++
1 file changed, 147 insertions(+)
create mode 100644 .docker/nginx-proxy/dashboard.tmpl
diff --git a/.docker/nginx-proxy/dashboard.tmpl b/.docker/nginx-proxy/dashboard.tmpl
new file mode 100644
index 00000000..b8c31fb0
--- /dev/null
+++ b/.docker/nginx-proxy/dashboard.tmpl
@@ -0,0 +1,147 @@
+{{- $routes := list -}}
+{{- range $hostname, $containers := groupByMulti . "Env.VIRTUAL_HOST" "," -}}
+ {{- $hostname = trim $hostname -}}
+ {{- if or (eq $hostname "") (eq $hostname "localhost") (eq $hostname "*.localhost") -}}
+ {{- continue -}}
+ {{- end -}}
+
+ {{- range $container := $containers -}}
+ {{- $onProxyNetwork := false -}}
+ {{- range $network := $container.Networks -}}
+ {{- if eq $network.Name "librecode-dev-proxy" -}}
+ {{- $onProxyNetwork = true -}}
+ {{- end -}}
+ {{- end -}}
+ {{- if not $onProxyNetwork -}}
+ {{- continue -}}
+ {{- end -}}
+
+ {{- $project := index $container.Labels "com.docker.compose.project" -}}
+ {{- $service := index $container.Labels "com.docker.compose.service" -}}
+ {{- if not $project -}}
+ {{- $project = "Docker" -}}
+ {{- end -}}
+ {{- if not $service -}}
+ {{- $service = "Service" -}}
+ {{- end -}}
+
+ {{- $routes = append $routes (dict "host" $hostname "project" $project "service" $service) -}}
+ {{- break -}}
+ {{- end -}}
+{{- end -}}
+
+
+
+
+
+ LibreCode Development Proxy
+
+
+
+
+ π LibreCode Development Proxy
+
+ Environment not found
+ There is no active development service for .
+ Did you mean ?
+
+
+ The shared development proxy is running.
+ Active environments
+
+ This page is generated from the routes currently connected to the shared development proxy.
+
+
+
+
+
+
From 73b927244fd31b285e85811393010b469efffb4b Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 012/153] refactor: serve dashboard inside nginx-proxy
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/dashboard.conf | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.docker/nginx-proxy/dashboard.conf b/.docker/nginx-proxy/dashboard.conf
index 37fe983c..52e31c3f 100644
--- a/.docker/nginx-proxy/dashboard.conf
+++ b/.docker/nginx-proxy/dashboard.conf
@@ -1,6 +1,6 @@
server {
- listen 80 default_server;
- listen [::]:80 default_server;
+ listen 8080;
+ listen [::]:8080;
server_name _;
From db330e16975d69be953dc654f45868983e4f08ec Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 013/153] refactor: keep dashboard inside proxy service
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/docker-compose.proxy.yml | 33 ++++----------------------------
1 file changed, 4 insertions(+), 29 deletions(-)
diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml
index e013d730..1262fcf5 100644
--- a/.docker/docker-compose.proxy.yml
+++ b/.docker/docker-compose.proxy.yml
@@ -1,6 +1,8 @@
services:
nginx-proxy:
- image: nginxproxy/nginx-proxy:1.11-alpine
+ build:
+ context: ./.docker/nginx-proxy
+ dockerfile: Dockerfile
container_name: librecode-dev-proxy
restart: unless-stopped
labels:
@@ -17,39 +19,12 @@ services:
- librecode-dev-proxy-html:/usr/share/nginx/html
environment:
- ENABLE_IPV6=true
- networks:
- - proxy
-
- proxy-dashboard:
- image: nginx:alpine
- container_name: librecode-dev-proxy-dashboard
- restart: unless-stopped
- labels:
- coop.librecode.dev-proxy-dashboard: "true"
- volumes:
- - librecode-dev-proxy-html:/usr/share/nginx/html:ro
- - ./.docker/nginx-proxy/dashboard.conf:/etc/nginx/conf.d/default.conf:ro
- environment:
- VIRTUAL_HOST=localhost,*.localhost
- - VIRTUAL_PORT=80
+ - VIRTUAL_PORT=8080
- SELF_SIGNED_HOST=localhost,*.localhost
networks:
- proxy
- proxy-dashboard-updater:
- image: docker:29-cli
- container_name: librecode-dev-proxy-dashboard-updater
- restart: unless-stopped
- labels:
- coop.librecode.dev-proxy-dashboard-updater: "true"
- volumes:
- - ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock:ro
- - librecode-dev-proxy-html:/html
- - ./.docker/scripts/proxy-dashboard.sh:/usr/local/bin/proxy-dashboard.sh:ro
- command: /bin/sh /usr/local/bin/proxy-dashboard.sh
- networks:
- - proxy
-
ssl-companion:
image: sebastienheyd/self-signed-proxy-companion:latest
container_name: librecode-dev-proxy-ssl-companion
From 912d5a52a05be32f2686cd75c53a773afa8d1e6f Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 014/153] refactor: let proxy own dashboard assets
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy-coordinator.sh | 13 -------------
1 file changed, 13 deletions(-)
diff --git a/.docker/scripts/proxy-coordinator.sh b/.docker/scripts/proxy-coordinator.sh
index d6b9d519..d9a1c9dc 100755
--- a/.docker/scripts/proxy-coordinator.sh
+++ b/.docker/scripts/proxy-coordinator.sh
@@ -206,17 +206,6 @@ report_environment_ready() {
nextcloud sh /var/www/scripts/report-environment-ready
}
-install_proxy_assets() {
- docker run --rm \
- -v librecode-dev-proxy-conf:/conf \
- -v librecode-dev-proxy-html:/html \
- -v "$PROJECT_DIR/.docker/nginx-proxy:/source:ro" \
- alpine sh -c '
- cp /source/localhost.conf /conf/librecode-localhost.conf
- cp /source/index.html /html/index.html
- '
-}
-
success() {
case "$1" in
reused)
@@ -231,8 +220,6 @@ success() {
echo "Validating Compose project ${project} at ${PROJECT_DIR}."
compose config --quiet
-install_proxy_assets
-
if proxy_is_ready; then
proxy_compose up --detach
proxy_state=reused
From a4e1d6d2500b76adff12d385450ec2f132a0f579 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 015/153] refactor: remove dashboard updater service script
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy-dashboard.sh | 225 -----------------------------
1 file changed, 225 deletions(-)
delete mode 100755 .docker/scripts/proxy-dashboard.sh
diff --git a/.docker/scripts/proxy-dashboard.sh b/.docker/scripts/proxy-dashboard.sh
deleted file mode 100755
index c22bb91d..00000000
--- a/.docker/scripts/proxy-dashboard.sh
+++ /dev/null
@@ -1,225 +0,0 @@
-#!/bin/sh
-
-set -eu
-
-html_dir="${HTML_DIR:-/html}"
-output="$html_dir/index.html"
-proxy_network="${PROXY_NETWORK:-librecode-dev-proxy}"
-proxy_project="${PROXY_PROJECT:-librecode-dev-proxy}"
-
-html_escape() {
- printf '%s' "$1" |
- sed \
- -e 's/&/\&/g' \
- -e 's/\</g' \
- -e 's/>/\>/g' \
- -e 's/"/\"/g' \
- -e "s/'/\'/g"
-}
-
-service_title() {
- case "$1" in
- nginx)
- printf 'Nextcloud'
- ;;
- mailpit)
- printf 'Mailpit'
- ;;
- playwright)
- printf 'Playwright'
- ;;
- eurooffice)
- printf 'EuroOffice'
- ;;
- signal-gateway)
- printf 'Signal Gateway'
- ;;
- *)
- printf '%s' "$1"
- ;;
- esac
-}
-
-active_hosts() {
- docker ps --filter "network=$proxy_network" --format '{{.ID}}' |
- while IFS= read -r container; do
- [ -n "$container" ] || continue
-
- project="$(docker inspect \
- --format '{{ index .Config.Labels "com.docker.compose.project" }}' \
- "$container" 2>/dev/null || true)"
- service="$(docker inspect \
- --format '{{ index .Config.Labels "com.docker.compose.service" }}' \
- "$container" 2>/dev/null || true)"
-
- # The shared proxy infrastructure exposes its own routing metadata, but it
- # is not a development environment and must not appear in the dashboard.
- [ "$project" != "$proxy_project" ] || continue
-
- virtual_hosts="$(docker inspect \
- --format '{{range .Config.Env}}{{println .}}{{end}}' \
- "$container" |
- sed -n 's/^VIRTUAL_HOST=//p' |
- head -n 1)"
-
- [ -n "$virtual_hosts" ] || continue
- [ -n "$project" ] || project="Docker"
- [ -n "$service" ] || service="Service"
-
- printf '%s\n' "$virtual_hosts" |
- tr ',' '\n' |
- while IFS= read -r host; do
- host="$(printf '%s' "$host" | tr -d '[:space:]')"
- case "$host" in
- *.localhost)
- case "$host" in
- '*.'*|'~'*)
- continue
- ;;
- esac
- printf '%s|%s|%s\n' "$project" "$service" "$host"
- ;;
- esac
- done
- done |
- sort -u
-}
-
-generate_dashboard() {
- tmp="${output}.tmp"
- hosts_file="$(mktemp)"
- trap 'rm -f "$hosts_file" "$tmp"' EXIT INT TERM
-
- active_hosts > "$hosts_file"
-
- cat > "$tmp" <<'EOF'
-
-
-
-
-
- LibreCode Development Proxy
-
-
-
-
- π LibreCode Development Proxy
-
- Environment not found
- There is no active development service for .
- Did you mean ?
-
- The shared development proxy is running.
- Active environments
-EOF
-
- if [ ! -s "$hosts_file" ]; then
- printf ' No development environments are active.
\n' >> "$tmp"
- else
- current_project=""
- while IFS='|' read -r project service host; do
- project_html="$(html_escape "$project")"
- service_html="$(html_escape "$(service_title "$service")")"
- host_html="$(html_escape "$host")"
-
- if [ "$project" != "$current_project" ]; then
- if [ -n "$current_project" ]; then
- printf ' \n' >> "$tmp"
- fi
- printf ' %s
\n \n' "$project_html" >> "$tmp"
- current_project="$project"
- fi
-
- printf ' - %s: https://%s
\n' \
- "$service_html" "$host_html" "$host_html" "$host_html" >> "$tmp"
- done < "$hosts_file"
- printf '
\n' >> "$tmp"
- fi
-
- cat >> "$tmp" <<'EOF'
- This page is generated from the services currently connected to the shared development proxy.
-
-
-
-
-EOF
-
- mv "$tmp" "$output"
- rm -f "$hosts_file"
- trap - EXIT INT TERM
-}
-
-mkdir -p "$html_dir"
-generate_dashboard
-
-while true; do
- {
- docker events \
- --filter type=container \
- --filter event=start \
- --filter event=stop \
- --filter event=die \
- --filter event=destroy \
- --format '{{.Status}}' &
- container_events_pid=$!
-
- docker events \
- --filter type=network \
- --filter "network=$proxy_network" \
- --filter event=connect \
- --filter event=disconnect \
- --format '{{.Status}}' &
- network_events_pid=$!
-
- wait "$container_events_pid" "$network_events_pid"
- } |
- while IFS= read -r _event; do
- generate_dashboard
- done
-
- sleep 1
-done
From 0c64f90f35e1f92ed036c6fea83e33c620d47f7f Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 016/153] refactor: remove legacy localhost proxy config
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/localhost.conf | 3 ---
1 file changed, 3 deletions(-)
delete mode 100644 .docker/nginx-proxy/localhost.conf
diff --git a/.docker/nginx-proxy/localhost.conf b/.docker/nginx-proxy/localhost.conf
deleted file mode 100644
index bf2a2824..00000000
--- a/.docker/nginx-proxy/localhost.conf
+++ /dev/null
@@ -1,3 +0,0 @@
-# localhost is now provided by the proxy-dashboard service.
-# This file remains temporarily so existing coordinator installations can
-# replace the previous static server block in the shared configuration volume.
From 288bdb197e98266fe953689f47a35c95358087b0 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 017/153] refactor: remove static proxy dashboard
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/index.html | 72 ----------------------------------
1 file changed, 72 deletions(-)
delete mode 100644 .docker/nginx-proxy/index.html
diff --git a/.docker/nginx-proxy/index.html b/.docker/nginx-proxy/index.html
deleted file mode 100644
index ea292373..00000000
--- a/.docker/nginx-proxy/index.html
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
-
-
- LibreCode Development Proxy
-
-
-
-
- π LibreCode Development Proxy
-
- The shared development proxy is running.
-
-
- Open your environment using the URL shown by
- docker compose up.
-
-
-
- The hostname is usually based on the project directory name:
-
-
-
- php83-master/
- β https://php83-master.localhost
-
-
-
- Requests to *.localhost are routed automatically.
-
-
-
-
From 84dba5d5be13c63098c805e94190505d45a08666 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 018/153] refactor: keep generated proxy files ephemeral
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/docker-compose.proxy.yml | 6 ------
1 file changed, 6 deletions(-)
diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml
index 1262fcf5..fe4f1ad3 100644
--- a/.docker/docker-compose.proxy.yml
+++ b/.docker/docker-compose.proxy.yml
@@ -13,10 +13,8 @@ services:
volumes:
- ${DOCKER_SOCKET:-/var/run/docker.sock}:/tmp/docker.sock:ro
- librecode-dev-proxy-vhost:/etc/nginx/vhost.d
- - librecode-dev-proxy-conf:/etc/nginx/conf.d
- librecode-dev-proxy-log:/etc/nginx/log
- librecode-dev-proxy-certs:/etc/nginx/certs:ro
- - librecode-dev-proxy-html:/usr/share/nginx/html
environment:
- ENABLE_IPV6=true
- VIRTUAL_HOST=localhost,*.localhost
@@ -46,11 +44,7 @@ networks:
volumes:
librecode-dev-proxy-vhost:
name: librecode-dev-proxy-vhost
- librecode-dev-proxy-conf:
- name: librecode-dev-proxy-conf
librecode-dev-proxy-log:
name: librecode-dev-proxy-log
librecode-dev-proxy-certs:
name: librecode-dev-proxy-certs
- librecode-dev-proxy-html:
- name: librecode-dev-proxy-html
From 3d724a0757764fed93fb7625c05c00f1e5f47238 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 019/153] feat: generate proxy config and dashboard together
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/docker-gen.cfg | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
create mode 100644 .docker/nginx-proxy/docker-gen.cfg
diff --git a/.docker/nginx-proxy/docker-gen.cfg b/.docker/nginx-proxy/docker-gen.cfg
new file mode 100644
index 00000000..484d5801
--- /dev/null
+++ b/.docker/nginx-proxy/docker-gen.cfg
@@ -0,0 +1,19 @@
+[[config]]
+template = "/app/nginx.tmpl"
+dest = "/etc/nginx/conf.d/default.conf"
+watch = true
+wait = "100ms:500ms"
+notifycmd = "nginx -s reload"
+
+[[config]]
+template = "/dashboard/dashboard.conf.tmpl"
+dest = "/etc/nginx/conf.d/dashboard.conf"
+watch = true
+wait = "100ms:500ms"
+notifycmd = "nginx -s reload"
+
+[[config]]
+template = "/dashboard/dashboard.tmpl"
+dest = "/usr/share/nginx/html/index.html"
+watch = true
+wait = "100ms:500ms"
From 06ee3cb218ac45c93eef3cc7ba5c81c8f652ee59 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:07 -0300
Subject: [PATCH 020/153] feat: serve dashboard from localhost vhost
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/localhost_location_override | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 .docker/nginx-proxy/localhost_location_override
diff --git a/.docker/nginx-proxy/localhost_location_override b/.docker/nginx-proxy/localhost_location_override
new file mode 100644
index 00000000..a831b0aa
--- /dev/null
+++ b/.docker/nginx-proxy/localhost_location_override
@@ -0,0 +1,2 @@
+root /usr/share/nginx/html;
+try_files /index.html =404;
From 3e6e45bf87de3738bf3a7aa1f09da78ec08ff878 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 021/153] feat: render dashboard for unknown localhost hosts
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/*.localhost | 6 ++++++
1 file changed, 6 insertions(+)
create mode 100644 .docker/nginx-proxy/*.localhost
diff --git a/.docker/nginx-proxy/*.localhost b/.docker/nginx-proxy/*.localhost
new file mode 100644
index 00000000..a2450af0
--- /dev/null
+++ b/.docker/nginx-proxy/*.localhost
@@ -0,0 +1,6 @@
+root /usr/share/nginx/html;
+error_page 404 =404 /index.html;
+
+location = /index.html {
+ internal;
+}
From 1d2224f3676979189c932674cff154c012912864 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 022/153] feat: return 404 for unknown localhost hosts
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/*.localhost_location_override | 1 +
1 file changed, 1 insertion(+)
create mode 100644 .docker/nginx-proxy/*.localhost_location_override
diff --git a/.docker/nginx-proxy/*.localhost_location_override b/.docker/nginx-proxy/*.localhost_location_override
new file mode 100644
index 00000000..421c127f
--- /dev/null
+++ b/.docker/nginx-proxy/*.localhost_location_override
@@ -0,0 +1 @@
+return 404;
From 14f796bc41797be90cf4a6ae9f553a89c1a59091 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 023/153] refactor: run dashboard generator from stock proxy
image
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/Procfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.docker/nginx-proxy/Procfile b/.docker/nginx-proxy/Procfile
index dff02fed..69b47b8e 100644
--- a/.docker/nginx-proxy/Procfile
+++ b/.docker/nginx-proxy/Procfile
@@ -1,3 +1,3 @@
dockergen: docker-gen -watch -wait 100ms:500ms -event-filter event=connect -event-filter event=disconnect -notify "nginx -s reload" /app/nginx.tmpl /etc/nginx/conf.d/default.conf
-dashboard: docker-gen -watch -wait 100ms:500ms -event-filter event=connect -event-filter event=disconnect /app/dashboard.tmpl /usr/share/nginx/html/index.html
+dashboard: docker-gen -watch -wait 100ms:500ms -event-filter event=connect -event-filter event=disconnect /dashboard/dashboard.tmpl /usr/share/nginx/html/index.html
nginx: nginx -g "daemon off;"
From 3ae587287a3dc8aabdc9a6cd2f864296c559f57b Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 024/153] refactor: run shared proxy without local build
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/docker-compose.proxy.yml | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml
index fe4f1ad3..0abd44c5 100644
--- a/.docker/docker-compose.proxy.yml
+++ b/.docker/docker-compose.proxy.yml
@@ -1,24 +1,24 @@
services:
nginx-proxy:
- build:
- context: ./.docker/nginx-proxy
- dockerfile: Dockerfile
+ image: nginxproxy/nginx-proxy:1.11-alpine
container_name: librecode-dev-proxy
- restart: unless-stopped
labels:
coop.librecode.dev-proxy: "true"
+ working_dir: /dashboard
+ command: ["forego", "start", "-r"]
ports:
- "80:80"
- "443:443"
volumes:
- ${DOCKER_SOCKET:-/var/run/docker.sock}:/tmp/docker.sock:ro
- librecode-dev-proxy-vhost:/etc/nginx/vhost.d
+ - librecode-dev-proxy-assets:/dashboard:ro
- librecode-dev-proxy-log:/etc/nginx/log
- librecode-dev-proxy-certs:/etc/nginx/certs:ro
environment:
- ENABLE_IPV6=true
- VIRTUAL_HOST=localhost,*.localhost
- - VIRTUAL_PORT=8080
+ - VIRTUAL_PORT=80
- SELF_SIGNED_HOST=localhost,*.localhost
networks:
- proxy
@@ -26,7 +26,6 @@ services:
ssl-companion:
image: sebastienheyd/self-signed-proxy-companion:latest
container_name: librecode-dev-proxy-ssl-companion
- restart: unless-stopped
labels:
coop.librecode.dev-proxy-companion: "true"
volumes:
@@ -44,6 +43,8 @@ networks:
volumes:
librecode-dev-proxy-vhost:
name: librecode-dev-proxy-vhost
+ librecode-dev-proxy-assets:
+ name: librecode-dev-proxy-assets
librecode-dev-proxy-log:
name: librecode-dev-proxy-log
librecode-dev-proxy-certs:
From f8a49d3d2b6514c17c1c9c27b9a27cc79784f88d Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 025/153] refactor: run proxy coordinator without local build
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
docker-compose.yml | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/docker-compose.yml b/docker-compose.yml
index c44c5947..c8c5a29d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -64,11 +64,13 @@ services:
- VIRTUAL_PORT=80
- SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}.localhost
proxy-coordinator:
- build:
- context: .docker/
- dockerfile: Dockerfile.proxy-coordinator
+ image: docker:29-cli
+ entrypoint: ["/bin/sh", "/usr/local/bin/proxy-coordinator.sh"]
+ labels:
+ coop.librecode.dev-proxy-client: "true"
volumes:
- ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock
+ - ./.docker/scripts/proxy-coordinator.sh:/usr/local/bin/proxy-coordinator.sh:ro
- .:${PWD}:ro
working_dir: ${PWD}
environment:
@@ -77,6 +79,7 @@ services:
- NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD:-admin}
- VERSION_NEXTCLOUD=${VERSION_NEXTCLOUD:-master}
restart: "no"
+ stop_grace_period: 15s
depends_on:
- nextcloud
mailpit:
From fb95f2c757991debfd8802d67210c82f7419ba15 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 026/153] feat: release shared proxy with last environment
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy-coordinator.sh | 105 +++++++++++++++++++++++++--
1 file changed, 99 insertions(+), 6 deletions(-)
diff --git a/.docker/scripts/proxy-coordinator.sh b/.docker/scripts/proxy-coordinator.sh
index d9a1c9dc..96c673c1 100755
--- a/.docker/scripts/proxy-coordinator.sh
+++ b/.docker/scripts/proxy-coordinator.sh
@@ -5,6 +5,9 @@ set -eu
proxy_project=librecode-dev-proxy
proxy_network=librecode-dev-proxy
proxy_label=coop.librecode.dev-proxy=true
+proxy_client_label=coop.librecode.dev-proxy-client=true
+proxy_assets_volume=librecode-dev-proxy-assets
+proxy_vhost_volume=librecode-dev-proxy-vhost
compose_project() {
docker inspect \
@@ -124,7 +127,6 @@ start_proxy() {
return 0
fi
- # Another checkout may have created the shared proxy concurrently.
if proxy_is_ready; then
return 0
fi
@@ -140,6 +142,55 @@ start_proxy() {
exit 1
}
+copy_to_named_volume() {
+ volume="$1"
+ source="$2"
+ destination="$3"
+
+ docker run --rm -i \
+ -v "$volume:/target" \
+ docker:29-cli \
+ sh -c 'cat > "/target/$1"' sh "$destination" \
+ < "$source"
+}
+
+install_proxy_assets() {
+ docker volume create "$proxy_assets_volume" >/dev/null
+ docker volume create "$proxy_vhost_volume" >/dev/null
+
+ docker run --rm \
+ -v "$proxy_assets_volume:/target" \
+ docker:29-cli \
+ sh -c 'rm -f /target/Procfile /target/dashboard.tmpl'
+
+ copy_to_named_volume \
+ "$proxy_assets_volume" \
+ "$PROJECT_DIR/.docker/nginx-proxy/Procfile" \
+ Procfile
+ copy_to_named_volume \
+ "$proxy_assets_volume" \
+ "$PROJECT_DIR/.docker/nginx-proxy/dashboard.tmpl" \
+ dashboard.tmpl
+
+ docker run --rm \
+ -v "$proxy_vhost_volume:/target" \
+ docker:29-cli \
+ sh -c 'rm -f /target/librecode-localhost.conf /target/localhost /target/localhost_location_override /target/\*.localhost /target/\*.localhost_location_override'
+
+ copy_to_named_volume \
+ "$proxy_vhost_volume" \
+ "$PROJECT_DIR/.docker/nginx-proxy/localhost_location_override" \
+ localhost_location_override
+ copy_to_named_volume \
+ "$proxy_vhost_volume" \
+ "$PROJECT_DIR/.docker/nginx-proxy/*.localhost" \
+ '*.localhost'
+ copy_to_named_volume \
+ "$proxy_vhost_volume" \
+ "$PROJECT_DIR/.docker/nginx-proxy/*.localhost_location_override" \
+ '*.localhost_location_override'
+}
+
running_services="$(compose ps --status running --services)"
service_is_running() {
@@ -206,20 +257,55 @@ report_environment_ready() {
nextcloud sh /var/www/scripts/report-environment-ready
}
+other_proxy_client_is_running() {
+ docker ps \
+ --filter "label=$proxy_client_label" \
+ --format '{{.Label "com.docker.compose.project"}}' |
+ grep -v -x "$project" |
+ grep -q .
+}
+
+release_proxy_if_unused() {
+ if other_proxy_client_is_running; then
+ echo 'β
Shared development proxy is still used by another environment.'
+ return 0
+ fi
+
+ sleep 1
+
+ if other_proxy_client_is_running; then
+ echo 'β
Shared development proxy is still used by another environment.'
+ return 0
+ fi
+
+ echo 'Stopping unused shared development proxy.'
+ if ! proxy_compose down --remove-orphans; then
+ echo 'Could not stop the unused shared development proxy.' >&2
+ fi
+}
+
+shutdown() {
+ trap - INT TERM HUP
+ release_proxy_if_unused
+ exit 0
+}
+
success() {
case "$1" in
reused)
- echo 'β
Existing LibreCode development proxy reused. Coordinator exiting normally.'
- ;;
- started)
- echo 'β
Development proxy started successfully. Coordinator exiting normally.'
- ;;
+ echo 'β
Existing LibreCode development proxy reused. Coordinator lease is active.'
+ ;;
+ started)
+ echo 'β
Development proxy started successfully. Coordinator lease is active.'
+ ;;
esac
}
echo "Validating Compose project ${project} at ${PROJECT_DIR}."
compose config --quiet
+install_proxy_assets
+
if proxy_is_ready; then
proxy_compose up --detach
proxy_state=reused
@@ -240,3 +326,10 @@ if ! report_environment_ready; then
fi
success "$proxy_state"
+
+trap shutdown INT TERM HUP
+
+while :; do
+ sleep 3600 &
+ wait "$!" || true
+done
From 6e35c04790780c01bdd89577318a16affda7323f Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 027/153] refactor: remove local proxy image build
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/Dockerfile | 5 -----
1 file changed, 5 deletions(-)
delete mode 100644 .docker/nginx-proxy/Dockerfile
diff --git a/.docker/nginx-proxy/Dockerfile b/.docker/nginx-proxy/Dockerfile
deleted file mode 100644
index 6941b92b..00000000
--- a/.docker/nginx-proxy/Dockerfile
+++ /dev/null
@@ -1,5 +0,0 @@
-FROM nginxproxy/nginx-proxy:1.11-alpine
-
-COPY Procfile /app/Procfile
-COPY dashboard.tmpl /app/dashboard.tmpl
-COPY dashboard.conf /etc/nginx/conf.d/dashboard.conf
From bac090a47091ce5698871bfec04f50b3376607e9 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 028/153] refactor: remove unused docker-gen config
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/docker-gen.cfg | 19 -------------------
1 file changed, 19 deletions(-)
delete mode 100644 .docker/nginx-proxy/docker-gen.cfg
diff --git a/.docker/nginx-proxy/docker-gen.cfg b/.docker/nginx-proxy/docker-gen.cfg
deleted file mode 100644
index 484d5801..00000000
--- a/.docker/nginx-proxy/docker-gen.cfg
+++ /dev/null
@@ -1,19 +0,0 @@
-[[config]]
-template = "/app/nginx.tmpl"
-dest = "/etc/nginx/conf.d/default.conf"
-watch = true
-wait = "100ms:500ms"
-notifycmd = "nginx -s reload"
-
-[[config]]
-template = "/dashboard/dashboard.conf.tmpl"
-dest = "/etc/nginx/conf.d/dashboard.conf"
-watch = true
-wait = "100ms:500ms"
-notifycmd = "nginx -s reload"
-
-[[config]]
-template = "/dashboard/dashboard.tmpl"
-dest = "/usr/share/nginx/html/index.html"
-watch = true
-wait = "100ms:500ms"
From ed58a8f682c46a237d5bdf4b85934aaf657a0934 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 029/153] refactor: remove separate dashboard server config
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/dashboard.conf | 23 -----------------------
1 file changed, 23 deletions(-)
delete mode 100644 .docker/nginx-proxy/dashboard.conf
diff --git a/.docker/nginx-proxy/dashboard.conf b/.docker/nginx-proxy/dashboard.conf
deleted file mode 100644
index 52e31c3f..00000000
--- a/.docker/nginx-proxy/dashboard.conf
+++ /dev/null
@@ -1,23 +0,0 @@
-server {
- listen 8080;
- listen [::]:8080;
-
- server_name _;
-
- root /usr/share/nginx/html;
- index index.html;
-
- location = /index.html {
- internal;
- }
-
- location / {
- if ($host != localhost) {
- return 404;
- }
-
- try_files /index.html =404;
- }
-
- error_page 404 =404 /index.html;
-}
From 245f9daf695290c74de8659a96edd82c8f14d6b6 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 030/153] refactor: remove local coordinator image build
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/Dockerfile.proxy-coordinator | 9 ---------
1 file changed, 9 deletions(-)
delete mode 100644 .docker/Dockerfile.proxy-coordinator
diff --git a/.docker/Dockerfile.proxy-coordinator b/.docker/Dockerfile.proxy-coordinator
deleted file mode 100644
index fcde7fca..00000000
--- a/.docker/Dockerfile.proxy-coordinator
+++ /dev/null
@@ -1,9 +0,0 @@
-FROM docker:29-cli
-
-RUN apk add --no-cache docker-cli-compose
-
-COPY scripts/proxy-coordinator.sh /usr/local/bin/proxy-coordinator.sh
-
-RUN chmod +x /usr/local/bin/proxy-coordinator.sh
-
-ENTRYPOINT ["/usr/local/bin/proxy-coordinator.sh"]
From 3a0aead66442db71134c420bdd734ee88be696e0 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 031/153] refactor: keep shared proxy network external
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/docker-compose.proxy.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml
index 0abd44c5..a98bf158 100644
--- a/.docker/docker-compose.proxy.yml
+++ b/.docker/docker-compose.proxy.yml
@@ -39,6 +39,7 @@ services:
networks:
proxy:
name: librecode-dev-proxy
+ external: true
volumes:
librecode-dev-proxy-vhost:
From 221533439a6b8863a8579ec51c323f842c88de40 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 032/153] fix: ensure shared proxy network exists
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy-coordinator.sh | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/.docker/scripts/proxy-coordinator.sh b/.docker/scripts/proxy-coordinator.sh
index 96c673c1..d9725f20 100755
--- a/.docker/scripts/proxy-coordinator.sh
+++ b/.docker/scripts/proxy-coordinator.sh
@@ -122,6 +122,19 @@ ensure_ports_available() {
done
}
+ensure_proxy_network() {
+ if docker network inspect "$proxy_network" >/dev/null 2>&1; then
+ return 0
+ fi
+
+ if docker network create "$proxy_network" >/dev/null 2>&1; then
+ return 0
+ fi
+
+ # Another checkout may have created it concurrently.
+ docker network inspect "$proxy_network" >/dev/null
+}
+
start_proxy() {
if proxy_compose up --detach; then
return 0
@@ -304,6 +317,7 @@ success() {
echo "Validating Compose project ${project} at ${PROJECT_DIR}."
compose config --quiet
+ensure_proxy_network
install_proxy_assets
if proxy_is_ready; then
From d4fa66fd75c7e0a38fb09ec4890e539331866030 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 033/153] feat: generate proxy config and dashboard together
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/docker-gen.cfg | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
create mode 100644 .docker/nginx-proxy/docker-gen.cfg
diff --git a/.docker/nginx-proxy/docker-gen.cfg b/.docker/nginx-proxy/docker-gen.cfg
new file mode 100644
index 00000000..484d5801
--- /dev/null
+++ b/.docker/nginx-proxy/docker-gen.cfg
@@ -0,0 +1,19 @@
+[[config]]
+template = "/app/nginx.tmpl"
+dest = "/etc/nginx/conf.d/default.conf"
+watch = true
+wait = "100ms:500ms"
+notifycmd = "nginx -s reload"
+
+[[config]]
+template = "/dashboard/dashboard.conf.tmpl"
+dest = "/etc/nginx/conf.d/dashboard.conf"
+watch = true
+wait = "100ms:500ms"
+notifycmd = "nginx -s reload"
+
+[[config]]
+template = "/dashboard/dashboard.tmpl"
+dest = "/usr/share/nginx/html/index.html"
+watch = true
+wait = "100ms:500ms"
From fe7fc4fdc96b0b803505853e26ac7c409e7f20ac Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 034/153] refactor: use one docker-gen watcher
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/Procfile | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/.docker/nginx-proxy/Procfile b/.docker/nginx-proxy/Procfile
index 69b47b8e..c0f1ad20 100644
--- a/.docker/nginx-proxy/Procfile
+++ b/.docker/nginx-proxy/Procfile
@@ -1,3 +1,2 @@
-dockergen: docker-gen -watch -wait 100ms:500ms -event-filter event=connect -event-filter event=disconnect -notify "nginx -s reload" /app/nginx.tmpl /etc/nginx/conf.d/default.conf
-dashboard: docker-gen -watch -wait 100ms:500ms -event-filter event=connect -event-filter event=disconnect /dashboard/dashboard.tmpl /usr/share/nginx/html/index.html
+dockergen: docker-gen -config /dashboard/docker-gen.cfg -event-filter event=connect -event-filter event=disconnect
nginx: nginx -g "daemon off;"
From ffbd3683f1986131fbaf5190885dc4eb3a9a8ea7 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 035/153] refactor: generate dashboard nginx config
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/dashboard.conf.tmpl | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
create mode 100644 .docker/nginx-proxy/dashboard.conf.tmpl
diff --git a/.docker/nginx-proxy/dashboard.conf.tmpl b/.docker/nginx-proxy/dashboard.conf.tmpl
new file mode 100644
index 00000000..52e31c3f
--- /dev/null
+++ b/.docker/nginx-proxy/dashboard.conf.tmpl
@@ -0,0 +1,23 @@
+server {
+ listen 8080;
+ listen [::]:8080;
+
+ server_name _;
+
+ root /usr/share/nginx/html;
+ index index.html;
+
+ location = /index.html {
+ internal;
+ }
+
+ location / {
+ if ($host != localhost) {
+ return 404;
+ }
+
+ try_files /index.html =404;
+ }
+
+ error_page 404 =404 /index.html;
+}
From b13e5ba417e9014dbaa9c897d39c4fd0012d4ba7 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 036/153] refactor: keep dashboard generation in one watcher
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/docker-gen.cfg | 7 -------
1 file changed, 7 deletions(-)
diff --git a/.docker/nginx-proxy/docker-gen.cfg b/.docker/nginx-proxy/docker-gen.cfg
index 484d5801..15161780 100644
--- a/.docker/nginx-proxy/docker-gen.cfg
+++ b/.docker/nginx-proxy/docker-gen.cfg
@@ -5,13 +5,6 @@ watch = true
wait = "100ms:500ms"
notifycmd = "nginx -s reload"
-[[config]]
-template = "/dashboard/dashboard.conf.tmpl"
-dest = "/etc/nginx/conf.d/dashboard.conf"
-watch = true
-wait = "100ms:500ms"
-notifycmd = "nginx -s reload"
-
[[config]]
template = "/dashboard/dashboard.tmpl"
dest = "/usr/share/nginx/html/index.html"
From b01798360ec430d47d9de6f1d5068b4a1b3552e2 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 037/153] refactor: remove redundant dashboard nginx server
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/dashboard.conf.tmpl | 23 -----------------------
1 file changed, 23 deletions(-)
delete mode 100644 .docker/nginx-proxy/dashboard.conf.tmpl
diff --git a/.docker/nginx-proxy/dashboard.conf.tmpl b/.docker/nginx-proxy/dashboard.conf.tmpl
deleted file mode 100644
index 52e31c3f..00000000
--- a/.docker/nginx-proxy/dashboard.conf.tmpl
+++ /dev/null
@@ -1,23 +0,0 @@
-server {
- listen 8080;
- listen [::]:8080;
-
- server_name _;
-
- root /usr/share/nginx/html;
- index index.html;
-
- location = /index.html {
- internal;
- }
-
- location / {
- if ($host != localhost) {
- return 404;
- }
-
- try_files /index.html =404;
- }
-
- error_page 404 =404 /index.html;
-}
From 3bc6480f31a8379fd8640781b59588f3a2fd52d8 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 038/153] fix: release shared proxy with coordinator leases
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy-coordinator.sh | 72 ++++++++++++++++++++++++----
1 file changed, 64 insertions(+), 8 deletions(-)
diff --git a/.docker/scripts/proxy-coordinator.sh b/.docker/scripts/proxy-coordinator.sh
index d9725f20..20ac8ac4 100755
--- a/.docker/scripts/proxy-coordinator.sh
+++ b/.docker/scripts/proxy-coordinator.sh
@@ -8,11 +8,13 @@ proxy_label=coop.librecode.dev-proxy=true
proxy_client_label=coop.librecode.dev-proxy-client=true
proxy_assets_volume=librecode-dev-proxy-assets
proxy_vhost_volume=librecode-dev-proxy-vhost
+proxy_lease_acquired=false
+coordinator_container="$(hostname)"
compose_project() {
docker inspect \
--format '{{ index .Config.Labels "com.docker.compose.project" }}' \
- "$(hostname)"
+ "$coordinator_container"
}
project="$(compose_project)"
@@ -174,12 +176,16 @@ install_proxy_assets() {
docker run --rm \
-v "$proxy_assets_volume:/target" \
docker:29-cli \
- sh -c 'rm -f /target/Procfile /target/dashboard.tmpl'
+ sh -c 'rm -f /target/Procfile /target/docker-gen.cfg /target/dashboard.tmpl'
copy_to_named_volume \
"$proxy_assets_volume" \
"$PROJECT_DIR/.docker/nginx-proxy/Procfile" \
Procfile
+ copy_to_named_volume \
+ "$proxy_assets_volume" \
+ "$PROJECT_DIR/.docker/nginx-proxy/docker-gen.cfg" \
+ docker-gen.cfg
copy_to_named_volume \
"$proxy_assets_volume" \
"$PROJECT_DIR/.docker/nginx-proxy/dashboard.tmpl" \
@@ -270,23 +276,72 @@ report_environment_ready() {
nextcloud sh /var/www/scripts/report-environment-ready
}
+acquire_proxy_lease() {
+ if ! docker inspect \
+ --format '{{ json .NetworkSettings.Networks }}' \
+ "$coordinator_container" |
+ grep -q "\"$proxy_network\""; then
+ docker network connect "$proxy_network" "$coordinator_container"
+ fi
+
+ proxy_lease_acquired=true
+}
+
+release_proxy_lease() {
+ [ "$proxy_lease_acquired" = true ] || return 0
+
+ docker network disconnect "$proxy_network" "$coordinator_container" >/dev/null 2>&1 || true
+ proxy_lease_acquired=false
+}
+
other_proxy_client_is_running() {
docker ps \
--filter "label=$proxy_client_label" \
- --format '{{.Label "com.docker.compose.project"}}' |
- grep -v -x "$project" |
+ --filter "network=$proxy_network" \
+ --format '{{.ID}}' |
grep -q .
}
+other_proxy_route_is_running() {
+ for container in $(docker ps --filter "network=$proxy_network" --format '{{.ID}}'); do
+ container_project="$(docker inspect \
+ --format '{{ index .Config.Labels "com.docker.compose.project" }}' \
+ "$container" 2>/dev/null || true)"
+
+ case "$container_project" in
+ "$project"|"$proxy_project")
+ continue
+ ;;
+ esac
+
+ virtual_host="$(docker inspect \
+ --format '{{range .Config.Env}}{{println .}}{{end}}' \
+ "$container" 2>/dev/null |
+ sed -n 's/^VIRTUAL_HOST=//p' |
+ head -n 1)"
+
+ [ -z "$virtual_host" ] || return 0
+ done
+
+ return 1
+}
+
+proxy_is_used_by_another_environment() {
+ other_proxy_client_is_running || other_proxy_route_is_running
+}
+
release_proxy_if_unused() {
- if other_proxy_client_is_running; then
+ release_proxy_lease
+
+ if proxy_is_used_by_another_environment; then
echo 'β
Shared development proxy is still used by another environment.'
return 0
fi
+ # Give an environment starting concurrently time to acquire its lease.
sleep 1
- if other_proxy_client_is_running; then
+ if proxy_is_used_by_another_environment; then
echo 'β
Shared development proxy is still used by another environment.'
return 0
fi
@@ -329,6 +384,9 @@ else
proxy_state=started
fi
+acquire_proxy_lease
+trap shutdown INT TERM HUP
+
connect_running_service_to_proxy_network nginx
connect_running_service_to_proxy_network mailpit
connect_running_service_to_proxy_network eurooffice
@@ -341,8 +399,6 @@ fi
success "$proxy_state"
-trap shutdown INT TERM HUP
-
while :; do
sleep 3600 &
wait "$!" || true
From 14430963021f6caaa55510529f9b8331f8b54047 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:08 -0300
Subject: [PATCH 039/153] fix: serve dashboard for unknown localhost hosts
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/*.localhost_location_override | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.docker/nginx-proxy/*.localhost_location_override b/.docker/nginx-proxy/*.localhost_location_override
index 421c127f..a831b0aa 100644
--- a/.docker/nginx-proxy/*.localhost_location_override
+++ b/.docker/nginx-proxy/*.localhost_location_override
@@ -1 +1,2 @@
-return 404;
+root /usr/share/nginx/html;
+try_files /index.html =404;
From eaad008bab4298db42ed8fa3feb22428c677e7d6 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 040/153] fix: handle localhost dashboard directly in nginx
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/dashboard.conf | 63 ++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)
create mode 100644 .docker/nginx-proxy/dashboard.conf
diff --git a/.docker/nginx-proxy/dashboard.conf b/.docker/nginx-proxy/dashboard.conf
new file mode 100644
index 00000000..f847c0d1
--- /dev/null
+++ b/.docker/nginx-proxy/dashboard.conf
@@ -0,0 +1,63 @@
+server {
+ listen 80;
+ listen [::]:80;
+ server_name localhost;
+
+ root /usr/share/nginx/html;
+
+ location / {
+ try_files /index.html =404;
+ }
+}
+
+server {
+ listen 80;
+ listen [::]:80;
+ server_name *.localhost;
+
+ root /usr/share/nginx/html;
+
+ location = /index.html {
+ internal;
+ }
+
+ location / {
+ error_page 404 =404 /index.html;
+ return 404;
+ }
+}
+
+server {
+ listen 443 ssl;
+ listen [::]:443 ssl;
+ server_name localhost;
+
+ ssl_certificate /etc/nginx/certs/localhost.crt;
+ ssl_certificate_key /etc/nginx/certs/localhost.key;
+
+ root /usr/share/nginx/html;
+
+ location / {
+ try_files /index.html =404;
+ }
+}
+
+server {
+ listen 443 ssl;
+ listen [::]:443 ssl;
+ server_name *.localhost;
+
+ ssl_certificate /etc/nginx/certs/*.localhost.crt;
+ ssl_certificate_key /etc/nginx/certs/*.localhost.key;
+
+ root /usr/share/nginx/html;
+
+ location = /index.html {
+ internal;
+ }
+
+ location / {
+ error_page 404 =404 /index.html;
+ return 404;
+ }
+}
From 4f82dee2550a57df1fc788f9ca6b18a6831bde70 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 041/153] refactor: serve dashboard from proxy config
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/docker-compose.proxy.yml | 8 +++-----
1 file changed, 3 insertions(+), 5 deletions(-)
diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml
index a98bf158..23c846ee 100644
--- a/.docker/docker-compose.proxy.yml
+++ b/.docker/docker-compose.proxy.yml
@@ -11,14 +11,12 @@ services:
- "443:443"
volumes:
- ${DOCKER_SOCKET:-/var/run/docker.sock}:/tmp/docker.sock:ro
- - librecode-dev-proxy-vhost:/etc/nginx/vhost.d
- librecode-dev-proxy-assets:/dashboard:ro
+ - librecode-dev-proxy-conf:/etc/nginx/conf.d
- librecode-dev-proxy-log:/etc/nginx/log
- librecode-dev-proxy-certs:/etc/nginx/certs:ro
environment:
- ENABLE_IPV6=true
- - VIRTUAL_HOST=localhost,*.localhost
- - VIRTUAL_PORT=80
- SELF_SIGNED_HOST=localhost,*.localhost
networks:
- proxy
@@ -42,10 +40,10 @@ networks:
external: true
volumes:
- librecode-dev-proxy-vhost:
- name: librecode-dev-proxy-vhost
librecode-dev-proxy-assets:
name: librecode-dev-proxy-assets
+ librecode-dev-proxy-conf:
+ name: librecode-dev-proxy-conf
librecode-dev-proxy-log:
name: librecode-dev-proxy-log
librecode-dev-proxy-certs:
From e3ad06ee0f1828024dbeefee0a30d1a997f245b3 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 042/153] revert: keep wildcard host managed by nginx-proxy
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/docker-compose.proxy.yml | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/.docker/docker-compose.proxy.yml b/.docker/docker-compose.proxy.yml
index 23c846ee..a98bf158 100644
--- a/.docker/docker-compose.proxy.yml
+++ b/.docker/docker-compose.proxy.yml
@@ -11,12 +11,14 @@ services:
- "443:443"
volumes:
- ${DOCKER_SOCKET:-/var/run/docker.sock}:/tmp/docker.sock:ro
+ - librecode-dev-proxy-vhost:/etc/nginx/vhost.d
- librecode-dev-proxy-assets:/dashboard:ro
- - librecode-dev-proxy-conf:/etc/nginx/conf.d
- librecode-dev-proxy-log:/etc/nginx/log
- librecode-dev-proxy-certs:/etc/nginx/certs:ro
environment:
- ENABLE_IPV6=true
+ - VIRTUAL_HOST=localhost,*.localhost
+ - VIRTUAL_PORT=80
- SELF_SIGNED_HOST=localhost,*.localhost
networks:
- proxy
@@ -40,10 +42,10 @@ networks:
external: true
volumes:
+ librecode-dev-proxy-vhost:
+ name: librecode-dev-proxy-vhost
librecode-dev-proxy-assets:
name: librecode-dev-proxy-assets
- librecode-dev-proxy-conf:
- name: librecode-dev-proxy-conf
librecode-dev-proxy-log:
name: librecode-dev-proxy-log
librecode-dev-proxy-certs:
From 95d910e9715b5547762bca8415bdb794d2904f83 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 043/153] revert: remove direct dashboard server
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/dashboard.conf | 63 ------------------------------
1 file changed, 63 deletions(-)
delete mode 100644 .docker/nginx-proxy/dashboard.conf
diff --git a/.docker/nginx-proxy/dashboard.conf b/.docker/nginx-proxy/dashboard.conf
deleted file mode 100644
index f847c0d1..00000000
--- a/.docker/nginx-proxy/dashboard.conf
+++ /dev/null
@@ -1,63 +0,0 @@
-server {
- listen 80;
- listen [::]:80;
- server_name localhost;
-
- root /usr/share/nginx/html;
-
- location / {
- try_files /index.html =404;
- }
-}
-
-server {
- listen 80;
- listen [::]:80;
- server_name *.localhost;
-
- root /usr/share/nginx/html;
-
- location = /index.html {
- internal;
- }
-
- location / {
- error_page 404 =404 /index.html;
- return 404;
- }
-}
-
-server {
- listen 443 ssl;
- listen [::]:443 ssl;
- server_name localhost;
-
- ssl_certificate /etc/nginx/certs/localhost.crt;
- ssl_certificate_key /etc/nginx/certs/localhost.key;
-
- root /usr/share/nginx/html;
-
- location / {
- try_files /index.html =404;
- }
-}
-
-server {
- listen 443 ssl;
- listen [::]:443 ssl;
- server_name *.localhost;
-
- ssl_certificate /etc/nginx/certs/*.localhost.crt;
- ssl_certificate_key /etc/nginx/certs/*.localhost.key;
-
- root /usr/share/nginx/html;
-
- location = /index.html {
- internal;
- }
-
- location / {
- error_page 404 =404 /index.html;
- return 404;
- }
-}
From 9403f3d9fbd0e7c70920176a46261ccc00a5da49 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 044/153] fix: render friendly wildcard 404 page
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/*.localhost | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/.docker/nginx-proxy/*.localhost b/.docker/nginx-proxy/*.localhost
index a2450af0..b66d860e 100644
--- a/.docker/nginx-proxy/*.localhost
+++ b/.docker/nginx-proxy/*.localhost
@@ -1,6 +1,6 @@
root /usr/share/nginx/html;
-error_page 404 =404 /index.html;
+error_page 404 =404 @librecode_dashboard_404;
-location = /index.html {
- internal;
+location @librecode_dashboard_404 {
+ try_files /index.html =404;
}
From aeb643013482ab5e6da030a95c2decfcff016f09 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 045/153] fix: preserve 404 status for wildcard dashboard
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/*.localhost_location_override | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/.docker/nginx-proxy/*.localhost_location_override b/.docker/nginx-proxy/*.localhost_location_override
index a831b0aa..421c127f 100644
--- a/.docker/nginx-proxy/*.localhost_location_override
+++ b/.docker/nginx-proxy/*.localhost_location_override
@@ -1,2 +1 @@
-root /usr/share/nginx/html;
-try_files /index.html =404;
+return 404;
From a29ee72ef169b358cc0cb10516dd24f5d364cd9e Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 046/153] fix: ignore current proxy lease during shutdown
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy-coordinator.sh | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/.docker/scripts/proxy-coordinator.sh b/.docker/scripts/proxy-coordinator.sh
index 20ac8ac4..8dd161b8 100755
--- a/.docker/scripts/proxy-coordinator.sh
+++ b/.docker/scripts/proxy-coordinator.sh
@@ -290,16 +290,25 @@ acquire_proxy_lease() {
release_proxy_lease() {
[ "$proxy_lease_acquired" = true ] || return 0
- docker network disconnect "$proxy_network" "$coordinator_container" >/dev/null 2>&1 || true
+ if ! docker network disconnect "$proxy_network" "$coordinator_container" >/dev/null 2>&1; then
+ echo 'Could not disconnect this coordinator lease from the shared proxy network; continuing with project-based lease detection.' >&2
+ fi
proxy_lease_acquired=false
}
other_proxy_client_is_running() {
- docker ps \
+ for container in $(docker ps \
--filter "label=$proxy_client_label" \
--filter "network=$proxy_network" \
- --format '{{.ID}}' |
- grep -q .
+ --format '{{.ID}}'); do
+ container_project="$(docker inspect \
+ --format '{{ index .Config.Labels "com.docker.compose.project" }}' \
+ "$container" 2>/dev/null || true)"
+
+ [ "$container_project" = "$project" ] || return 0
+ done
+
+ return 1
}
other_proxy_route_is_running() {
@@ -354,6 +363,7 @@ release_proxy_if_unused() {
shutdown() {
trap - INT TERM HUP
+ echo 'Releasing shared development proxy lease.'
release_proxy_if_unused
exit 0
}
From da1116702b959b869f10f337393e9424c38221de Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 047/153] refactor: extract proxy coordinator common helpers
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy/common.sh | 48 +++++++++++++++++++++++++++++++++
1 file changed, 48 insertions(+)
create mode 100644 .docker/scripts/proxy/common.sh
diff --git a/.docker/scripts/proxy/common.sh b/.docker/scripts/proxy/common.sh
new file mode 100644
index 00000000..1fc56e4d
--- /dev/null
+++ b/.docker/scripts/proxy/common.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+proxy_project=${PROXY_PROJECT:-librecode-dev-proxy}
+proxy_network=${PROXY_NETWORK:-librecode-dev-proxy}
+proxy_label=${PROXY_LABEL:-coop.librecode.dev-proxy=true}
+proxy_client_label=${PROXY_CLIENT_LABEL:-coop.librecode.dev-proxy-client=true}
+proxy_assets_volume=${PROXY_ASSETS_VOLUME:-librecode-dev-proxy-assets}
+proxy_vhost_volume=${PROXY_VHOST_VOLUME:-librecode-dev-proxy-vhost}
+
+Docker() {
+ docker "$@"
+}
+
+container_project() {
+ Docker inspect \
+ --format '{{ index .Config.Labels "com.docker.compose.project" }}' \
+ "$1" 2>/dev/null || true
+}
+
+container_virtual_host() {
+ Docker inspect \
+ --format '{{range .Config.Env}}{{println .}}{{end}}' \
+ "$1" 2>/dev/null |
+ sed -n 's/^VIRTUAL_HOST=//p' |
+ head -n 1
+}
+
+container_networks() {
+ Docker inspect \
+ --format '{{ json .NetworkSettings.Networks }}' \
+ "$1" 2>/dev/null || true
+}
+
+compose() {
+ Docker compose \
+ --project-name "$project" \
+ --project-directory "$PROJECT_DIR" \
+ --file "$PROJECT_DIR/docker-compose.yml" \
+ "$@"
+}
+
+proxy_compose() {
+ Docker compose \
+ --project-name "$proxy_project" \
+ --project-directory "$PROJECT_DIR" \
+ --file "$PROJECT_DIR/.docker/docker-compose.proxy.yml" \
+ "$@"
+}
From e403b5fb650c35e96e2644608c662a1547562053 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 048/153] refactor: extract shared proxy infrastructure logic
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy/infrastructure.sh | 115 ++++++++++++++++++++++++
1 file changed, 115 insertions(+)
create mode 100644 .docker/scripts/proxy/infrastructure.sh
diff --git a/.docker/scripts/proxy/infrastructure.sh b/.docker/scripts/proxy/infrastructure.sh
new file mode 100644
index 00000000..9c013dde
--- /dev/null
+++ b/.docker/scripts/proxy/infrastructure.sh
@@ -0,0 +1,115 @@
+#!/bin/sh
+
+container_for_published_port() {
+ port="$1"
+
+ Docker ps \
+ --filter "publish=$port" \
+ --format '{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Label "coop.librecode.dev-proxy"}}'
+}
+
+port_is_in_use() {
+ container_for_published_port "$1" | grep -q .
+}
+
+compatible_proxy_container() {
+ Docker ps \
+ --filter "label=$proxy_label" \
+ --format '{{.ID}}' |
+ head -n 1
+}
+
+is_compatible_proxy_port_owner() {
+ port="$1"
+ info="$(container_for_published_port "$port" | head -n 1)"
+
+ [ -n "$info" ] || return 1
+ [ "$(printf '%s\n' "$info" | cut -f4)" = "true" ]
+}
+
+proxy_is_ready() {
+ [ -n "$(compatible_proxy_container || true)" ] &&
+ is_compatible_proxy_port_owner 80 &&
+ is_compatible_proxy_port_owner 443
+}
+
+show_conflict() {
+ port="$1"
+ container_info="$(container_for_published_port "$port" | head -n 1)"
+
+ printf 'ββ β Development proxy cannot start βββββββββββββββββββββ\n' >&2
+ printf 'β\n' >&2
+ printf 'β Port 80 or 443 is already in use by another service.\n' >&2
+ printf 'β\n' >&2
+ printf 'β This development environment requires:\n' >&2
+ printf 'β\n' >&2
+ printf 'β HTTP localhost:80\n' >&2
+ printf 'β HTTPS localhost:443\n' >&2
+ printf 'β\n' >&2
+ printf 'β Stop the conflicting service and run:\n' >&2
+ printf 'β\n' >&2
+ printf 'β docker compose up\n' >&2
+ printf 'β\n' >&2
+
+ if [ -n "$container_info" ]; then
+ printf 'β Conflicting container\n' >&2
+ printf 'β Name %s\n' "$(printf '%s\n' "$container_info" | cut -f2)" >&2
+ printf 'β Image %s\n' "$(printf '%s\n' "$container_info" | cut -f3)" >&2
+ printf 'β Port %s\n' "$port" >&2
+ else
+ printf 'β Port %s is already in use by a process outside Docker.\n' "$port" >&2
+ fi
+
+ printf 'β\n' >&2
+ printf 'βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n' >&2
+}
+
+ensure_ports_available() {
+ for port in 80 443; do
+ if port_is_in_use "$port"; then
+ show_conflict "$port"
+ return 1
+ fi
+ done
+}
+
+ensure_proxy_network() {
+ if Docker network inspect "$proxy_network" >/dev/null 2>&1; then
+ return 0
+ fi
+
+ if Docker network create "$proxy_network" >/dev/null 2>&1; then
+ return 0
+ fi
+
+ # Another checkout may have created it concurrently.
+ Docker network inspect "$proxy_network" >/dev/null
+}
+
+start_proxy() {
+ if proxy_compose up --detach; then
+ return 0
+ fi
+
+ # Another checkout may have started the shared proxy concurrently.
+ if proxy_is_ready; then
+ return 0
+ fi
+
+ ensure_ports_available || return 1
+
+ echo 'Could not start the LibreCode development proxy.' >&2
+ return 1
+}
+
+ensure_proxy_running() {
+ if proxy_is_ready; then
+ proxy_compose up --detach
+ printf 'reused\n'
+ return 0
+ fi
+
+ ensure_ports_available || return 1
+ start_proxy || return 1
+ printf 'started\n'
+}
From 2bf03bf6ebb55504fe123da15a1209ef72e99ef4 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 049/153] refactor: extract shared proxy asset installation
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy/assets.sh | 36 +++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
create mode 100644 .docker/scripts/proxy/assets.sh
diff --git a/.docker/scripts/proxy/assets.sh b/.docker/scripts/proxy/assets.sh
new file mode 100644
index 00000000..93a1fe5c
--- /dev/null
+++ b/.docker/scripts/proxy/assets.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+copy_to_named_volume() {
+ volume="$1"
+ source="$2"
+ destination="$3"
+
+ Docker run --rm -i \
+ -v "$volume:/target" \
+ docker:29-cli \
+ sh -c 'cat > "/target/$1"' sh "$destination" \
+ < "$source"
+}
+
+install_proxy_assets() {
+ Docker volume create "$proxy_assets_volume" >/dev/null
+ Docker volume create "$proxy_vhost_volume" >/dev/null
+
+ Docker run --rm \
+ -v "$proxy_assets_volume:/target" \
+ docker:29-cli \
+ sh -c 'rm -f /target/Procfile /target/docker-gen.cfg /target/dashboard.tmpl'
+
+ copy_to_named_volume "$proxy_assets_volume" "$PROJECT_DIR/.docker/nginx-proxy/Procfile" Procfile
+ copy_to_named_volume "$proxy_assets_volume" "$PROJECT_DIR/.docker/nginx-proxy/docker-gen.cfg" docker-gen.cfg
+ copy_to_named_volume "$proxy_assets_volume" "$PROJECT_DIR/.docker/nginx-proxy/dashboard.tmpl" dashboard.tmpl
+
+ Docker run --rm \
+ -v "$proxy_vhost_volume:/target" \
+ docker:29-cli \
+ sh -c 'rm -f /target/librecode-localhost.conf /target/localhost /target/localhost_location_override /target/\*.localhost /target/\*.localhost_location_override'
+
+ copy_to_named_volume "$proxy_vhost_volume" "$PROJECT_DIR/.docker/nginx-proxy/localhost_location_override" localhost_location_override
+ copy_to_named_volume "$proxy_vhost_volume" "$PROJECT_DIR/.docker/nginx-proxy/*.localhost" '*.localhost'
+ copy_to_named_volume "$proxy_vhost_volume" "$PROJECT_DIR/.docker/nginx-proxy/*.localhost_location_override" '*.localhost_location_override'
+}
From dd523b9a759c66288c1565e053181d3737692104 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 050/153] refactor: extract proxied service coordination
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy/services.sh | 64 +++++++++++++++++++++++++++++++
1 file changed, 64 insertions(+)
create mode 100644 .docker/scripts/proxy/services.sh
diff --git a/.docker/scripts/proxy/services.sh b/.docker/scripts/proxy/services.sh
new file mode 100644
index 00000000..d35996f9
--- /dev/null
+++ b/.docker/scripts/proxy/services.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+
+service_is_running() {
+ compose ps --status running --services |
+ grep -qx "$1"
+}
+
+container_for_service() {
+ compose ps -q "$1" 2>/dev/null || true
+}
+
+connect_to_proxy_network() {
+ service="$1"
+ container="$(container_for_service "$service")"
+
+ [ -n "$container" ] || return 0
+
+ if container_networks "$container" | grep -q "\"$proxy_network\""; then
+ return 0
+ fi
+
+ Docker network connect "$proxy_network" "$container"
+}
+
+connect_running_service_to_proxy_network() {
+ service="$1"
+
+ service_is_running "$service" || return 0
+ connect_to_proxy_network "$service"
+}
+
+connect_project_services() {
+ for service in nginx mailpit eurooffice playwright signal-gateway; do
+ connect_running_service_to_proxy_network "$service"
+ done
+}
+
+report_environment_ready() {
+ set -- \
+ -e ENV_NEXTCLOUD_URL="https://${project}.localhost" \
+ -e ENV_ADMIN_USER="$NEXTCLOUD_ADMIN_USER" \
+ -e ENV_ADMIN_PASSWORD="$NEXTCLOUD_ADMIN_PASSWORD" \
+ -e ENV_NEXTCLOUD_BRANCH="$VERSION_NEXTCLOUD"
+
+ if service_is_running mailpit; then
+ set -- "$@" -e ENV_MAILPIT_URL="https://${project}-mailpit.localhost"
+ fi
+
+ if service_is_running eurooffice; then
+ set -- "$@" -e ENV_EUROOFFICE_URL="https://${project}-eurooffice.localhost"
+ fi
+
+ if service_is_running playwright; then
+ set -- "$@" -e ENV_PLAYWRIGHT_URL="https://${project}-playwright.localhost"
+ fi
+
+ if service_is_running signal-gateway; then
+ set -- "$@" -e ENV_SIGNAL_URL="https://${project}-signal.localhost"
+ fi
+
+ compose exec -T \
+ "$@" \
+ nextcloud sh /var/www/scripts/report-environment-ready
+}
From 3dca7dab71e73684e17db4cb9dcf25ac60bedce1 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 051/153] refactor: extract shared proxy lease lifecycle
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy/lease.sh | 78 ++++++++++++++++++++++++++++++++++
1 file changed, 78 insertions(+)
create mode 100644 .docker/scripts/proxy/lease.sh
diff --git a/.docker/scripts/proxy/lease.sh b/.docker/scripts/proxy/lease.sh
new file mode 100644
index 00000000..6e080c25
--- /dev/null
+++ b/.docker/scripts/proxy/lease.sh
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+proxy_lease_acquired=false
+
+acquire_proxy_lease() {
+ if ! container_networks "$coordinator_container" | grep -q "\"$proxy_network\""; then
+ Docker network connect "$proxy_network" "$coordinator_container"
+ fi
+
+ proxy_lease_acquired=true
+}
+
+release_proxy_lease() {
+ [ "$proxy_lease_acquired" = true ] || return 0
+
+ if ! Docker network disconnect "$proxy_network" "$coordinator_container" >/dev/null 2>&1; then
+ echo 'Could not disconnect this coordinator lease from the shared proxy network; continuing with project-based lease detection.' >&2
+ fi
+
+ proxy_lease_acquired=false
+}
+
+other_proxy_client_is_running() {
+ for container in $(Docker ps \
+ --filter "label=$proxy_client_label" \
+ --filter "network=$proxy_network" \
+ --format '{{.ID}}'); do
+ [ "$(container_project "$container")" = "$project" ] || return 0
+ done
+
+ return 1
+}
+
+other_proxy_route_is_running() {
+ for container in $(Docker ps --filter "network=$proxy_network" --format '{{.ID}}'); do
+ container_project_name="$(container_project "$container")"
+
+ case "$container_project_name" in
+ "$project"|"$proxy_project")
+ continue
+ ;;
+ esac
+
+ [ -z "$(container_virtual_host "$container")" ] || return 0
+ done
+
+ return 1
+}
+
+proxy_is_used_by_another_environment() {
+ other_proxy_client_is_running || other_proxy_route_is_running
+}
+
+wait_for_concurrent_lease() {
+ sleep "${PROXY_LEASE_GRACE_SECONDS:-1}"
+}
+
+release_proxy_if_unused() {
+ release_proxy_lease
+
+ if proxy_is_used_by_another_environment; then
+ echo 'β
Shared development proxy is still used by another environment.'
+ return 0
+ fi
+
+ wait_for_concurrent_lease
+
+ if proxy_is_used_by_another_environment; then
+ echo 'β
Shared development proxy is still used by another environment.'
+ return 0
+ fi
+
+ echo 'Stopping unused shared development proxy.'
+ if ! proxy_compose down --remove-orphans; then
+ echo 'Could not stop the unused shared development proxy.' >&2
+ return 1
+ fi
+}
From 11a3f340292774110eb2a26716ed6dd60713d955 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 052/153] refactor: reduce proxy coordinator to orchestration
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy-coordinator.sh | 443 ++++-----------------------
1 file changed, 57 insertions(+), 386 deletions(-)
diff --git a/.docker/scripts/proxy-coordinator.sh b/.docker/scripts/proxy-coordinator.sh
index 8dd161b8..b3b5a227 100755
--- a/.docker/scripts/proxy-coordinator.sh
+++ b/.docker/scripts/proxy-coordinator.sh
@@ -2,414 +2,85 @@
set -eu
-proxy_project=librecode-dev-proxy
-proxy_network=librecode-dev-proxy
-proxy_label=coop.librecode.dev-proxy=true
-proxy_client_label=coop.librecode.dev-proxy-client=true
-proxy_assets_volume=librecode-dev-proxy-assets
-proxy_vhost_volume=librecode-dev-proxy-vhost
-proxy_lease_acquired=false
-coordinator_container="$(hostname)"
-
-compose_project() {
- docker inspect \
- --format '{{ index .Config.Labels "com.docker.compose.project" }}' \
- "$coordinator_container"
-}
-
-project="$(compose_project)"
-
-if [ -z "$project" ]; then
- echo 'Could not determine the Compose project from the coordinator container.' >&2
- exit 1
-fi
-
-if [ -z "${PROJECT_DIR:-}" ]; then
- echo 'The host project directory was not provided to the coordinator.' >&2
- exit 1
-fi
-
-compose() {
- docker compose \
- --project-name "$project" \
- --project-directory "$PROJECT_DIR" \
- --file "$PROJECT_DIR/docker-compose.yml" \
- "$@"
-}
-
-proxy_compose() {
- docker compose \
- --project-name "$proxy_project" \
- --project-directory "$PROJECT_DIR" \
- --file "$PROJECT_DIR/.docker/docker-compose.proxy.yml" \
- "$@"
-}
-
-container_for_published_port() {
- port="$1"
-
- docker ps \
- --filter "publish=$port" \
- --format '{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Label "coop.librecode.dev-proxy"}}'
-}
-
-port_is_in_use() {
- container_for_published_port "$1" | grep -q .
-}
-
-compatible_proxy_container() {
- docker ps \
- --filter "label=$proxy_label" \
- --format '{{.ID}}' |
- head -n 1
-}
-
-is_compatible_proxy_port_owner() {
- port="$1"
- info="$(container_for_published_port "$port" | head -n 1)"
-
- [ -n "$info" ] || return 1
-
- compatible="$(printf '%s\n' "$info" | cut -f4)"
-
- [ "$compatible" = "true" ]
-}
-
-proxy_is_ready() {
- [ -n "$(compatible_proxy_container || true)" ] &&
- is_compatible_proxy_port_owner 80 &&
- is_compatible_proxy_port_owner 443
-}
-
-show_conflict() {
- port="$1"
- container_info="$(container_for_published_port "$port" | head -n 1)"
-
- printf 'ββ β Development proxy cannot start βββββββββββββββββββββ\n' >&2
- printf 'β\n' >&2
- printf 'β Port 80 or 443 is already in use by another service.\n' >&2
- printf 'β\n' >&2
- printf 'β This development environment requires:\n' >&2
- printf 'β\n' >&2
- printf 'β HTTP localhost:80\n' >&2
- printf 'β HTTPS localhost:443\n' >&2
- printf 'β\n' >&2
- printf 'β Stop the conflicting service and run:\n' >&2
- printf 'β\n' >&2
- printf 'β docker compose up\n' >&2
- printf 'β\n' >&2
-
- if [ -n "$container_info" ]; then
- name="$(printf '%s\n' "$container_info" | cut -f2)"
- image="$(printf '%s\n' "$container_info" | cut -f3)"
-
- printf 'β Conflicting container\n' >&2
- printf 'β Name %s\n' "$name" >&2
- printf 'β Image %s\n' "$image" >&2
- printf 'β Port %s\n' "$port" >&2
- else
- printf 'β Port %s is already in use by a process outside Docker.\n' "$port" >&2
- fi
-
- printf 'β\n' >&2
- printf 'βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n' >&2
-}
-
-ensure_ports_available() {
- for port in 80 443; do
- if port_is_in_use "$port"; then
- show_conflict "$port"
- exit 1
- fi
- done
-}
-
-ensure_proxy_network() {
- if docker network inspect "$proxy_network" >/dev/null 2>&1; then
- return 0
- fi
-
- if docker network create "$proxy_network" >/dev/null 2>&1; then
- return 0
- fi
-
- # Another checkout may have created it concurrently.
- docker network inspect "$proxy_network" >/dev/null
-}
-
-start_proxy() {
- if proxy_compose up --detach; then
- return 0
- fi
-
- if proxy_is_ready; then
- return 0
- fi
-
- for port in 80 443; do
- if port_is_in_use "$port"; then
- show_conflict "$port"
- exit 1
- fi
- done
-
- echo 'Could not start the LibreCode development proxy.' >&2
- exit 1
-}
-
-copy_to_named_volume() {
- volume="$1"
- source="$2"
- destination="$3"
-
- docker run --rm -i \
- -v "$volume:/target" \
- docker:29-cli \
- sh -c 'cat > "/target/$1"' sh "$destination" \
- < "$source"
-}
-
-install_proxy_assets() {
- docker volume create "$proxy_assets_volume" >/dev/null
- docker volume create "$proxy_vhost_volume" >/dev/null
-
- docker run --rm \
- -v "$proxy_assets_volume:/target" \
- docker:29-cli \
- sh -c 'rm -f /target/Procfile /target/docker-gen.cfg /target/dashboard.tmpl'
-
- copy_to_named_volume \
- "$proxy_assets_volume" \
- "$PROJECT_DIR/.docker/nginx-proxy/Procfile" \
- Procfile
- copy_to_named_volume \
- "$proxy_assets_volume" \
- "$PROJECT_DIR/.docker/nginx-proxy/docker-gen.cfg" \
- docker-gen.cfg
- copy_to_named_volume \
- "$proxy_assets_volume" \
- "$PROJECT_DIR/.docker/nginx-proxy/dashboard.tmpl" \
- dashboard.tmpl
-
- docker run --rm \
- -v "$proxy_vhost_volume:/target" \
- docker:29-cli \
- sh -c 'rm -f /target/librecode-localhost.conf /target/localhost /target/localhost_location_override /target/\*.localhost /target/\*.localhost_location_override'
-
- copy_to_named_volume \
- "$proxy_vhost_volume" \
- "$PROJECT_DIR/.docker/nginx-proxy/localhost_location_override" \
- localhost_location_override
- copy_to_named_volume \
- "$proxy_vhost_volume" \
- "$PROJECT_DIR/.docker/nginx-proxy/*.localhost" \
- '*.localhost'
- copy_to_named_volume \
- "$proxy_vhost_volume" \
- "$PROJECT_DIR/.docker/nginx-proxy/*.localhost_location_override" \
- '*.localhost_location_override'
-}
-
-running_services="$(compose ps --status running --services)"
-
-service_is_running() {
- printf '%s\n' "$running_services" |
- grep -qx "$1"
-}
-
-container_for_service() {
- compose ps -q "$1" 2>/dev/null || true
-}
+script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
+proxy_lib_dir="${PROXY_LIB_DIR:-$script_dir/proxy}"
+
+# shellcheck source=.docker/scripts/proxy/common.sh
+. "$proxy_lib_dir/common.sh"
+# shellcheck source=.docker/scripts/proxy/infrastructure.sh
+. "$proxy_lib_dir/infrastructure.sh"
+# shellcheck source=.docker/scripts/proxy/assets.sh
+. "$proxy_lib_dir/assets.sh"
+# shellcheck source=.docker/scripts/proxy/services.sh
+. "$proxy_lib_dir/services.sh"
+# shellcheck source=.docker/scripts/proxy/lease.sh
+. "$proxy_lib_dir/lease.sh"
-connect_to_proxy_network() {
- service="$1"
- container="$(container_for_service "$service")"
-
- [ -n "$container" ] || return 0
-
- if docker inspect \
- --format '{{ json .NetworkSettings.Networks }}' \
- "$container" |
- grep -q "\"$proxy_network\""; then
- return 0
- fi
-
- docker network connect "$proxy_network" "$container"
-}
-
-connect_running_service_to_proxy_network() {
- service="$1"
-
- service_is_running "$service" || return 0
- connect_to_proxy_network "$service"
-}
-
-report_environment_ready() {
- set -- \
- -e ENV_NEXTCLOUD_URL="https://${project}.localhost" \
- -e ENV_ADMIN_USER="$NEXTCLOUD_ADMIN_USER" \
- -e ENV_ADMIN_PASSWORD="$NEXTCLOUD_ADMIN_PASSWORD" \
- -e ENV_NEXTCLOUD_BRANCH="$VERSION_NEXTCLOUD"
-
- if service_is_running mailpit; then
- set -- "$@" \
- -e ENV_MAILPIT_URL="https://${project}-mailpit.localhost"
- fi
-
- if service_is_running eurooffice; then
- set -- "$@" \
- -e ENV_EUROOFFICE_URL="https://${project}-eurooffice.localhost"
- fi
-
- if service_is_running playwright; then
- set -- "$@" \
- -e ENV_PLAYWRIGHT_URL="https://${project}-playwright.localhost"
- fi
+coordinator_container="$(hostname)"
+project="$(container_project "$coordinator_container")"
- if service_is_running signal-gateway; then
- set -- "$@" \
- -e ENV_SIGNAL_URL="https://${project}-signal.localhost"
+validate_environment() {
+ if [ -z "$project" ]; then
+ echo 'Could not determine the Compose project from the coordinator container.' >&2
+ return 1
fi
- compose exec -T \
- "$@" \
- nextcloud sh /var/www/scripts/report-environment-ready
-}
-
-acquire_proxy_lease() {
- if ! docker inspect \
- --format '{{ json .NetworkSettings.Networks }}' \
- "$coordinator_container" |
- grep -q "\"$proxy_network\""; then
- docker network connect "$proxy_network" "$coordinator_container"
+ if [ -z "${PROJECT_DIR:-}" ]; then
+ echo 'The host project directory was not provided to the coordinator.' >&2
+ return 1
fi
- proxy_lease_acquired=true
+ echo "Validating Compose project ${project} at ${PROJECT_DIR}."
+ compose config --quiet
}
-release_proxy_lease() {
- [ "$proxy_lease_acquired" = true ] || return 0
-
- if ! docker network disconnect "$proxy_network" "$coordinator_container" >/dev/null 2>&1; then
- echo 'Could not disconnect this coordinator lease from the shared proxy network; continuing with project-based lease detection.' >&2
- fi
- proxy_lease_acquired=false
+success() {
+ case "$1" in
+ reused)
+ echo 'β
Existing LibreCode development proxy reused. Coordinator lease is active.'
+ ;;
+ started)
+ echo 'β
Development proxy started successfully. Coordinator lease is active.'
+ ;;
+ esac
}
-other_proxy_client_is_running() {
- for container in $(docker ps \
- --filter "label=$proxy_client_label" \
- --filter "network=$proxy_network" \
- --format '{{.ID}}'); do
- container_project="$(docker inspect \
- --format '{{ index .Config.Labels "com.docker.compose.project" }}' \
- "$container" 2>/dev/null || true)"
-
- [ "$container_project" = "$project" ] || return 0
- done
-
- return 1
+shutdown() {
+ trap - INT TERM HUP
+ echo 'Releasing shared development proxy lease.'
+ release_proxy_if_unused || true
+ exit 0
}
-other_proxy_route_is_running() {
- for container in $(docker ps --filter "network=$proxy_network" --format '{{.ID}}'); do
- container_project="$(docker inspect \
- --format '{{ index .Config.Labels "com.docker.compose.project" }}' \
- "$container" 2>/dev/null || true)"
-
- case "$container_project" in
- "$project"|"$proxy_project")
- continue
- ;;
- esac
-
- virtual_host="$(docker inspect \
- --format '{{range .Config.Env}}{{println .}}{{end}}' \
- "$container" 2>/dev/null |
- sed -n 's/^VIRTUAL_HOST=//p' |
- head -n 1)"
+wait_for_shutdown() {
+ trap shutdown INT TERM HUP
- [ -z "$virtual_host" ] || return 0
+ while :; do
+ sleep 3600 &
+ wait "$!" || true
done
-
- return 1
-}
-
-proxy_is_used_by_another_environment() {
- other_proxy_client_is_running || other_proxy_route_is_running
}
-release_proxy_if_unused() {
- release_proxy_lease
+main() {
+ validate_environment
+ ensure_proxy_network
+ install_proxy_assets
- if proxy_is_used_by_another_environment; then
- echo 'β
Shared development proxy is still used by another environment.'
- return 0
- fi
+ proxy_state="$(ensure_proxy_running)"
- # Give an environment starting concurrently time to acquire its lease.
- sleep 1
+ acquire_proxy_lease
+ trap shutdown INT TERM HUP
- if proxy_is_used_by_another_environment; then
- echo 'β
Shared development proxy is still used by another environment.'
- return 0
- fi
+ connect_project_services
- echo 'Stopping unused shared development proxy.'
- if ! proxy_compose down --remove-orphans; then
- echo 'Could not stop the unused shared development proxy.' >&2
+ if ! report_environment_ready; then
+ echo 'Could not print environment banner.' >&2
fi
-}
-shutdown() {
- trap - INT TERM HUP
- echo 'Releasing shared development proxy lease.'
- release_proxy_if_unused
- exit 0
+ success "$proxy_state"
+ wait_for_shutdown
}
-success() {
- case "$1" in
- reused)
- echo 'β
Existing LibreCode development proxy reused. Coordinator lease is active.'
- ;;
- started)
- echo 'β
Development proxy started successfully. Coordinator lease is active.'
- ;;
- esac
-}
-
-echo "Validating Compose project ${project} at ${PROJECT_DIR}."
-compose config --quiet
-
-ensure_proxy_network
-install_proxy_assets
-
-if proxy_is_ready; then
- proxy_compose up --detach
- proxy_state=reused
-else
- ensure_ports_available
- start_proxy
- proxy_state=started
+if [ "${PROXY_COORDINATOR_SOURCE_ONLY:-false}" != "true" ]; then
+ main "$@"
fi
-
-acquire_proxy_lease
-trap shutdown INT TERM HUP
-
-connect_running_service_to_proxy_network nginx
-connect_running_service_to_proxy_network mailpit
-connect_running_service_to_proxy_network eurooffice
-connect_running_service_to_proxy_network playwright
-connect_running_service_to_proxy_network signal-gateway
-
-if ! report_environment_ready; then
- echo 'Could not print environment banner.' >&2
-fi
-
-success "$proxy_state"
-
-while :; do
- sleep 3600 &
- wait "$!" || true
-done
From 13fbd34609eb5d4d14ddbc911a7a8802e42854cd Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 053/153] refactor: mount proxy coordinator modules
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
docker-compose.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docker-compose.yml b/docker-compose.yml
index c8c5a29d..0554df69 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -65,12 +65,12 @@ services:
- SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}.localhost
proxy-coordinator:
image: docker:29-cli
- entrypoint: ["/bin/sh", "/usr/local/bin/proxy-coordinator.sh"]
+ entrypoint: ["/bin/sh", "/usr/local/lib/librecode/proxy-coordinator.sh"]
labels:
coop.librecode.dev-proxy-client: "true"
volumes:
- ${DOCKER_SOCKET:-/var/run/docker.sock}:/var/run/docker.sock
- - ./.docker/scripts/proxy-coordinator.sh:/usr/local/bin/proxy-coordinator.sh:ro
+ - ./.docker/scripts:/usr/local/lib/librecode:ro
- .:${PWD}:ro
working_dir: ${PWD}
environment:
From b86d87b55707b198d77279035a80d2e34edc4afd Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 054/153] refactor: make compose files injectable for tests
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy/common.sh | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.docker/scripts/proxy/common.sh b/.docker/scripts/proxy/common.sh
index 1fc56e4d..fb17b6be 100644
--- a/.docker/scripts/proxy/common.sh
+++ b/.docker/scripts/proxy/common.sh
@@ -35,7 +35,7 @@ compose() {
Docker compose \
--project-name "$project" \
--project-directory "$PROJECT_DIR" \
- --file "$PROJECT_DIR/docker-compose.yml" \
+ --file "${PROJECT_COMPOSE_FILE:-$PROJECT_DIR/docker-compose.yml}" \
"$@"
}
@@ -43,6 +43,6 @@ proxy_compose() {
Docker compose \
--project-name "$proxy_project" \
--project-directory "$PROJECT_DIR" \
- --file "$PROJECT_DIR/.docker/docker-compose.proxy.yml" \
+ --file "${PROXY_COMPOSE_FILE:-$PROJECT_DIR/.docker/docker-compose.proxy.yml}" \
"$@"
}
From 2aac7913369ed055249dccbc007733ec18a59fd3 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 055/153] test: cover shared proxy lease decisions
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
tests/proxy/lease.bats | 115 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 115 insertions(+)
create mode 100644 tests/proxy/lease.bats
diff --git a/tests/proxy/lease.bats b/tests/proxy/lease.bats
new file mode 100644
index 00000000..78158a02
--- /dev/null
+++ b/tests/proxy/lease.bats
@@ -0,0 +1,115 @@
+#!/usr/bin/env bats
+
+setup() {
+ REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
+ TEST_LOG="$BATS_TEST_TMPDIR/docker.log"
+ : > "$TEST_LOG"
+
+ # shellcheck source=.docker/scripts/proxy/common.sh
+ source "$REPO_ROOT/.docker/scripts/proxy/common.sh"
+ # shellcheck source=.docker/scripts/proxy/lease.sh
+ source "$REPO_ROOT/.docker/scripts/proxy/lease.sh"
+
+ project=current
+ coordinator_container=current-coordinator
+ PROXY_LEASE_GRACE_SECONDS=0
+}
+
+@test "current project coordinator is not another proxy client" {
+ Docker() {
+ printf '%s\n' current-coordinator
+ }
+ container_project() {
+ printf '%s\n' current
+ }
+
+ run other_proxy_client_is_running
+
+ [ "$status" -eq 1 ]
+}
+
+@test "coordinator from another project keeps the proxy leased" {
+ Docker() {
+ printf '%s\n' current-coordinator other-coordinator
+ }
+ container_project() {
+ case "$1" in
+ current-coordinator) printf '%s\n' current ;;
+ other-coordinator) printf '%s\n' other ;;
+ esac
+ }
+
+ run other_proxy_client_is_running
+
+ [ "$status" -eq 0 ]
+}
+
+@test "route from another project keeps the proxy leased" {
+ Docker() {
+ printf '%s\n' proxy current-route other-route
+ }
+ container_project() {
+ case "$1" in
+ proxy) printf '%s\n' "$proxy_project" ;;
+ current-route) printf '%s\n' current ;;
+ other-route) printf '%s\n' other ;;
+ esac
+ }
+ container_virtual_host() {
+ [ "$1" = other-route ] && printf '%s\n' other.localhost
+ }
+
+ run other_proxy_route_is_running
+
+ [ "$status" -eq 0 ]
+}
+
+@test "last lease stops the shared proxy" {
+ proxy_lease_acquired=true
+ proxy_is_used_by_another_environment() {
+ return 1
+ }
+ Docker() {
+ printf 'docker %s\n' "$*" >> "$TEST_LOG"
+ }
+ proxy_compose() {
+ printf 'proxy-compose %s\n' "$*" >> "$TEST_LOG"
+ }
+
+ run release_proxy_if_unused
+
+ [ "$status" -eq 0 ]
+ grep -q '^proxy-compose down --remove-orphans$' "$TEST_LOG"
+}
+
+@test "another lease prevents proxy shutdown" {
+ proxy_lease_acquired=true
+ proxy_is_used_by_another_environment() {
+ return 0
+ }
+ Docker() {
+ printf 'docker %s\n' "$*" >> "$TEST_LOG"
+ }
+ proxy_compose() {
+ printf 'proxy-compose %s\n' "$*" >> "$TEST_LOG"
+ }
+
+ run release_proxy_if_unused
+
+ [ "$status" -eq 0 ]
+ ! grep -q '^proxy-compose down' "$TEST_LOG"
+}
+
+@test "acquiring a lease connects the coordinator only when needed" {
+ container_networks() {
+ printf '{}\n'
+ }
+ Docker() {
+ printf 'docker %s\n' "$*" >> "$TEST_LOG"
+ }
+
+ acquire_proxy_lease
+
+ grep -q "^docker network connect $proxy_network $coordinator_container$" "$TEST_LOG"
+ [ "$proxy_lease_acquired" = true ]
+}
From 2754c1ac58c9ec560ce30b53e813bf9c13bdc2b0 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 056/153] test: cover shared proxy infrastructure decisions
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
tests/proxy/infrastructure.bats | 69 +++++++++++++++++++++++++++++++++
1 file changed, 69 insertions(+)
create mode 100644 tests/proxy/infrastructure.bats
diff --git a/tests/proxy/infrastructure.bats b/tests/proxy/infrastructure.bats
new file mode 100644
index 00000000..0890e9d7
--- /dev/null
+++ b/tests/proxy/infrastructure.bats
@@ -0,0 +1,69 @@
+#!/usr/bin/env bats
+
+setup() {
+ REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
+ TEST_LOG="$BATS_TEST_TMPDIR/proxy.log"
+ : > "$TEST_LOG"
+
+ # shellcheck source=.docker/scripts/proxy/common.sh
+ source "$REPO_ROOT/.docker/scripts/proxy/common.sh"
+ # shellcheck source=.docker/scripts/proxy/infrastructure.sh
+ source "$REPO_ROOT/.docker/scripts/proxy/infrastructure.sh"
+}
+
+@test "ready proxy is reconciled and reused" {
+ proxy_is_ready() { return 0; }
+ proxy_compose() {
+ printf 'proxy-compose %s\n' "$*" >> "$TEST_LOG"
+ }
+
+ run ensure_proxy_running
+
+ [ "$status" -eq 0 ]
+ [ "$output" = reused ]
+ grep -q '^proxy-compose up --detach$' "$TEST_LOG"
+}
+
+@test "missing proxy is started" {
+ proxy_is_ready() { return 1; }
+ ensure_ports_available() { return 0; }
+ start_proxy() {
+ printf 'start-proxy\n' >> "$TEST_LOG"
+ }
+
+ run ensure_proxy_running
+
+ [ "$status" -eq 0 ]
+ [ "$output" = started ]
+ grep -q '^start-proxy$' "$TEST_LOG"
+}
+
+@test "occupied required port prevents startup" {
+ port_is_in_use() {
+ [ "$1" = 80 ]
+ }
+ show_conflict() {
+ printf 'conflict %s\n' "$1" >> "$TEST_LOG"
+ }
+
+ run ensure_ports_available
+
+ [ "$status" -eq 1 ]
+ grep -q '^conflict 80$' "$TEST_LOG"
+}
+
+@test "existing proxy network is reused" {
+ Docker() {
+ printf 'docker %s\n' "$*" >> "$TEST_LOG"
+ case "$*" in
+ "network inspect $proxy_network") return 0 ;;
+ esac
+ return 1
+ }
+
+ run ensure_proxy_network
+
+ [ "$status" -eq 0 ]
+ grep -q "^docker network inspect $proxy_network$" "$TEST_LOG"
+ ! grep -q '^docker network create' "$TEST_LOG"
+}
From e7f65326d7065c2f78a68a1d83412dd2434f1a12 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 057/153] test: cover proxied service coordination
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
tests/proxy/services.bats | 51 +++++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
create mode 100644 tests/proxy/services.bats
diff --git a/tests/proxy/services.bats b/tests/proxy/services.bats
new file mode 100644
index 00000000..4fcf639c
--- /dev/null
+++ b/tests/proxy/services.bats
@@ -0,0 +1,51 @@
+#!/usr/bin/env bats
+
+setup() {
+ REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
+ TEST_LOG="$BATS_TEST_TMPDIR/services.log"
+ : > "$TEST_LOG"
+
+ # shellcheck source=.docker/scripts/proxy/common.sh
+ source "$REPO_ROOT/.docker/scripts/proxy/common.sh"
+ # shellcheck source=.docker/scripts/proxy/services.sh
+ source "$REPO_ROOT/.docker/scripts/proxy/services.sh"
+
+ project=current
+}
+
+@test "running service is connected to proxy network" {
+ service_is_running() { return 0; }
+ container_for_service() { printf '%s\n' service-container; }
+ container_networks() { printf '{}\n'; }
+ Docker() {
+ printf 'docker %s\n' "$*" >> "$TEST_LOG"
+ }
+
+ connect_running_service_to_proxy_network nginx
+
+ grep -q "^docker network connect $proxy_network service-container$" "$TEST_LOG"
+}
+
+@test "service already on proxy network is not connected twice" {
+ service_is_running() { return 0; }
+ container_for_service() { printf '%s\n' service-container; }
+ container_networks() { printf '{\"%s\":{}}\n' "$proxy_network"; }
+ Docker() {
+ printf 'docker %s\n' "$*" >> "$TEST_LOG"
+ }
+
+ connect_running_service_to_proxy_network nginx
+
+ ! grep -q '^docker network connect' "$TEST_LOG"
+}
+
+@test "stopped service is ignored" {
+ service_is_running() { return 1; }
+ Docker() {
+ printf 'docker %s\n' "$*" >> "$TEST_LOG"
+ }
+
+ connect_running_service_to_proxy_network nginx
+
+ [ ! -s "$TEST_LOG" ]
+}
From a734a2394e8e4cdddb70dddce5eccc7c02e6ec02 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 058/153] test: add minimal proxy integration fixture
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
tests/proxy/fixtures/compose.yml | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
create mode 100644 tests/proxy/fixtures/compose.yml
diff --git a/tests/proxy/fixtures/compose.yml b/tests/proxy/fixtures/compose.yml
new file mode 100644
index 00000000..e7aa35e1
--- /dev/null
+++ b/tests/proxy/fixtures/compose.yml
@@ -0,0 +1,28 @@
+services:
+ nginx:
+ image: nginx:alpine
+ environment:
+ - VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}.localhost
+ - VIRTUAL_PORT=80
+ - SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}.localhost
+
+ proxy-coordinator:
+ image: docker:29-cli
+ entrypoint: ["/bin/sh", "/project/.docker/scripts/proxy-coordinator.sh"]
+ labels:
+ coop.librecode.dev-proxy-client: "true"
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ - ${REPO_ROOT}:/project:ro
+ working_dir: /project
+ environment:
+ - PROJECT_DIR=/project
+ - PROJECT_COMPOSE_FILE=/project/tests/proxy/fixtures/compose.yml
+ - PROXY_LEASE_GRACE_SECONDS=0
+ - NEXTCLOUD_ADMIN_USER=admin
+ - NEXTCLOUD_ADMIN_PASSWORD=admin
+ - VERSION_NEXTCLOUD=master
+ restart: "no"
+ stop_grace_period: 10s
+ depends_on:
+ - nginx
From 6ab5227dffee7531655cd913d4be665ef4ec3fc2 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:09 -0300
Subject: [PATCH 059/153] test: cover shared proxy lifecycle and routing
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
tests/proxy/integration.bats | 111 +++++++++++++++++++++++++++++++++++
1 file changed, 111 insertions(+)
create mode 100644 tests/proxy/integration.bats
diff --git a/tests/proxy/integration.bats b/tests/proxy/integration.bats
new file mode 100644
index 00000000..f7011388
--- /dev/null
+++ b/tests/proxy/integration.bats
@@ -0,0 +1,111 @@
+#!/usr/bin/env bats
+
+setup() {
+ REPO_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
+ FIXTURE="$REPO_ROOT/tests/proxy/fixtures/compose.yml"
+ BODY="$BATS_TEST_TMPDIR/body.html"
+ cleanup_proxy_tests
+}
+
+teardown() {
+ cleanup_proxy_tests
+}
+
+compose_test() {
+ project="$1"
+ shift
+ COMPOSE_PROJECT_NAME="$project" REPO_ROOT="$REPO_ROOT" \
+ docker compose --project-name "$project" --file "$FIXTURE" "$@"
+}
+
+cleanup_proxy_tests() {
+ for project in proxytesta proxytestb; do
+ COMPOSE_PROJECT_NAME="$project" REPO_ROOT="$REPO_ROOT" \
+ docker compose --project-name "$project" --file "$FIXTURE" down --volumes --remove-orphans >/dev/null 2>&1 || true
+ done
+
+ docker compose \
+ --project-name librecode-dev-proxy \
+ --project-directory "$REPO_ROOT" \
+ --file "$REPO_ROOT/.docker/docker-compose.proxy.yml" \
+ down --remove-orphans >/dev/null 2>&1 || true
+
+ docker network rm librecode-dev-proxy >/dev/null 2>&1 || true
+}
+
+container_is_running() {
+ docker ps --format '{{.Names}}' | grep -qx "$1"
+}
+
+wait_for_running() {
+ name="$1"
+ for _ in $(seq 1 60); do
+ container_is_running "$name" && return 0
+ sleep 0.5
+ done
+ return 1
+}
+
+wait_for_absent() {
+ name="$1"
+ for _ in $(seq 1 60); do
+ container_is_running "$name" || return 0
+ sleep 0.5
+ done
+ return 1
+}
+
+wait_for_https_status() {
+ host="$1"
+ expected="$2"
+
+ for _ in $(seq 1 60); do
+ status="$(curl --silent --show-error --insecure \
+ --resolve "$host:443:127.0.0.1" \
+ --output "$BODY" \
+ --write-out '%{http_code}' \
+ "https://$host/" 2>/dev/null || true)"
+ [ "$status" = "$expected" ] && return 0
+ sleep 0.5
+ done
+ return 1
+}
+
+@test "single project starts routing and releases the shared proxy" {
+ compose_test proxytesta up --detach
+
+ wait_for_running librecode-dev-proxy
+ wait_for_running librecode-dev-proxy-ssl-companion
+ wait_for_https_status localhost 200
+ grep -q 'LibreCode Development Proxy' "$BODY"
+
+ wait_for_https_status proxytesta.localhost 200
+ grep -q 'Welcome to nginx' "$BODY"
+
+ wait_for_https_status something-wrong.localhost 404
+ grep -q 'Environment not found' "$BODY"
+
+ compose_test proxytesta stop
+
+ wait_for_absent librecode-dev-proxy
+ wait_for_absent librecode-dev-proxy-ssl-companion
+}
+
+@test "shared proxy stays alive until the last project stops" {
+ compose_test proxytesta up --detach
+ compose_test proxytestb up --detach
+
+ wait_for_running librecode-dev-proxy
+ wait_for_https_status proxytesta.localhost 200
+ wait_for_https_status proxytestb.localhost 200
+
+ compose_test proxytesta stop
+
+ wait_for_running librecode-dev-proxy
+ wait_for_https_status proxytestb.localhost 200
+
+ compose_test proxytestb stop
+
+ wait_for_absent librecode-dev-proxy
+ wait_for_absent librecode-dev-proxy-ssl-companion
+}
From 1b4a856da0e51f980d5a54b42fd729db9fe8257a Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 060/153] ci: test shared proxy lifecycle with Bats
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.github/workflows/proxy-tests.yml | 46 +++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
create mode 100644 .github/workflows/proxy-tests.yml
diff --git a/.github/workflows/proxy-tests.yml b/.github/workflows/proxy-tests.yml
new file mode 100644
index 00000000..411c943a
--- /dev/null
+++ b/.github/workflows/proxy-tests.yml
@@ -0,0 +1,46 @@
+name: Proxy tests
+
+on:
+ pull_request:
+ push:
+ branches:
+ - main
+
+permissions:
+ contents: read
+
+jobs:
+ unit:
+ name: Proxy unit tests
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v7
+ - name: Setup Bats-core
+ uses: bats-core/bats-action@4.0.0
+ with:
+ support-install: false
+ assert-install: false
+ detik-install: false
+ file-install: false
+ - name: Run unit tests
+ run: bats tests/proxy/lease.bats tests/proxy/infrastructure.bats tests/proxy/services.bats
+
+ integration:
+ name: Proxy Docker integration tests
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v7
+ - name: Setup Bats-core
+ uses: bats-core/bats-action@4.0.0
+ with:
+ support-install: false
+ assert-install: false
+ detik-install: false
+ file-install: false
+ - name: Validate Compose files
+ run: |
+ docker compose config --quiet
+ REPO_ROOT="$GITHUB_WORKSPACE" COMPOSE_PROJECT_NAME=proxytesta docker compose --file tests/proxy/fixtures/compose.yml config --quiet
+ - name: Run Docker integration tests
+ run: bats tests/proxy/integration.bats
From 5be1aa50d3a62052041f00cd809055dd97e86dff Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 061/153] fix: make proxy coordinator shellcheck clean
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy-coordinator.sh | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/.docker/scripts/proxy-coordinator.sh b/.docker/scripts/proxy-coordinator.sh
index b3b5a227..eee5ecf6 100755
--- a/.docker/scripts/proxy-coordinator.sh
+++ b/.docker/scripts/proxy-coordinator.sh
@@ -2,18 +2,19 @@
set -eu
-script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
+script_dir="$(cd -- "$(dirname -- "$0")" && pwd)"
proxy_lib_dir="${PROXY_LIB_DIR:-$script_dir/proxy}"
-# shellcheck source=.docker/scripts/proxy/common.sh
+# The modules are loaded from a runtime path in the coordinator container.
+# shellcheck disable=SC1090,SC1091
. "$proxy_lib_dir/common.sh"
-# shellcheck source=.docker/scripts/proxy/infrastructure.sh
+# shellcheck disable=SC1090,SC1091
. "$proxy_lib_dir/infrastructure.sh"
-# shellcheck source=.docker/scripts/proxy/assets.sh
+# shellcheck disable=SC1090,SC1091
. "$proxy_lib_dir/assets.sh"
-# shellcheck source=.docker/scripts/proxy/services.sh
+# shellcheck disable=SC1090,SC1091
. "$proxy_lib_dir/services.sh"
-# shellcheck source=.docker/scripts/proxy/lease.sh
+# shellcheck disable=SC1090,SC1091
. "$proxy_lib_dir/lease.sh"
coordinator_container="$(hostname)"
From 59bf8af1d9b3311150e0e475dea9a25b1c602fd2 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 062/153] fix: document sourced proxy globals for shellcheck
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy/common.sh | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.docker/scripts/proxy/common.sh b/.docker/scripts/proxy/common.sh
index fb17b6be..59415123 100644
--- a/.docker/scripts/proxy/common.sh
+++ b/.docker/scripts/proxy/common.sh
@@ -1,5 +1,8 @@
#!/bin/sh
+# This module defines and consumes globals shared by the sourced proxy modules.
+# shellcheck disable=SC2034,SC2154
+
proxy_project=${PROXY_PROJECT:-librecode-dev-proxy}
proxy_network=${PROXY_NETWORK:-librecode-dev-proxy}
proxy_label=${PROXY_LABEL:-coop.librecode.dev-proxy=true}
From 511c31629aa66cdb0c0b7323ca65dfc86eb389ed Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 063/153] fix: mark sourced proxy globals for infrastructure
module
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy/infrastructure.sh | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.docker/scripts/proxy/infrastructure.sh b/.docker/scripts/proxy/infrastructure.sh
index 9c013dde..0e3fe71d 100644
--- a/.docker/scripts/proxy/infrastructure.sh
+++ b/.docker/scripts/proxy/infrastructure.sh
@@ -1,5 +1,8 @@
#!/bin/sh
+# Globals are provided by common.sh before this module is sourced.
+# shellcheck disable=SC2154
+
container_for_published_port() {
port="$1"
From 4bca3fec90d94612727c9bc695df86de87b9691d Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 064/153] fix: mark sourced proxy globals for asset module
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy/assets.sh | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.docker/scripts/proxy/assets.sh b/.docker/scripts/proxy/assets.sh
index 93a1fe5c..affaf05d 100644
--- a/.docker/scripts/proxy/assets.sh
+++ b/.docker/scripts/proxy/assets.sh
@@ -1,5 +1,9 @@
#!/bin/sh
+# Globals are provided by common.sh before this module is sourced.
+# The single-quoted shell snippets are intentionally evaluated in helper containers.
+# shellcheck disable=SC2154,SC2016
+
copy_to_named_volume() {
volume="$1"
source="$2"
From 093187e87360ba4eece350a43d88213be26dbafd Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 065/153] fix: mark sourced proxy globals for service module
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy/services.sh | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.docker/scripts/proxy/services.sh b/.docker/scripts/proxy/services.sh
index d35996f9..e219f7e2 100644
--- a/.docker/scripts/proxy/services.sh
+++ b/.docker/scripts/proxy/services.sh
@@ -1,5 +1,8 @@
#!/bin/sh
+# Globals are provided by common.sh before this module is sourced.
+# shellcheck disable=SC2154
+
service_is_running() {
compose ps --status running --services |
grep -qx "$1"
From 943da4f80ad773d5993360394a847d3780affb6f Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 066/153] fix: mark sourced proxy globals for lease module
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy/lease.sh | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.docker/scripts/proxy/lease.sh b/.docker/scripts/proxy/lease.sh
index 6e080c25..d7c300a8 100644
--- a/.docker/scripts/proxy/lease.sh
+++ b/.docker/scripts/proxy/lease.sh
@@ -1,5 +1,8 @@
#!/bin/sh
+# Globals are provided by common.sh and proxy-coordinator.sh before use.
+# shellcheck disable=SC2154
+
proxy_lease_acquired=false
acquire_proxy_lease() {
From db23c2d2b20fdbaa78163cfaa913fad21675d32b Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 067/153] fix: keep proxy test fixture valid inside coordinator
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
tests/proxy/fixtures/compose.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/tests/proxy/fixtures/compose.yml b/tests/proxy/fixtures/compose.yml
index e7aa35e1..0e91d81d 100644
--- a/tests/proxy/fixtures/compose.yml
+++ b/tests/proxy/fixtures/compose.yml
@@ -18,6 +18,7 @@ services:
environment:
- PROJECT_DIR=/project
- PROJECT_COMPOSE_FILE=/project/tests/proxy/fixtures/compose.yml
+ - REPO_ROOT=/project
- PROXY_LEASE_GRACE_SECONDS=0
- NEXTCLOUD_ADMIN_USER=admin
- NEXTCLOUD_ADMIN_PASSWORD=admin
From 9d5c1db955605b0edd7dab7c5196b2a390d76aa9 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 068/153] fix: bound shared proxy shutdown time
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy/lease.sh | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/.docker/scripts/proxy/lease.sh b/.docker/scripts/proxy/lease.sh
index d7c300a8..72d54870 100644
--- a/.docker/scripts/proxy/lease.sh
+++ b/.docker/scripts/proxy/lease.sh
@@ -74,7 +74,9 @@ release_proxy_if_unused() {
fi
echo 'Stopping unused shared development proxy.'
- if ! proxy_compose down --remove-orphans; then
+ if ! proxy_compose down \
+ --timeout "${PROXY_STOP_TIMEOUT_SECONDS:-3}" \
+ --remove-orphans; then
echo 'Could not stop the unused shared development proxy.' >&2
return 1
fi
From d1fa931be686f09732d122f43a913edf3137f292 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 069/153] test: cover Ctrl+C proxy shutdown
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
tests/proxy/integration.bats | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/tests/proxy/integration.bats b/tests/proxy/integration.bats
index f7011388..07f872c9 100644
--- a/tests/proxy/integration.bats
+++ b/tests/proxy/integration.bats
@@ -91,6 +91,27 @@ wait_for_https_status() {
wait_for_absent librecode-dev-proxy-ssl-companion
}
+@test "Ctrl+C on attached compose stops the last shared proxy" {
+ log="$BATS_TEST_TMPDIR/compose-up.log"
+
+ COMPOSE_PROJECT_NAME=proxytesta REPO_ROOT="$REPO_ROOT" \
+ docker compose \
+ --project-name proxytesta \
+ --file "$FIXTURE" \
+ up >"$log" 2>&1 &
+ compose_pid=$!
+
+ wait_for_running librecode-dev-proxy
+ wait_for_running librecode-dev-proxy-ssl-companion
+ wait_for_https_status proxytesta.localhost 200
+
+ kill -INT "$compose_pid"
+ wait "$compose_pid" || true
+
+ wait_for_absent librecode-dev-proxy
+ wait_for_absent librecode-dev-proxy-ssl-companion
+}
+
@test "shared proxy stays alive until the last project stops" {
compose_test proxytesta up --detach
compose_test proxytestb up --detach
From eae012f7faba602d25fcd2fd0e0d892215d7cc0b Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 070/153] test: expect bounded proxy shutdown
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
tests/proxy/lease.bats | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/tests/proxy/lease.bats b/tests/proxy/lease.bats
index 78158a02..8ebc5deb 100644
--- a/tests/proxy/lease.bats
+++ b/tests/proxy/lease.bats
@@ -64,8 +64,9 @@ setup() {
[ "$status" -eq 0 ]
}
-@test "last lease stops the shared proxy" {
+@test "last lease stops the shared proxy with bounded timeout" {
proxy_lease_acquired=true
+ PROXY_STOP_TIMEOUT_SECONDS=7
proxy_is_used_by_another_environment() {
return 1
}
@@ -79,7 +80,7 @@ setup() {
run release_proxy_if_unused
[ "$status" -eq 0 ]
- grep -q '^proxy-compose down --remove-orphans$' "$TEST_LOG"
+ grep -q '^proxy-compose down --timeout 7 --remove-orphans$' "$TEST_LOG"
}
@test "another lease prevents proxy shutdown" {
From b55dbba91866b26b13876468084e5c566fda04f2 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 071/153] refactor: make proxy lease release idempotent
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy/lease.sh | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)
diff --git a/.docker/scripts/proxy/lease.sh b/.docker/scripts/proxy/lease.sh
index 72d54870..a8b590d3 100644
--- a/.docker/scripts/proxy/lease.sh
+++ b/.docker/scripts/proxy/lease.sh
@@ -3,24 +3,20 @@
# Globals are provided by common.sh and proxy-coordinator.sh before use.
# shellcheck disable=SC2154
-proxy_lease_acquired=false
-
acquire_proxy_lease() {
if ! container_networks "$coordinator_container" | grep -q "\"$proxy_network\""; then
Docker network connect "$proxy_network" "$coordinator_container"
fi
-
- proxy_lease_acquired=true
}
release_proxy_lease() {
- [ "$proxy_lease_acquired" = true ] || return 0
+ if ! container_networks "$coordinator_container" | grep -q "\"$proxy_network\""; then
+ return 0
+ fi
if ! Docker network disconnect "$proxy_network" "$coordinator_container" >/dev/null 2>&1; then
echo 'Could not disconnect this coordinator lease from the shared proxy network; continuing with project-based lease detection.' >&2
fi
-
- proxy_lease_acquired=false
}
other_proxy_client_is_running() {
From a752bcd473f2f6b333968f8de74fe277bad540ad Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 072/153] fix: support reliable proxy pre-stop cleanup
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy-coordinator.sh | 36 +++++++++++++++++++++++-----
1 file changed, 30 insertions(+), 6 deletions(-)
diff --git a/.docker/scripts/proxy-coordinator.sh b/.docker/scripts/proxy-coordinator.sh
index eee5ecf6..2ad62a61 100755
--- a/.docker/scripts/proxy-coordinator.sh
+++ b/.docker/scripts/proxy-coordinator.sh
@@ -4,6 +4,7 @@ set -eu
script_dir="$(cd -- "$(dirname -- "$0")" && pwd)"
proxy_lib_dir="${PROXY_LIB_DIR:-$script_dir/proxy}"
+release_marker=/tmp/librecode-proxy-lease-released
# The modules are loaded from a runtime path in the coordinator container.
# shellcheck disable=SC1090,SC1091
@@ -46,10 +47,23 @@ success() {
esac
}
+release() {
+ if [ -f "$release_marker" ]; then
+ return 0
+ fi
+
+ echo 'Releasing shared development proxy lease.'
+ if release_proxy_if_unused; then
+ touch "$release_marker"
+ return 0
+ fi
+
+ return 1
+}
+
shutdown() {
trap - INT TERM HUP
- echo 'Releasing shared development proxy lease.'
- release_proxy_if_unused || true
+ release || true
exit 0
}
@@ -62,7 +76,7 @@ wait_for_shutdown() {
done
}
-main() {
+run() {
validate_environment
ensure_proxy_network
install_proxy_assets
@@ -70,6 +84,7 @@ main() {
proxy_state="$(ensure_proxy_running)"
acquire_proxy_lease
+ rm -f "$release_marker"
trap shutdown INT TERM HUP
connect_project_services
@@ -82,6 +97,15 @@ main() {
wait_for_shutdown
}
-if [ "${PROXY_COORDINATOR_SOURCE_ONLY:-false}" != "true" ]; then
- main "$@"
-fi
+case "${1:-run}" in
+ run)
+ run
+ ;;
+ release)
+ release
+ ;;
+ *)
+ echo "Unknown proxy coordinator command: $1" >&2
+ exit 2
+ ;;
+esac
From d751cfe9bdbe0cdf5ecc8e1902fa3fc26cfc91dc Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 073/153] fix: make local shutdown fast and deterministic
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
docker-compose.yml | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/docker-compose.yml b/docker-compose.yml
index 0554df69..7f201185 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -19,6 +19,8 @@ services:
- ~/.npm:/var/www/.npm/
- ./volumes/nextcloud:/var/www/html
restart: unless-stopped
+ stop_signal: SIGTERM
+ stop_grace_period: 3s
depends_on:
- database
environment:
@@ -53,6 +55,8 @@ services:
# context: .docker/
# dockerfile: Dockerfile.nginx
restart: unless-stopped
+ stop_signal: SIGTERM
+ stop_grace_period: 3s
depends_on:
- nextcloud
volumes:
@@ -78,18 +82,22 @@ services:
- NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER:-admin}
- NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD:-admin}
- VERSION_NEXTCLOUD=${VERSION_NEXTCLOUD:-master}
+ pre_stop:
+ - command: ["/bin/sh", "/usr/local/lib/librecode/proxy-coordinator.sh", "release"]
restart: "no"
- stop_grace_period: 15s
+ stop_grace_period: 8s
depends_on:
- nextcloud
mailpit:
image: axllent/mailpit
+ stop_grace_period: 3s
environment:
- VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}-mailpit.localhost
- VIRTUAL_PORT=8025
- SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}-mailpit.localhost
redis:
image: redis
+ stop_grace_period: 3s
eurooffice:
image: ghcr.io/euro-office/documentserver:latest
profiles:
@@ -158,4 +166,4 @@ services:
- WHATSAPP_WEBHOOK_EVENTS=${WHATSAPP_WEBHOOK_EVENTS:-message,message.ack,group.participants,chat_presence}
- WHATSAPP_AUTO_REPLY="Conta de sistema. Para falar com a LibreCode, mande email para contato@libresign.coop"
volumes:
- - ./volumes/go-whatsapp-web:/usr/src/app/files
+ - ./volumes/go-whatsapp-web:/usr/src/app/files
\ No newline at end of file
From 6f4156a180509d78cb2cfb140cc2c4938510a097 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 074/153] test: exercise coordinator pre-stop cleanup
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
tests/proxy/fixtures/compose.yml | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/tests/proxy/fixtures/compose.yml b/tests/proxy/fixtures/compose.yml
index 0e91d81d..68a12300 100644
--- a/tests/proxy/fixtures/compose.yml
+++ b/tests/proxy/fixtures/compose.yml
@@ -1,6 +1,8 @@
services:
nginx:
image: nginx:alpine
+ stop_signal: SIGTERM
+ stop_grace_period: 3s
environment:
- VIRTUAL_HOST=${COMPOSE_PROJECT_NAME}.localhost
- VIRTUAL_PORT=80
@@ -23,7 +25,9 @@ services:
- NEXTCLOUD_ADMIN_USER=admin
- NEXTCLOUD_ADMIN_PASSWORD=admin
- VERSION_NEXTCLOUD=master
+ pre_stop:
+ - command: ["/bin/sh", "/project/.docker/scripts/proxy-coordinator.sh", "release"]
restart: "no"
- stop_grace_period: 10s
+ stop_grace_period: 8s
depends_on:
- nginx
From c63e3ad6bdc891dfb0f500d743b2385e0562ddbe Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 075/153] test: cover idempotent proxy lease release
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
tests/proxy/lease.bats | 25 ++++++++++++++++++++++---
1 file changed, 22 insertions(+), 3 deletions(-)
diff --git a/tests/proxy/lease.bats b/tests/proxy/lease.bats
index 8ebc5deb..dd56c542 100644
--- a/tests/proxy/lease.bats
+++ b/tests/proxy/lease.bats
@@ -65,8 +65,10 @@ setup() {
}
@test "last lease stops the shared proxy with bounded timeout" {
- proxy_lease_acquired=true
PROXY_STOP_TIMEOUT_SECONDS=7
+ container_networks() {
+ printf '{"%s":{}}\n' "$proxy_network"
+ }
proxy_is_used_by_another_environment() {
return 1
}
@@ -80,11 +82,14 @@ setup() {
run release_proxy_if_unused
[ "$status" -eq 0 ]
+ grep -q "^docker network disconnect $proxy_network $coordinator_container$" "$TEST_LOG"
grep -q '^proxy-compose down --timeout 7 --remove-orphans$' "$TEST_LOG"
}
@test "another lease prevents proxy shutdown" {
- proxy_lease_acquired=true
+ container_networks() {
+ printf '{"%s":{}}\n' "$proxy_network"
+ }
proxy_is_used_by_another_environment() {
return 0
}
@@ -98,9 +103,24 @@ setup() {
run release_proxy_if_unused
[ "$status" -eq 0 ]
+ grep -q "^docker network disconnect $proxy_network $coordinator_container$" "$TEST_LOG"
! grep -q '^proxy-compose down' "$TEST_LOG"
}
+@test "release is safe when compose already disconnected the coordinator" {
+ container_networks() {
+ printf '{}\n'
+ }
+ Docker() {
+ printf 'docker %s\n' "$*" >> "$TEST_LOG"
+ }
+
+ run release_proxy_lease
+
+ [ "$status" -eq 0 ]
+ ! grep -q '^docker network disconnect' "$TEST_LOG"
+}
+
@test "acquiring a lease connects the coordinator only when needed" {
container_networks() {
printf '{}\n'
@@ -112,5 +132,4 @@ setup() {
acquire_proxy_lease
grep -q "^docker network connect $proxy_network $coordinator_container$" "$TEST_LOG"
- [ "$proxy_lease_acquired" = true ]
}
From 96ffa12a148bb52926aa3cd119fa50241f76a455 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 076/153] test: bound attached compose shutdown time
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
tests/proxy/integration.bats | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/tests/proxy/integration.bats b/tests/proxy/integration.bats
index 07f872c9..cc29b924 100644
--- a/tests/proxy/integration.bats
+++ b/tests/proxy/integration.bats
@@ -91,7 +91,7 @@ wait_for_https_status() {
wait_for_absent librecode-dev-proxy-ssl-companion
}
-@test "Ctrl+C on attached compose stops the last shared proxy" {
+@test "Ctrl+C on attached compose stops the last shared proxy promptly" {
log="$BATS_TEST_TMPDIR/compose-up.log"
COMPOSE_PROJECT_NAME=proxytesta REPO_ROOT="$REPO_ROOT" \
@@ -105,11 +105,16 @@ wait_for_https_status() {
wait_for_running librecode-dev-proxy-ssl-companion
wait_for_https_status proxytesta.localhost 200
+ started_at="$(date +%s)"
kill -INT "$compose_pid"
wait "$compose_pid" || true
+ finished_at="$(date +%s)"
wait_for_absent librecode-dev-proxy
wait_for_absent librecode-dev-proxy-ssl-companion
+
+ elapsed=$((finished_at - started_at))
+ [ "$elapsed" -lt 10 ]
}
@test "shared proxy stays alive until the last project stops" {
From fa2012d651bf0d2f7a55e4908023ef6a0b03ef3f Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 077/153] fix: keep compose lifecycle hooks backward compatible
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
docker-compose.yml | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/docker-compose.yml b/docker-compose.yml
index 7f201185..ec012240 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -82,8 +82,6 @@ services:
- NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER:-admin}
- NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD:-admin}
- VERSION_NEXTCLOUD=${VERSION_NEXTCLOUD:-master}
- pre_stop:
- - command: ["/bin/sh", "/usr/local/lib/librecode/proxy-coordinator.sh", "release"]
restart: "no"
stop_grace_period: 8s
depends_on:
@@ -166,4 +164,4 @@ services:
- WHATSAPP_WEBHOOK_EVENTS=${WHATSAPP_WEBHOOK_EVENTS:-message,message.ack,group.participants,chat_presence}
- WHATSAPP_AUTO_REPLY="Conta de sistema. Para falar com a LibreCode, mande email para contato@libresign.coop"
volumes:
- - ./volumes/go-whatsapp-web:/usr/src/app/files
\ No newline at end of file
+ - ./volumes/go-whatsapp-web:/usr/src/app/files
From b53ff0d0a603bc2d0c051fd1b550010c5ced7e2d Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 078/153] test: avoid unsupported compose lifecycle hook
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
tests/proxy/fixtures/compose.yml | 2 --
1 file changed, 2 deletions(-)
diff --git a/tests/proxy/fixtures/compose.yml b/tests/proxy/fixtures/compose.yml
index 68a12300..a2901a62 100644
--- a/tests/proxy/fixtures/compose.yml
+++ b/tests/proxy/fixtures/compose.yml
@@ -25,8 +25,6 @@ services:
- NEXTCLOUD_ADMIN_USER=admin
- NEXTCLOUD_ADMIN_PASSWORD=admin
- VERSION_NEXTCLOUD=master
- pre_stop:
- - command: ["/bin/sh", "/project/.docker/scripts/proxy-coordinator.sh", "release"]
restart: "no"
stop_grace_period: 8s
depends_on:
From e68db4241acfffef4ddbbbb1ac7e9ab9de4bec62 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:10 -0300
Subject: [PATCH 079/153] fix: process coordinator shutdown signals promptly
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy-coordinator.sh | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/.docker/scripts/proxy-coordinator.sh b/.docker/scripts/proxy-coordinator.sh
index 2ad62a61..5ac45df7 100755
--- a/.docker/scripts/proxy-coordinator.sh
+++ b/.docker/scripts/proxy-coordinator.sh
@@ -70,8 +70,11 @@ shutdown() {
wait_for_shutdown() {
trap shutdown INT TERM HUP
+ # Keep the wait interval short. Some /bin/sh implementations defer traps
+ # while waiting for a child process, so a long sleep can outlive Compose's
+ # stop grace period and prevent the lease cleanup from running.
while :; do
- sleep 3600 &
+ sleep "${PROXY_SIGNAL_POLL_SECONDS:-1}" &
wait "$!" || true
done
}
From 18eaf67a5d8106c6512f4f4e1a788c6d15f25d83 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:11 -0300
Subject: [PATCH 080/153] fix: run proxy coordinator with init
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
docker-compose.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/docker-compose.yml b/docker-compose.yml
index ec012240..4420f313 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -69,6 +69,7 @@ services:
- SELF_SIGNED_HOST=${COMPOSE_PROJECT_NAME}.localhost
proxy-coordinator:
image: docker:29-cli
+ init: true
entrypoint: ["/bin/sh", "/usr/local/lib/librecode/proxy-coordinator.sh"]
labels:
coop.librecode.dev-proxy-client: "true"
From ff0122c514463233f8fc2dc361a479e3e26b94f7 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:11 -0300
Subject: [PATCH 081/153] test: run proxy coordinator with init
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
tests/proxy/fixtures/compose.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/tests/proxy/fixtures/compose.yml b/tests/proxy/fixtures/compose.yml
index a2901a62..7700a0ed 100644
--- a/tests/proxy/fixtures/compose.yml
+++ b/tests/proxy/fixtures/compose.yml
@@ -10,6 +10,7 @@ services:
proxy-coordinator:
image: docker:29-cli
+ init: true
entrypoint: ["/bin/sh", "/project/.docker/scripts/proxy-coordinator.sh"]
labels:
coop.librecode.dev-proxy-client: "true"
From 1e8ab5844dfb73b6727a889cd908e32a5e1bedee Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:11 -0300
Subject: [PATCH 082/153] feat: generate proxy runtime diagnostics
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy/diagnostics.sh | 77 ++++++++++++++++++++++++++++
1 file changed, 77 insertions(+)
create mode 100644 .docker/scripts/proxy/diagnostics.sh
diff --git a/.docker/scripts/proxy/diagnostics.sh b/.docker/scripts/proxy/diagnostics.sh
new file mode 100644
index 00000000..4dba0cad
--- /dev/null
+++ b/.docker/scripts/proxy/diagnostics.sh
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+# Globals are provided by common.sh before this module is sourced.
+# shellcheck disable=SC2154
+
+runtime_version() {
+ component="$1"
+
+ case "$component" in
+ docker)
+ Docker version --format '{{.Server.Version}}'
+ ;;
+ compose)
+ Docker compose version --short
+ ;;
+ runc)
+ Docker version --format '{{range .Server.Components}}{{if eq .Name "runc"}}{{.Version}}{{end}}{{end}}'
+ ;;
+ esac
+}
+
+version_at_most() {
+ version="${1#v}"
+ maximum="${2#v}"
+
+ version="${version%%-*}"
+ maximum="${maximum%%-*}"
+
+ old_ifs="$IFS"
+ IFS=.
+ set -- $version
+ version_major="${1:-0}"
+ version_minor="${2:-0}"
+ version_patch="${3:-0}"
+ set -- $maximum
+ maximum_major="${1:-0}"
+ maximum_minor="${2:-0}"
+ maximum_patch="${3:-0}"
+ IFS="$old_ifs"
+
+ [ "$version_major" -lt "$maximum_major" ] && return 0
+ [ "$version_major" -gt "$maximum_major" ] && return 1
+ [ "$version_minor" -lt "$maximum_minor" ] && return 0
+ [ "$version_minor" -gt "$maximum_minor" ] && return 1
+ [ "$version_patch" -le "$maximum_patch" ]
+}
+
+runtime_has_known_shutdown_risk() {
+ docker_version="$1"
+ runc_version="$2"
+
+ version_at_most "$docker_version" "${PROXY_KNOWN_BAD_DOCKER_MAX:-25.0.2}" &&
+ version_at_most "$runc_version" "${PROXY_KNOWN_BAD_RUNC_MAX:-1.1.12}"
+}
+
+runtime_diagnostics_json() {
+ docker_version="$(runtime_version docker)"
+ compose_version="$(runtime_version compose)"
+ runc_version="$(runtime_version runc)"
+
+ if runtime_has_known_shutdown_risk "$docker_version" "$runc_version"; then
+ warnings='[{"code":"outdated-docker-runtime","message":"This Docker and runc combination may fail to stop containers correctly on recent Linux/AppArmor hosts. Update Docker Engine before investigating shutdown problems."}]'
+ else
+ warnings='[]'
+ fi
+
+ printf '{"docker":"%s","compose":"%s","runc":"%s","warnings":%s}\n' \
+ "$docker_version" "$compose_version" "$runc_version" "$warnings"
+}
+
+install_runtime_diagnostics() {
+ runtime_diagnostics_json |
+ Docker run --rm -i \
+ -v "$proxy_assets_volume:/target" \
+ docker:29-cli \
+ sh -c 'cat > /target/runtime.json'
+}
From 00353cbba3957f97cad84d16fe86e2d7d9f230a2 Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:11 -0300
Subject: [PATCH 083/153] feat: publish proxy runtime diagnostics
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/scripts/proxy-coordinator.sh | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.docker/scripts/proxy-coordinator.sh b/.docker/scripts/proxy-coordinator.sh
index 5ac45df7..9532cbbb 100755
--- a/.docker/scripts/proxy-coordinator.sh
+++ b/.docker/scripts/proxy-coordinator.sh
@@ -14,6 +14,8 @@ release_marker=/tmp/librecode-proxy-lease-released
# shellcheck disable=SC1090,SC1091
. "$proxy_lib_dir/assets.sh"
# shellcheck disable=SC1090,SC1091
+. "$proxy_lib_dir/diagnostics.sh"
+# shellcheck disable=SC1090,SC1091
. "$proxy_lib_dir/services.sh"
# shellcheck disable=SC1090,SC1091
. "$proxy_lib_dir/lease.sh"
@@ -83,6 +85,7 @@ run() {
validate_environment
ensure_proxy_network
install_proxy_assets
+ install_runtime_diagnostics
proxy_state="$(ensure_proxy_running)"
From 55bee39d7c1696db2209e66a88ef28c7c7a1248d Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:11 -0300
Subject: [PATCH 084/153] feat: serve proxy runtime diagnostics
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/localhost_location_override | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.docker/nginx-proxy/localhost_location_override b/.docker/nginx-proxy/localhost_location_override
index a831b0aa..b3f4e9c6 100644
--- a/.docker/nginx-proxy/localhost_location_override
+++ b/.docker/nginx-proxy/localhost_location_override
@@ -1,2 +1,2 @@
root /usr/share/nginx/html;
-try_files /index.html =404;
+try_files $uri /index.html =404;
From d1bdf659269ad9764bfb0c1fb90e6e8916a1c65a Mon Sep 17 00:00:00 2001
From: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
Date: Sat, 12 Sep 2026 16:24:11 -0300
Subject: [PATCH 085/153] feat: show runtime diagnostics on proxy dashboard
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
---
.docker/nginx-proxy/dashboard.tmpl | 54 +++++++++++++++++++++++++++++-
1 file changed, 53 insertions(+), 1 deletion(-)
diff --git a/.docker/nginx-proxy/dashboard.tmpl b/.docker/nginx-proxy/dashboard.tmpl
index b8c31fb0..799fbe25 100644
--- a/.docker/nginx-proxy/dashboard.tmpl
+++ b/.docker/nginx-proxy/dashboard.tmpl
@@ -46,7 +46,11 @@
li { margin: .45rem 0; }
a { color: LinkText; }
code { padding: .15em .4em; border-radius: 4px; background: color-mix(in srgb, CanvasText 10%, transparent); }
- .not-found { padding: 1rem; border: 1px solid color-mix(in srgb, CanvasText 24%, transparent); border-radius: 8px; }
+ .not-found, .runtime-warning { padding: 1rem; border: 1px solid color-mix(in srgb, CanvasText 24%, transparent); border-radius: 8px; }
+ .runtime-warning { margin-top: 1rem; border-color: #c47f00; }
+ .runtime-warning strong { display: block; margin-bottom: .4rem; }
+ .runtime-list { list-style: none; padding: 0; }
+ .runtime-list li { margin: .35rem 0; }
.muted { opacity: .7; }
@@ -62,6 +66,12 @@
The shared development proxy is running.
Active environments
+
+
+ Environment checks
+ Loading runtime informationβ¦
+
+
This page is generated from the routes currently connected to the shared development proxy.
@@ -141,6 +151,48 @@
link.textContent = closest;
suggestion.hidden = false;
}
+ } else {
+ const runtimeSection = document.getElementById('runtime-section');
+ runtimeSection.hidden = false;
+
+ fetch('/runtime.json', { cache: 'no-store' })
+ .then((response) => {
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+ return response.json();
+ })
+ .then((runtime) => {
+ const checks = document.getElementById('runtime-checks');
+ checks.replaceChildren();
+
+ const list = document.createElement('ul');
+ list.className = 'runtime-list';
+ for (const [label, version] of [
+ ['Docker Engine', runtime.docker],
+ ['Docker Compose', runtime.compose],
+ ['runc', runtime.runc],
+ ]) {
+ const item = document.createElement('li');
+ item.textContent = `β ${label} ${version || 'unknown'}`;
+ list.appendChild(item);
+ }
+ checks.appendChild(list);
+
+ for (const warning of runtime.warnings || []) {
+ const panel = document.createElement('div');
+ panel.className = 'runtime-warning';
+ const title = document.createElement('strong');
+ title.textContent = 'β Docker runtime needs attention';
+ const message = document.createElement('span');
+ message.textContent = warning.message;
+ panel.append(title, message);
+ checks.appendChild(panel);
+ }
+ })
+ .catch(() => {
+ const checks = document.getElementById('runtime-checks');
+ checks.textContent = 'Runtime information is not available.';
+ checks.className = 'muted';
+ });
}