From 354a579788773974c99be0e4022a1bdaf9b895fa Mon Sep 17 00:00:00 2001 From: George Brownbridge Date: Tue, 6 Jan 2026 12:01:42 +0000 Subject: [PATCH 01/28] add-traefik-support: Added a stack config setting to allow the reverse-proxy to be specified. --- .../stack/clients/core/StackClient.java | 9 +++++++++ .../cmclinnovations/stack/services/ServiceManager.java | 10 ++++++---- .../src/main/java/com/cmclinnovations/stack/Stack.java | 1 + .../java/com/cmclinnovations/stack/StackConfig.java | 8 ++++++++ 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/core/StackClient.java b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/core/StackClient.java index 8350f876..2c3d3c95 100644 --- a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/core/StackClient.java +++ b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/core/StackClient.java @@ -36,6 +36,7 @@ public final class StackClient { private static StackHost stackHost = new StackHost(); private static boolean isolated = false; + private static String reverseProxyName; static { String envVarStackName = System.getenv(StackClient.STACK_NAME_KEY); @@ -107,6 +108,14 @@ public static Path getAbsDataPath() { return getStackBaseDir().resolve("inputs").resolve("data"); } + public static void setReverseProxyName(String reverseProxyName) { + StackClient.reverseProxyName = reverseProxyName; + } + + public static String getReverseProxyName() { + return reverseProxyName; + } + /** * Get a RemoteRDBStoreClient for the named Postgres RDB running in this stack. * diff --git a/stack-clients/src/main/java/com/cmclinnovations/stack/services/ServiceManager.java b/stack-clients/src/main/java/com/cmclinnovations/stack/services/ServiceManager.java index 14a92f7e..d87d2dc4 100644 --- a/stack-clients/src/main/java/com/cmclinnovations/stack/services/ServiceManager.java +++ b/stack-clients/src/main/java/com/cmclinnovations/stack/services/ServiceManager.java @@ -167,15 +167,17 @@ public S initialiseService(String stackName, String serviceN DockerService dockerService = getOrInitialiseService(stackName, StackClient.getContainerEngineName()); dockerService.doPreStartUpConfiguration(newContainerService); + if (!StackClient.getReverseProxyName().equals(serviceName)) { + ReverseProxyService reverseProxyService = getOrInitialiseService(stackName, + StackClient.getReverseProxyName()); + reverseProxyService.addService(newContainerService); + } + dockerService.writeEndpointConfigs(newContainerService); if (dockerService.startContainer(newContainerService)) { dockerService.doFirstTimePostStartUpConfiguration(newContainerService); } dockerService.doEveryTimePostStartUpConfiguration(newContainerService); - if (!NginxService.TYPE.equals(serviceName)) { - ReverseProxyService reverseProxyService = getOrInitialiseService(stackName, NginxService.TYPE); - reverseProxyService.addService(newContainerService); - } } services.put(serviceName, newService); diff --git a/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java b/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java index 692cdc6e..14136c07 100644 --- a/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java +++ b/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java @@ -83,6 +83,7 @@ private Stack(String name, ServiceManager manager, StackConfig config) { if (null != config) { StackClient.setStackHost(config.getHost()); StackClient.setIsolated(config.isIsolated()); + StackClient.setReverseProxyName(config.getReverseProxyName()); } } diff --git a/stack-manager/src/main/java/com/cmclinnovations/stack/StackConfig.java b/stack-manager/src/main/java/com/cmclinnovations/stack/StackConfig.java index 95b2d62a..7c4470ec 100644 --- a/stack-manager/src/main/java/com/cmclinnovations/stack/StackConfig.java +++ b/stack-manager/src/main/java/com/cmclinnovations/stack/StackConfig.java @@ -7,6 +7,7 @@ import java.util.Map; import com.cmclinnovations.stack.clients.core.StackHost; +import com.cmclinnovations.stack.services.NginxService; import com.fasterxml.jackson.annotation.JsonProperty; public class StackConfig { @@ -30,6 +31,9 @@ private enum Selector { @JsonProperty private final Boolean isolated = false; + @JsonProperty("reverseProxy") + private final String reverseProxy = NginxService.TYPE; + @JsonProperty("hostName") private void setHostName(String hostName) { host = new StackHost(hostName); @@ -54,4 +58,8 @@ Map getVolumes() { public boolean isIsolated() { return isolated; } + + public String getReverseProxyName() { + return reverseProxy; + } } From 4f645cbf6e7f771c953e9483b89197cd66c3d1fb Mon Sep 17 00:00:00 2001 From: George Brownbridge Date: Tue, 6 Jan 2026 12:03:13 +0000 Subject: [PATCH 02/28] add-traefik-support: Bumped stack version. --- stack-clients/docker-compose.yml | 2 +- stack-clients/pom.xml | 2 +- stack-data-uploader/docker-compose.yml | 2 +- stack-data-uploader/pom.xml | 4 ++-- stack-manager/docker-compose.yml | 2 +- stack-manager/pom.xml | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/stack-clients/docker-compose.yml b/stack-clients/docker-compose.yml index 1596f5b1..5801c070 100644 --- a/stack-clients/docker-compose.yml +++ b/stack-clients/docker-compose.yml @@ -1,6 +1,6 @@ services: stack-client: - image: ghcr.io/theworldavatar/stack-client${IMAGE_SUFFIX}:1.56.2 + image: ghcr.io/theworldavatar/stack-client${IMAGE_SUFFIX}:1.57.0-traefik-support-SNAPSHOT secrets: - blazegraph_password - postgis_password diff --git a/stack-clients/pom.xml b/stack-clients/pom.xml index a76fd8d7..c0b5e052 100644 --- a/stack-clients/pom.xml +++ b/stack-clients/pom.xml @@ -7,7 +7,7 @@ com.cmclinnovations stack-clients - 1.56.2 + 1.57.0-traefik-support-SNAPSHOT Stack Clients https://theworldavatar.io diff --git a/stack-data-uploader/docker-compose.yml b/stack-data-uploader/docker-compose.yml index 2492d196..c110fea3 100644 --- a/stack-data-uploader/docker-compose.yml +++ b/stack-data-uploader/docker-compose.yml @@ -1,6 +1,6 @@ services: stack-data-uploader: - image: ghcr.io/theworldavatar/stack-data-uploader${IMAGE_SUFFIX}:1.56.2 + image: ghcr.io/theworldavatar/stack-data-uploader${IMAGE_SUFFIX}:1.57.0-traefik-support-SNAPSHOT secrets: - blazegraph_password - postgis_password diff --git a/stack-data-uploader/pom.xml b/stack-data-uploader/pom.xml index 9a621abf..4f6025ef 100644 --- a/stack-data-uploader/pom.xml +++ b/stack-data-uploader/pom.xml @@ -7,7 +7,7 @@ com.cmclinnovations stack-data-uploader - 1.56.2 + 1.57.0-traefik-support-SNAPSHOT Stack Data Uploader https://theworldavatar.io @@ -38,7 +38,7 @@ com.cmclinnovations stack-clients - 1.56.2 + 1.57.0-traefik-support-SNAPSHOT diff --git a/stack-manager/docker-compose.yml b/stack-manager/docker-compose.yml index 83e67e62..f2536823 100644 --- a/stack-manager/docker-compose.yml +++ b/stack-manager/docker-compose.yml @@ -1,6 +1,6 @@ services: stack-manager: - image: ghcr.io/theworldavatar/stack-manager${IMAGE_SUFFIX}:1.56.2 + image: ghcr.io/theworldavatar/stack-manager${IMAGE_SUFFIX}:1.57.0-traefik-support-SNAPSHOT environment: EXTERNAL_PORT: "${EXTERNAL_PORT-3838}" STACK_BASE_DIR: "${STACK_BASE_DIR}" diff --git a/stack-manager/pom.xml b/stack-manager/pom.xml index ab2fc7ba..d8984e8a 100644 --- a/stack-manager/pom.xml +++ b/stack-manager/pom.xml @@ -7,7 +7,7 @@ com.cmclinnovations stack-manager - 1.56.2 + 1.57.0-traefik-support-SNAPSHOT Stack Manager https://theworldavatar.io @@ -38,7 +38,7 @@ com.cmclinnovations stack-clients - 1.56.2 + 1.57.0-traefik-support-SNAPSHOT From 1124705c8ddb1aecb75fc62c754f22ee50ea9c0d Mon Sep 17 00:00:00 2001 From: George Brownbridge Date: Tue, 6 Jan 2026 12:07:39 +0000 Subject: [PATCH 03/28] add-traefik-support: Added "empty" `TraefikService` class. --- .../stack/services/TraefikService.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 stack-clients/src/main/java/com/cmclinnovations/stack/services/TraefikService.java diff --git a/stack-clients/src/main/java/com/cmclinnovations/stack/services/TraefikService.java b/stack-clients/src/main/java/com/cmclinnovations/stack/services/TraefikService.java new file mode 100644 index 00000000..090be337 --- /dev/null +++ b/stack-clients/src/main/java/com/cmclinnovations/stack/services/TraefikService.java @@ -0,0 +1,36 @@ +package com.cmclinnovations.stack.services; + +import java.util.HashMap; +import java.util.Map; + +import com.cmclinnovations.stack.clients.core.StackClient; +import com.cmclinnovations.stack.clients.utils.FileUtils; +import com.cmclinnovations.stack.services.config.Connection; +import com.cmclinnovations.stack.services.config.ServiceConfig; + +public class TraefikService extends ContainerService implements ReverseProxyService { + + public TraefikService(String stackName, ServiceConfig config) { + super(stackName, config); + } + + @Override + public void addService(ContainerService service) { + Map labels = service.getContainerSpec().getLabels(); + if (null == labels) { + labels = new HashMap<>(); + service.getContainerSpec().withLabels(labels); + } + labels.put("traefik.enable", "true"); + + service.getConfig().getEndpoints().forEach((name,connection) -> { + // TODO: Set the labels correctly + // labels.put("traefik.http.routers." + service.getContainerName() + ".rule", + // "PathPrefix(`" + FileUtils.fixSlashes(connection.getExternalPath().getPath(), true, false) + "`)"); + // labels.put( + // "traefik.http.services." + service.getServiceName() + ".loadbalancer.server.port", + // String.valueOf(connection.getInternalPort())); + }); + } + +} From 84a1fd5101908c65144ce3f924cbbcb211d9945e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Fri, 16 Jan 2026 11:12:55 +0000 Subject: [PATCH 04/28] get debugger working --- stack-manager/.vscode/tasks.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/stack-manager/.vscode/tasks.json b/stack-manager/.vscode/tasks.json index af41b2f3..06455fe2 100644 --- a/stack-manager/.vscode/tasks.json +++ b/stack-manager/.vscode/tasks.json @@ -33,7 +33,10 @@ ], "options": { "shell": { - "executable": "bash" + "executable": "bash", + "args": [ + "-c" + ] } } }, From 52332ce27ebf8665f4441009379e7777f0bf7b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Fri, 16 Jan 2026 14:57:27 +0000 Subject: [PATCH 05/28] traefik service config file --- .../stack/services/built-ins/traefik.json | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 stack-clients/src/main/resources/com/cmclinnovations/stack/services/built-ins/traefik.json diff --git a/stack-clients/src/main/resources/com/cmclinnovations/stack/services/built-ins/traefik.json b/stack-clients/src/main/resources/com/cmclinnovations/stack/services/built-ins/traefik.json new file mode 100644 index 00000000..ff525b4c --- /dev/null +++ b/stack-clients/src/main/resources/com/cmclinnovations/stack/services/built-ins/traefik.json @@ -0,0 +1,46 @@ +{ + "type": "traefik", + "ServiceSpec": { + "Name": "traefik", + "TaskTemplate": { + "ContainerSpec": { + "Image": "traefik:v3.6", + "Mounts": [ + { + "Type": "bind", + "Source": "/var/run/docker.sock", + "Target": "/var/run/docker.sock", + "ReadOnly": true + }, + { + "Type": "volume", + "Source": "traefik_config", + "Target": "/etc/traefik" + } + ] + } + }, + "EndpointSpec": { + "Ports": [ + { + "Name": "web", + "Protocol": "tcp", + "TargetPort": "80", + "PublishedPort": "3838" + }, + { + "Name": "websecure", + "Protocol": "tcp", + "TargetPort": "443", + "PublishedPort": "443" + }, + { + "Name": "dashboard", + "Protocol": "tcp", + "TargetPort": "8080", + "PublishedPort": "8080" + } + ] + } + } +} \ No newline at end of file From 461b7c684f5520a262d558fd660cd1ba2bbb3da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Fri, 16 Jan 2026 14:57:47 +0000 Subject: [PATCH 06/28] addreverseproxy method in stack.java --- .../main/java/com/cmclinnovations/stack/Stack.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java b/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java index 14136c07..159b769d 100644 --- a/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java +++ b/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java @@ -135,6 +135,9 @@ private List calculateSelectedServicesFromConfig(List defaultSer + ", explicitly included by user. Please remove them from the \"includes\" list in the stack config file."); } + // Add the reverse proxy service specified in the config + addReverseProxyIfAbsent(selectedServices); + // Add user specified services selectedServices.addAll(config.getIncludedServices()); // Remove any excluded services (default and user specified) @@ -142,6 +145,15 @@ private List calculateSelectedServicesFromConfig(List defaultSer return selectedServices; } + private void addReverseProxyIfAbsent(List selectedServices) { + String reverseProxyService = config.getReverseProxyName(); + if (reverseProxyService != null && !reverseProxyService.isEmpty()) { + if (!selectedServices.contains(reverseProxyService)) { + selectedServices.add(reverseProxyService); + } + } + } + private void handleUserSuppliedData(List selectedServices) { Map volumes = config.getVolumes(); From 88aca33e37413b9120be16319ae4a3860b53a666 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Tue, 20 Jan 2026 11:44:24 +0000 Subject: [PATCH 07/28] remove snashot --- stack-clients/docker-compose.yml | 2 +- stack-clients/pom.xml | 4 ++-- stack-data-uploader/docker-compose.yml | 2 +- stack-data-uploader/pom.xml | 4 ++-- stack-manager/docker-compose.yml | 2 +- stack-manager/pom.xml | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/stack-clients/docker-compose.yml b/stack-clients/docker-compose.yml index 1596f5b1..42751b47 100644 --- a/stack-clients/docker-compose.yml +++ b/stack-clients/docker-compose.yml @@ -1,6 +1,6 @@ services: stack-client: - image: ghcr.io/theworldavatar/stack-client${IMAGE_SUFFIX}:1.56.2 + image: ghcr.io/theworldavatar/stack-client${IMAGE_SUFFIX}:1.56.3-update-parent-SNAPSHOT secrets: - blazegraph_password - postgis_password diff --git a/stack-clients/pom.xml b/stack-clients/pom.xml index a76fd8d7..8bd6ce18 100644 --- a/stack-clients/pom.xml +++ b/stack-clients/pom.xml @@ -7,7 +7,7 @@ com.cmclinnovations stack-clients - 1.56.2 + 1.56.3-update-parent-SNAPSHOT Stack Clients https://theworldavatar.io @@ -15,7 +15,7 @@ uk.ac.cam.cares.jps jps-parent-pom - 2.3.2 + 2.4.0 diff --git a/stack-data-uploader/docker-compose.yml b/stack-data-uploader/docker-compose.yml index 2492d196..2ab1be15 100644 --- a/stack-data-uploader/docker-compose.yml +++ b/stack-data-uploader/docker-compose.yml @@ -1,6 +1,6 @@ services: stack-data-uploader: - image: ghcr.io/theworldavatar/stack-data-uploader${IMAGE_SUFFIX}:1.56.2 + image: ghcr.io/theworldavatar/stack-data-uploader${IMAGE_SUFFIX}:1.56.3-update-parent-SNAPSHOT secrets: - blazegraph_password - postgis_password diff --git a/stack-data-uploader/pom.xml b/stack-data-uploader/pom.xml index 9a621abf..e22659f2 100644 --- a/stack-data-uploader/pom.xml +++ b/stack-data-uploader/pom.xml @@ -7,7 +7,7 @@ com.cmclinnovations stack-data-uploader - 1.56.2 + 1.56.3-update-parent-SNAPSHOT Stack Data Uploader https://theworldavatar.io @@ -38,7 +38,7 @@ com.cmclinnovations stack-clients - 1.56.2 + 1.56.3-update-parent-SNAPSHOT diff --git a/stack-manager/docker-compose.yml b/stack-manager/docker-compose.yml index 83e67e62..609e3b0a 100644 --- a/stack-manager/docker-compose.yml +++ b/stack-manager/docker-compose.yml @@ -1,6 +1,6 @@ services: stack-manager: - image: ghcr.io/theworldavatar/stack-manager${IMAGE_SUFFIX}:1.56.2 + image: ghcr.io/theworldavatar/stack-manager${IMAGE_SUFFIX}:1.56.3-update-parent-SNAPSHOT environment: EXTERNAL_PORT: "${EXTERNAL_PORT-3838}" STACK_BASE_DIR: "${STACK_BASE_DIR}" diff --git a/stack-manager/pom.xml b/stack-manager/pom.xml index ab2fc7ba..0ed7134d 100644 --- a/stack-manager/pom.xml +++ b/stack-manager/pom.xml @@ -7,7 +7,7 @@ com.cmclinnovations stack-manager - 1.56.2 + 1.56.3-update-parent-SNAPSHOT Stack Manager https://theworldavatar.io @@ -38,7 +38,7 @@ com.cmclinnovations stack-clients - 1.56.2 + 1.56.3-update-parent-SNAPSHOT From 711978e7841690b0bbe3058cc81e4308bb9542de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Tue, 20 Jan 2026 12:19:09 +0000 Subject: [PATCH 08/28] fix version --- stack-clients/docker-compose.yml | 2 +- stack-clients/pom.xml | 2 +- stack-data-uploader/docker-compose.yml | 2 +- stack-data-uploader/pom.xml | 4 ++-- stack-manager/docker-compose.yml | 2 +- stack-manager/pom.xml | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/stack-clients/docker-compose.yml b/stack-clients/docker-compose.yml index 42751b47..2f32c919 100644 --- a/stack-clients/docker-compose.yml +++ b/stack-clients/docker-compose.yml @@ -1,6 +1,6 @@ services: stack-client: - image: ghcr.io/theworldavatar/stack-client${IMAGE_SUFFIX}:1.56.3-update-parent-SNAPSHOT + image: ghcr.io/theworldavatar/stack-client${IMAGE_SUFFIX}:1.56.3 secrets: - blazegraph_password - postgis_password diff --git a/stack-clients/pom.xml b/stack-clients/pom.xml index 8bd6ce18..4d14832d 100644 --- a/stack-clients/pom.xml +++ b/stack-clients/pom.xml @@ -7,7 +7,7 @@ com.cmclinnovations stack-clients - 1.56.3-update-parent-SNAPSHOT + 1.56.3 Stack Clients https://theworldavatar.io diff --git a/stack-data-uploader/docker-compose.yml b/stack-data-uploader/docker-compose.yml index 2ab1be15..c08d56a0 100644 --- a/stack-data-uploader/docker-compose.yml +++ b/stack-data-uploader/docker-compose.yml @@ -1,6 +1,6 @@ services: stack-data-uploader: - image: ghcr.io/theworldavatar/stack-data-uploader${IMAGE_SUFFIX}:1.56.3-update-parent-SNAPSHOT + image: ghcr.io/theworldavatar/stack-data-uploader${IMAGE_SUFFIX}:1.56.3 secrets: - blazegraph_password - postgis_password diff --git a/stack-data-uploader/pom.xml b/stack-data-uploader/pom.xml index e22659f2..04986269 100644 --- a/stack-data-uploader/pom.xml +++ b/stack-data-uploader/pom.xml @@ -7,7 +7,7 @@ com.cmclinnovations stack-data-uploader - 1.56.3-update-parent-SNAPSHOT + 1.56.3 Stack Data Uploader https://theworldavatar.io @@ -38,7 +38,7 @@ com.cmclinnovations stack-clients - 1.56.3-update-parent-SNAPSHOT + 1.56.3 diff --git a/stack-manager/docker-compose.yml b/stack-manager/docker-compose.yml index 609e3b0a..95afaba4 100644 --- a/stack-manager/docker-compose.yml +++ b/stack-manager/docker-compose.yml @@ -1,6 +1,6 @@ services: stack-manager: - image: ghcr.io/theworldavatar/stack-manager${IMAGE_SUFFIX}:1.56.3-update-parent-SNAPSHOT + image: ghcr.io/theworldavatar/stack-manager${IMAGE_SUFFIX}:1.56.3 environment: EXTERNAL_PORT: "${EXTERNAL_PORT-3838}" STACK_BASE_DIR: "${STACK_BASE_DIR}" diff --git a/stack-manager/pom.xml b/stack-manager/pom.xml index 0ed7134d..bd991a9b 100644 --- a/stack-manager/pom.xml +++ b/stack-manager/pom.xml @@ -7,7 +7,7 @@ com.cmclinnovations stack-manager - 1.56.3-update-parent-SNAPSHOT + 1.56.3 Stack Manager https://theworldavatar.io @@ -38,7 +38,7 @@ com.cmclinnovations stack-clients - 1.56.3-update-parent-SNAPSHOT + 1.56.3 From 53e725ca0e2307fa8b97ae0441c3df8da9d91e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Tue, 20 Jan 2026 14:02:11 +0000 Subject: [PATCH 09/28] rm redundant import --- .../com/cmclinnovations/stack/clients/core/datasets/RML.java | 1 - 1 file changed, 1 deletion(-) diff --git a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/core/datasets/RML.java b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/core/datasets/RML.java index a9ca70f6..cb6deb73 100644 --- a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/core/datasets/RML.java +++ b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/core/datasets/RML.java @@ -1,7 +1,6 @@ package com.cmclinnovations.stack.clients.core.datasets; import java.nio.file.Path; -import java.util.Map; import com.cmclinnovations.stack.clients.rml.RmlMapperClient; From 7667dd97fae47f907d9cb683e14cae2f4c28fac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Tue, 20 Jan 2026 14:07:25 +0000 Subject: [PATCH 10/28] replace deprecated docker api call --- .../stack/clients/docker/DockerClient.java | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/docker/DockerClient.java b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/docker/DockerClient.java index 85391b99..a8b5cde4 100644 --- a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/docker/DockerClient.java +++ b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/docker/DockerClient.java @@ -53,7 +53,9 @@ import com.github.dockerjava.core.DefaultDockerClientConfig.Builder; import com.github.dockerjava.core.DockerClientBuilder; import com.github.dockerjava.core.DockerClientConfig; -import com.github.dockerjava.core.command.ExecStartResultCallback; +import com.github.dockerjava.api.async.ResultCallback; +import com.github.dockerjava.api.model.Frame; +import com.github.dockerjava.api.model.StreamType; import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; import com.github.dockerjava.transport.DockerHttpClient; @@ -115,7 +117,6 @@ public String executeSimpleCommand(String containerId, String... cmd) { .withOutputStream(outputStream) .withErrorStream(outputStream) .exec(); - String output = outputStream.toString(); return execId; } @@ -231,11 +232,21 @@ public String exec() { execStartCmd.withStdIn(inputStream); } - // ExecStartResultCallback is marked deprecated but seems to do exactly what we - // want and without knowing why it is deprecated any issues with it can't be - // overcome anyway. - try (ExecStartResultCallback result = execStartCmd - .exec(new ExecStartResultCallback(outputStream, errorStream))) { + try (ResultCallback.Adapter result = execStartCmd + .exec(new ResultCallback.Adapter() { + @Override + public void onNext(Frame frame) { + try { + if (frame.getStreamType() == StreamType.STDOUT && outputStream != null) { + outputStream.write(frame.getPayload()); + } else if (frame.getStreamType() == StreamType.STDERR && errorStream != null) { + errorStream.write(frame.getPayload()); + } + } catch (IOException ex) { + throw new RuntimeException("Failed to write frame payload", ex); + } + } + })) { if (wait) { if (!result.awaitCompletion(evaluationTimeout, TimeUnit.SECONDS)) { LOGGER.warn("Docker exec command '{}' still running after the {} second execution timeout.", @@ -553,7 +564,7 @@ public boolean isContainerUp(String containerName) { public String getContainerId(String containerName) { return getContainer(containerName).map(Container::getId) - .orElseThrow(() -> new NoSuchElementException("Cannot get container "+containerName+".")); + .orElseThrow(() -> new NoSuchElementException("Cannot get container " + containerName + ".")); } private Map> convertToConfigFilterMap(String configName, Map labelMap) { From dc8dee177ac240dbad1b5838b1eabeb64fcd1a00 Mon Sep 17 00:00:00 2001 From: George Brownbridge Date: Tue, 6 Jan 2026 12:01:42 +0000 Subject: [PATCH 11/28] add-traefik-support: Added a stack config setting to allow the reverse-proxy to be specified. --- .../stack/clients/core/StackClient.java | 9 +++++++++ .../cmclinnovations/stack/services/ServiceManager.java | 10 ++++++---- .../src/main/java/com/cmclinnovations/stack/Stack.java | 1 + .../java/com/cmclinnovations/stack/StackConfig.java | 8 ++++++++ 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/core/StackClient.java b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/core/StackClient.java index 8350f876..2c3d3c95 100644 --- a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/core/StackClient.java +++ b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/core/StackClient.java @@ -36,6 +36,7 @@ public final class StackClient { private static StackHost stackHost = new StackHost(); private static boolean isolated = false; + private static String reverseProxyName; static { String envVarStackName = System.getenv(StackClient.STACK_NAME_KEY); @@ -107,6 +108,14 @@ public static Path getAbsDataPath() { return getStackBaseDir().resolve("inputs").resolve("data"); } + public static void setReverseProxyName(String reverseProxyName) { + StackClient.reverseProxyName = reverseProxyName; + } + + public static String getReverseProxyName() { + return reverseProxyName; + } + /** * Get a RemoteRDBStoreClient for the named Postgres RDB running in this stack. * diff --git a/stack-clients/src/main/java/com/cmclinnovations/stack/services/ServiceManager.java b/stack-clients/src/main/java/com/cmclinnovations/stack/services/ServiceManager.java index 14a92f7e..d87d2dc4 100644 --- a/stack-clients/src/main/java/com/cmclinnovations/stack/services/ServiceManager.java +++ b/stack-clients/src/main/java/com/cmclinnovations/stack/services/ServiceManager.java @@ -167,15 +167,17 @@ public S initialiseService(String stackName, String serviceN DockerService dockerService = getOrInitialiseService(stackName, StackClient.getContainerEngineName()); dockerService.doPreStartUpConfiguration(newContainerService); + if (!StackClient.getReverseProxyName().equals(serviceName)) { + ReverseProxyService reverseProxyService = getOrInitialiseService(stackName, + StackClient.getReverseProxyName()); + reverseProxyService.addService(newContainerService); + } + dockerService.writeEndpointConfigs(newContainerService); if (dockerService.startContainer(newContainerService)) { dockerService.doFirstTimePostStartUpConfiguration(newContainerService); } dockerService.doEveryTimePostStartUpConfiguration(newContainerService); - if (!NginxService.TYPE.equals(serviceName)) { - ReverseProxyService reverseProxyService = getOrInitialiseService(stackName, NginxService.TYPE); - reverseProxyService.addService(newContainerService); - } } services.put(serviceName, newService); diff --git a/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java b/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java index 692cdc6e..14136c07 100644 --- a/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java +++ b/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java @@ -83,6 +83,7 @@ private Stack(String name, ServiceManager manager, StackConfig config) { if (null != config) { StackClient.setStackHost(config.getHost()); StackClient.setIsolated(config.isIsolated()); + StackClient.setReverseProxyName(config.getReverseProxyName()); } } diff --git a/stack-manager/src/main/java/com/cmclinnovations/stack/StackConfig.java b/stack-manager/src/main/java/com/cmclinnovations/stack/StackConfig.java index 95b2d62a..7c4470ec 100644 --- a/stack-manager/src/main/java/com/cmclinnovations/stack/StackConfig.java +++ b/stack-manager/src/main/java/com/cmclinnovations/stack/StackConfig.java @@ -7,6 +7,7 @@ import java.util.Map; import com.cmclinnovations.stack.clients.core.StackHost; +import com.cmclinnovations.stack.services.NginxService; import com.fasterxml.jackson.annotation.JsonProperty; public class StackConfig { @@ -30,6 +31,9 @@ private enum Selector { @JsonProperty private final Boolean isolated = false; + @JsonProperty("reverseProxy") + private final String reverseProxy = NginxService.TYPE; + @JsonProperty("hostName") private void setHostName(String hostName) { host = new StackHost(hostName); @@ -54,4 +58,8 @@ Map getVolumes() { public boolean isIsolated() { return isolated; } + + public String getReverseProxyName() { + return reverseProxy; + } } From d04d90d5fb3cdb19dc4407a255faf89cd620dfce Mon Sep 17 00:00:00 2001 From: George Brownbridge Date: Tue, 6 Jan 2026 12:03:13 +0000 Subject: [PATCH 12/28] add-traefik-support: Bumped stack version. --- stack-clients/docker-compose.yml | 2 +- stack-clients/pom.xml | 2 +- stack-data-uploader/docker-compose.yml | 2 +- stack-data-uploader/pom.xml | 4 ++-- stack-manager/docker-compose.yml | 2 +- stack-manager/pom.xml | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/stack-clients/docker-compose.yml b/stack-clients/docker-compose.yml index 2f32c919..5801c070 100644 --- a/stack-clients/docker-compose.yml +++ b/stack-clients/docker-compose.yml @@ -1,6 +1,6 @@ services: stack-client: - image: ghcr.io/theworldavatar/stack-client${IMAGE_SUFFIX}:1.56.3 + image: ghcr.io/theworldavatar/stack-client${IMAGE_SUFFIX}:1.57.0-traefik-support-SNAPSHOT secrets: - blazegraph_password - postgis_password diff --git a/stack-clients/pom.xml b/stack-clients/pom.xml index 4d14832d..b1d14acb 100644 --- a/stack-clients/pom.xml +++ b/stack-clients/pom.xml @@ -7,7 +7,7 @@ com.cmclinnovations stack-clients - 1.56.3 + 1.57.0-traefik-support-SNAPSHOT Stack Clients https://theworldavatar.io diff --git a/stack-data-uploader/docker-compose.yml b/stack-data-uploader/docker-compose.yml index c08d56a0..c110fea3 100644 --- a/stack-data-uploader/docker-compose.yml +++ b/stack-data-uploader/docker-compose.yml @@ -1,6 +1,6 @@ services: stack-data-uploader: - image: ghcr.io/theworldavatar/stack-data-uploader${IMAGE_SUFFIX}:1.56.3 + image: ghcr.io/theworldavatar/stack-data-uploader${IMAGE_SUFFIX}:1.57.0-traefik-support-SNAPSHOT secrets: - blazegraph_password - postgis_password diff --git a/stack-data-uploader/pom.xml b/stack-data-uploader/pom.xml index 04986269..4f6025ef 100644 --- a/stack-data-uploader/pom.xml +++ b/stack-data-uploader/pom.xml @@ -7,7 +7,7 @@ com.cmclinnovations stack-data-uploader - 1.56.3 + 1.57.0-traefik-support-SNAPSHOT Stack Data Uploader https://theworldavatar.io @@ -38,7 +38,7 @@ com.cmclinnovations stack-clients - 1.56.3 + 1.57.0-traefik-support-SNAPSHOT diff --git a/stack-manager/docker-compose.yml b/stack-manager/docker-compose.yml index 95afaba4..f2536823 100644 --- a/stack-manager/docker-compose.yml +++ b/stack-manager/docker-compose.yml @@ -1,6 +1,6 @@ services: stack-manager: - image: ghcr.io/theworldavatar/stack-manager${IMAGE_SUFFIX}:1.56.3 + image: ghcr.io/theworldavatar/stack-manager${IMAGE_SUFFIX}:1.57.0-traefik-support-SNAPSHOT environment: EXTERNAL_PORT: "${EXTERNAL_PORT-3838}" STACK_BASE_DIR: "${STACK_BASE_DIR}" diff --git a/stack-manager/pom.xml b/stack-manager/pom.xml index bd991a9b..d8984e8a 100644 --- a/stack-manager/pom.xml +++ b/stack-manager/pom.xml @@ -7,7 +7,7 @@ com.cmclinnovations stack-manager - 1.56.3 + 1.57.0-traefik-support-SNAPSHOT Stack Manager https://theworldavatar.io @@ -38,7 +38,7 @@ com.cmclinnovations stack-clients - 1.56.3 + 1.57.0-traefik-support-SNAPSHOT From 70871f6b95e079b07d92f97b6289d02393334050 Mon Sep 17 00:00:00 2001 From: George Brownbridge Date: Tue, 6 Jan 2026 12:07:39 +0000 Subject: [PATCH 13/28] add-traefik-support: Added "empty" `TraefikService` class. --- .../stack/services/TraefikService.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 stack-clients/src/main/java/com/cmclinnovations/stack/services/TraefikService.java diff --git a/stack-clients/src/main/java/com/cmclinnovations/stack/services/TraefikService.java b/stack-clients/src/main/java/com/cmclinnovations/stack/services/TraefikService.java new file mode 100644 index 00000000..090be337 --- /dev/null +++ b/stack-clients/src/main/java/com/cmclinnovations/stack/services/TraefikService.java @@ -0,0 +1,36 @@ +package com.cmclinnovations.stack.services; + +import java.util.HashMap; +import java.util.Map; + +import com.cmclinnovations.stack.clients.core.StackClient; +import com.cmclinnovations.stack.clients.utils.FileUtils; +import com.cmclinnovations.stack.services.config.Connection; +import com.cmclinnovations.stack.services.config.ServiceConfig; + +public class TraefikService extends ContainerService implements ReverseProxyService { + + public TraefikService(String stackName, ServiceConfig config) { + super(stackName, config); + } + + @Override + public void addService(ContainerService service) { + Map labels = service.getContainerSpec().getLabels(); + if (null == labels) { + labels = new HashMap<>(); + service.getContainerSpec().withLabels(labels); + } + labels.put("traefik.enable", "true"); + + service.getConfig().getEndpoints().forEach((name,connection) -> { + // TODO: Set the labels correctly + // labels.put("traefik.http.routers." + service.getContainerName() + ".rule", + // "PathPrefix(`" + FileUtils.fixSlashes(connection.getExternalPath().getPath(), true, false) + "`)"); + // labels.put( + // "traefik.http.services." + service.getServiceName() + ".loadbalancer.server.port", + // String.valueOf(connection.getInternalPort())); + }); + } + +} From e882d97a1086b96c4acf92ed611c5bdf19d261d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Fri, 16 Jan 2026 11:12:55 +0000 Subject: [PATCH 14/28] get debugger working --- stack-manager/.vscode/tasks.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/stack-manager/.vscode/tasks.json b/stack-manager/.vscode/tasks.json index af41b2f3..06455fe2 100644 --- a/stack-manager/.vscode/tasks.json +++ b/stack-manager/.vscode/tasks.json @@ -33,7 +33,10 @@ ], "options": { "shell": { - "executable": "bash" + "executable": "bash", + "args": [ + "-c" + ] } } }, From 14bb232e7b3fc5eb782ba26ec75a20594f8bcc4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Fri, 16 Jan 2026 14:57:27 +0000 Subject: [PATCH 15/28] traefik service config file --- .../stack/services/built-ins/traefik.json | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 stack-clients/src/main/resources/com/cmclinnovations/stack/services/built-ins/traefik.json diff --git a/stack-clients/src/main/resources/com/cmclinnovations/stack/services/built-ins/traefik.json b/stack-clients/src/main/resources/com/cmclinnovations/stack/services/built-ins/traefik.json new file mode 100644 index 00000000..ff525b4c --- /dev/null +++ b/stack-clients/src/main/resources/com/cmclinnovations/stack/services/built-ins/traefik.json @@ -0,0 +1,46 @@ +{ + "type": "traefik", + "ServiceSpec": { + "Name": "traefik", + "TaskTemplate": { + "ContainerSpec": { + "Image": "traefik:v3.6", + "Mounts": [ + { + "Type": "bind", + "Source": "/var/run/docker.sock", + "Target": "/var/run/docker.sock", + "ReadOnly": true + }, + { + "Type": "volume", + "Source": "traefik_config", + "Target": "/etc/traefik" + } + ] + } + }, + "EndpointSpec": { + "Ports": [ + { + "Name": "web", + "Protocol": "tcp", + "TargetPort": "80", + "PublishedPort": "3838" + }, + { + "Name": "websecure", + "Protocol": "tcp", + "TargetPort": "443", + "PublishedPort": "443" + }, + { + "Name": "dashboard", + "Protocol": "tcp", + "TargetPort": "8080", + "PublishedPort": "8080" + } + ] + } + } +} \ No newline at end of file From f0d7239908bfc7744f220440a5fbc2ad8faf7914 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Fri, 16 Jan 2026 14:57:47 +0000 Subject: [PATCH 16/28] addreverseproxy method in stack.java --- .../main/java/com/cmclinnovations/stack/Stack.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java b/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java index 14136c07..159b769d 100644 --- a/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java +++ b/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java @@ -135,6 +135,9 @@ private List calculateSelectedServicesFromConfig(List defaultSer + ", explicitly included by user. Please remove them from the \"includes\" list in the stack config file."); } + // Add the reverse proxy service specified in the config + addReverseProxyIfAbsent(selectedServices); + // Add user specified services selectedServices.addAll(config.getIncludedServices()); // Remove any excluded services (default and user specified) @@ -142,6 +145,15 @@ private List calculateSelectedServicesFromConfig(List defaultSer return selectedServices; } + private void addReverseProxyIfAbsent(List selectedServices) { + String reverseProxyService = config.getReverseProxyName(); + if (reverseProxyService != null && !reverseProxyService.isEmpty()) { + if (!selectedServices.contains(reverseProxyService)) { + selectedServices.add(reverseProxyService); + } + } + } + private void handleUserSuppliedData(List selectedServices) { Map volumes = config.getVolumes(); From 8bc930532b44569921cdf280a36c64db9690c909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Tue, 20 Jan 2026 14:02:11 +0000 Subject: [PATCH 17/28] rm redundant import --- .../com/cmclinnovations/stack/clients/core/datasets/RML.java | 1 - 1 file changed, 1 deletion(-) diff --git a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/core/datasets/RML.java b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/core/datasets/RML.java index a9ca70f6..cb6deb73 100644 --- a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/core/datasets/RML.java +++ b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/core/datasets/RML.java @@ -1,7 +1,6 @@ package com.cmclinnovations.stack.clients.core.datasets; import java.nio.file.Path; -import java.util.Map; import com.cmclinnovations.stack.clients.rml.RmlMapperClient; From e81af7a1eb429e1193d04147d78fc9ef97cb07b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Tue, 20 Jan 2026 14:07:25 +0000 Subject: [PATCH 18/28] replace deprecated docker api call --- .../stack/clients/docker/DockerClient.java | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/docker/DockerClient.java b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/docker/DockerClient.java index 85391b99..a8b5cde4 100644 --- a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/docker/DockerClient.java +++ b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/docker/DockerClient.java @@ -53,7 +53,9 @@ import com.github.dockerjava.core.DefaultDockerClientConfig.Builder; import com.github.dockerjava.core.DockerClientBuilder; import com.github.dockerjava.core.DockerClientConfig; -import com.github.dockerjava.core.command.ExecStartResultCallback; +import com.github.dockerjava.api.async.ResultCallback; +import com.github.dockerjava.api.model.Frame; +import com.github.dockerjava.api.model.StreamType; import com.github.dockerjava.httpclient5.ApacheDockerHttpClient; import com.github.dockerjava.transport.DockerHttpClient; @@ -115,7 +117,6 @@ public String executeSimpleCommand(String containerId, String... cmd) { .withOutputStream(outputStream) .withErrorStream(outputStream) .exec(); - String output = outputStream.toString(); return execId; } @@ -231,11 +232,21 @@ public String exec() { execStartCmd.withStdIn(inputStream); } - // ExecStartResultCallback is marked deprecated but seems to do exactly what we - // want and without knowing why it is deprecated any issues with it can't be - // overcome anyway. - try (ExecStartResultCallback result = execStartCmd - .exec(new ExecStartResultCallback(outputStream, errorStream))) { + try (ResultCallback.Adapter result = execStartCmd + .exec(new ResultCallback.Adapter() { + @Override + public void onNext(Frame frame) { + try { + if (frame.getStreamType() == StreamType.STDOUT && outputStream != null) { + outputStream.write(frame.getPayload()); + } else if (frame.getStreamType() == StreamType.STDERR && errorStream != null) { + errorStream.write(frame.getPayload()); + } + } catch (IOException ex) { + throw new RuntimeException("Failed to write frame payload", ex); + } + } + })) { if (wait) { if (!result.awaitCompletion(evaluationTimeout, TimeUnit.SECONDS)) { LOGGER.warn("Docker exec command '{}' still running after the {} second execution timeout.", @@ -553,7 +564,7 @@ public boolean isContainerUp(String containerName) { public String getContainerId(String containerName) { return getContainer(containerName).map(Container::getId) - .orElseThrow(() -> new NoSuchElementException("Cannot get container "+containerName+".")); + .orElseThrow(() -> new NoSuchElementException("Cannot get container " + containerName + ".")); } private Map> convertToConfigFilterMap(String configName, Map labelMap) { From 26f6c7dbfb21b2b930319beab7b5d2fa55ed52a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Wed, 21 Jan 2026 11:32:31 +0000 Subject: [PATCH 19/28] rm duplicate docker socket mount for traefik --- .../cmclinnovations/stack/services/built-ins/traefik.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/stack-clients/src/main/resources/com/cmclinnovations/stack/services/built-ins/traefik.json b/stack-clients/src/main/resources/com/cmclinnovations/stack/services/built-ins/traefik.json index ff525b4c..31e9f55d 100644 --- a/stack-clients/src/main/resources/com/cmclinnovations/stack/services/built-ins/traefik.json +++ b/stack-clients/src/main/resources/com/cmclinnovations/stack/services/built-ins/traefik.json @@ -6,12 +6,6 @@ "ContainerSpec": { "Image": "traefik:v3.6", "Mounts": [ - { - "Type": "bind", - "Source": "/var/run/docker.sock", - "Target": "/var/run/docker.sock", - "ReadOnly": true - }, { "Type": "volume", "Source": "traefik_config", From 5784de33c26bb68fef45be285fe32fbae7cdbe99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Wed, 21 Jan 2026 11:32:50 +0000 Subject: [PATCH 20/28] give traefik service clasee a type field --- .../stack/services/TraefikService.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/stack-clients/src/main/java/com/cmclinnovations/stack/services/TraefikService.java b/stack-clients/src/main/java/com/cmclinnovations/stack/services/TraefikService.java index 090be337..d8218f69 100644 --- a/stack-clients/src/main/java/com/cmclinnovations/stack/services/TraefikService.java +++ b/stack-clients/src/main/java/com/cmclinnovations/stack/services/TraefikService.java @@ -3,13 +3,12 @@ import java.util.HashMap; import java.util.Map; -import com.cmclinnovations.stack.clients.core.StackClient; -import com.cmclinnovations.stack.clients.utils.FileUtils; -import com.cmclinnovations.stack.services.config.Connection; import com.cmclinnovations.stack.services.config.ServiceConfig; public class TraefikService extends ContainerService implements ReverseProxyService { + public static final String TYPE = "traefik"; + public TraefikService(String stackName, ServiceConfig config) { super(stackName, config); } @@ -23,13 +22,15 @@ public void addService(ContainerService service) { } labels.put("traefik.enable", "true"); - service.getConfig().getEndpoints().forEach((name,connection) -> { + service.getConfig().getEndpoints().forEach((name, connection) -> { // TODO: Set the labels correctly // labels.put("traefik.http.routers." + service.getContainerName() + ".rule", - // "PathPrefix(`" + FileUtils.fixSlashes(connection.getExternalPath().getPath(), true, false) + "`)"); + // "PathPrefix(`" + FileUtils.fixSlashes(connection.getExternalPath().getPath(), + // true, false) + "`)"); // labels.put( - // "traefik.http.services." + service.getServiceName() + ".loadbalancer.server.port", - // String.valueOf(connection.getInternalPort())); + // "traefik.http.services." + service.getServiceName() + + // ".loadbalancer.server.port", + // String.valueOf(connection.getInternalPort())); }); } From 1b4617e32bf7b3b5f7e0bf5106bc74f7ca72d184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Wed, 21 Jan 2026 11:39:34 +0000 Subject: [PATCH 21/28] rm bad traefik service starter method --- .../main/java/com/cmclinnovations/stack/Stack.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java b/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java index 159b769d..14136c07 100644 --- a/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java +++ b/stack-manager/src/main/java/com/cmclinnovations/stack/Stack.java @@ -135,9 +135,6 @@ private List calculateSelectedServicesFromConfig(List defaultSer + ", explicitly included by user. Please remove them from the \"includes\" list in the stack config file."); } - // Add the reverse proxy service specified in the config - addReverseProxyIfAbsent(selectedServices); - // Add user specified services selectedServices.addAll(config.getIncludedServices()); // Remove any excluded services (default and user specified) @@ -145,15 +142,6 @@ private List calculateSelectedServicesFromConfig(List defaultSer return selectedServices; } - private void addReverseProxyIfAbsent(List selectedServices) { - String reverseProxyService = config.getReverseProxyName(); - if (reverseProxyService != null && !reverseProxyService.isEmpty()) { - if (!selectedServices.contains(reverseProxyService)) { - selectedServices.add(reverseProxyService); - } - } - } - private void handleUserSuppliedData(List selectedServices) { Map volumes = config.getVolumes(); From 6b50203d2002c5909156f220815843b6da6f596c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Wed, 21 Jan 2026 11:40:09 +0000 Subject: [PATCH 22/28] fix the reverseProxy string field in StackConfig --- .../src/main/java/com/cmclinnovations/stack/StackConfig.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/stack-manager/src/main/java/com/cmclinnovations/stack/StackConfig.java b/stack-manager/src/main/java/com/cmclinnovations/stack/StackConfig.java index 7c4470ec..fc97f92a 100644 --- a/stack-manager/src/main/java/com/cmclinnovations/stack/StackConfig.java +++ b/stack-manager/src/main/java/com/cmclinnovations/stack/StackConfig.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import com.cmclinnovations.stack.clients.core.StackHost; import com.cmclinnovations.stack.services.NginxService; @@ -32,7 +33,7 @@ private enum Selector { private final Boolean isolated = false; @JsonProperty("reverseProxy") - private final String reverseProxy = NginxService.TYPE; + private final Optional reverseProxy = Optional.empty(); @JsonProperty("hostName") private void setHostName(String hostName) { @@ -60,6 +61,6 @@ public boolean isIsolated() { } public String getReverseProxyName() { - return reverseProxy; + return reverseProxy.orElse(NginxService.TYPE); } } From 55d4d6b4e558cba6964fa93fdd591365957b392e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Wed, 21 Jan 2026 11:42:45 +0000 Subject: [PATCH 23/28] rm an unused import --- .../com/cmclinnovations/stack/clients/rdf4j/Rdf4jClient.java | 1 - 1 file changed, 1 deletion(-) diff --git a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/rdf4j/Rdf4jClient.java b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/rdf4j/Rdf4jClient.java index d75c0528..6dd94585 100644 --- a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/rdf4j/Rdf4jClient.java +++ b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/rdf4j/Rdf4jClient.java @@ -4,7 +4,6 @@ import org.eclipse.rdf4j.federated.repository.FedXRepositoryConfigBuilder; import org.eclipse.rdf4j.repository.config.RepositoryConfig; -import org.eclipse.rdf4j.repository.config.RepositoryImplConfig; import org.eclipse.rdf4j.repository.manager.RemoteRepositoryManager; import org.eclipse.rdf4j.repository.sail.config.SailRepositoryConfig; import org.eclipse.rdf4j.repository.sparql.config.SPARQLRepositoryConfig; From 702924e768283d9878387aa2851ed4c7217e03c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Wed, 21 Jan 2026 16:13:40 +0000 Subject: [PATCH 24/28] organise imports --- .../stack/clients/docker/PodmanClient.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/docker/PodmanClient.java b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/docker/PodmanClient.java index 3ee82684..c0a87e01 100644 --- a/stack-clients/src/main/java/com/cmclinnovations/stack/clients/docker/PodmanClient.java +++ b/stack-clients/src/main/java/com/cmclinnovations/stack/clients/docker/PodmanClient.java @@ -7,15 +7,16 @@ import com.cmclinnovations.stack.clients.core.StackClient; import com.cmclinnovations.stack.clients.utils.JsonHelper; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.dockerjava.api.model.Config; +import com.github.dockerjava.jaxrs.ApiClientExtension; + import io.theworldavatar.swagger.podman.ApiClient; import io.theworldavatar.swagger.podman.ApiException; import io.theworldavatar.swagger.podman.api.NetworksApi; import io.theworldavatar.swagger.podman.api.SecretsApi; import io.theworldavatar.swagger.podman.model.Network; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.dockerjava.api.model.Config; -import com.github.dockerjava.jaxrs.ApiClientExtension; public class PodmanClient extends DockerClient { From fbaa083cef5efbe81aa6b359511e0905c6ab6a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Wed, 21 Jan 2026 16:13:57 +0000 Subject: [PATCH 25/28] add a traefik config file --- .../services/traefik/configs/traefik.yml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 stack-clients/src/main/resources/com/cmclinnovations/stack/services/traefik/configs/traefik.yml diff --git a/stack-clients/src/main/resources/com/cmclinnovations/stack/services/traefik/configs/traefik.yml b/stack-clients/src/main/resources/com/cmclinnovations/stack/services/traefik/configs/traefik.yml new file mode 100644 index 00000000..78e77013 --- /dev/null +++ b/stack-clients/src/main/resources/com/cmclinnovations/stack/services/traefik/configs/traefik.yml @@ -0,0 +1,21 @@ +api: + dashboard: true + insecure: true + +entryPoints: + web: + address: ":80" + websecure: + address: ":443" + traefik: + address: ":8080" + +providers: + docker: + endpoint: "unix:///var/run/docker.sock" + exposedByDefault: false + swarmMode: true + network: "${STACK_NAME}" + +log: + level: INFO From 9224d02fb6fbc8b6f79afcbf2a89f8aecf0acc6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Wed, 21 Jan 2026 16:15:30 +0000 Subject: [PATCH 26/28] implement traefik config method --- .../stack/services/TraefikService.java | 64 +++++++++++++++++-- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/stack-clients/src/main/java/com/cmclinnovations/stack/services/TraefikService.java b/stack-clients/src/main/java/com/cmclinnovations/stack/services/TraefikService.java index d8218f69..02c91221 100644 --- a/stack-clients/src/main/java/com/cmclinnovations/stack/services/TraefikService.java +++ b/stack-clients/src/main/java/com/cmclinnovations/stack/services/TraefikService.java @@ -1,28 +1,80 @@ package com.cmclinnovations.stack.services; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; +import com.cmclinnovations.stack.clients.core.StackClient; +import com.cmclinnovations.stack.clients.utils.FileUtils; import com.cmclinnovations.stack.services.config.ServiceConfig; +import com.github.dockerjava.api.model.ContainerSpec; public class TraefikService extends ContainerService implements ReverseProxyService { public static final String TYPE = "traefik"; + private static final String TRAEFIK_CONF_DIR = "/etc/traefik/"; + private static final String TRAEFIK_CONFIG_TEMPLATE = "traefik/configs/traefik.yml"; + public TraefikService(String stackName, ServiceConfig config) { super(stackName, config); } @Override - public void addService(ContainerService service) { - Map labels = service.getContainerSpec().getLabels(); - if (null == labels) { - labels = new HashMap<>(); - service.getContainerSpec().withLabels(labels); + public void doFirstTimePostStartUpConfiguration() { + try (InputStream inStream = new BufferedInputStream( + TraefikService.class.getResourceAsStream(TRAEFIK_CONFIG_TEMPLATE))) { + + String configContent = new String(inStream.readAllBytes(), StandardCharsets.UTF_8); + // Replace the ${STACK_NAME} placeholder with actual stack name + String stackName = getEnvironmentVariable(StackClient.STACK_NAME_KEY); + configContent = configContent.replace("${STACK_NAME}", stackName); + + // Write the configuration file to the container + Map files = new HashMap<>(); + files.put("traefik.yml", configContent.getBytes(StandardCharsets.UTF_8)); + sendFilesContent(files, TRAEFIK_CONF_DIR); + + } catch (IOException ex) { + throw new RuntimeException("Failed to configure Traefik", ex); } + } + + @Override + public void addService(ContainerService service) { + ContainerSpec containerSpec = service.getContainerSpec(); + Map labels = new HashMap<>(); + containerSpec.withLabels(labels); labels.put("traefik.enable", "true"); - service.getConfig().getEndpoints().forEach((name, connection) -> { + service.getConfig().getEndpoints().forEach((endpointName, connection) -> { + URI externalPath = connection.getExternalPath(); + if (null != externalPath) { + String serviceName = service.getName(); + String routerName = serviceName + "_" + endpointName; + String pathPrefix = FileUtils.fixSlashes(externalPath.getPath(), true, false); + + // Configure router with path prefix rule + labels.put("traefik.http.routers." + routerName + ".rule", + "PathPrefix(`" + pathPrefix + "`)"); + labels.put("traefik.http.routers." + routerName + ".entrypoints", "web"); + + // Configure service with the internal port + URL url = connection.getUrl(); + int port = url.getPort(); + if (port == -1) { + port = 80; // Default port + } + labels.put("traefik.http.routers." + routerName + ".service", routerName); + labels.put("traefik.http.services." + routerName + ".loadbalancer.server.port", + String.valueOf(port)); + } + // TODO: Set the labels correctly // labels.put("traefik.http.routers." + service.getContainerName() + ".rule", // "PathPrefix(`" + FileUtils.fixSlashes(connection.getExternalPath().getPath(), From 0c469a57be43b86f0ebab5f3cd532ecd6f43a592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Thu, 22 Jan 2026 12:16:36 +0000 Subject: [PATCH 27/28] format and do imports on save for everyone --- stack-clients/.vscode/settings.json | 9 +++++++++ stack-data-uploader/.vscode/settings.json | 9 +++++++++ stack-manager/.vscode/settings.json | 9 +++++++++ 3 files changed, 27 insertions(+) create mode 100644 stack-clients/.vscode/settings.json create mode 100644 stack-data-uploader/.vscode/settings.json create mode 100644 stack-manager/.vscode/settings.json diff --git a/stack-clients/.vscode/settings.json b/stack-clients/.vscode/settings.json new file mode 100644 index 00000000..c7d20e1f --- /dev/null +++ b/stack-clients/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, + "[java]": { + "editor.formatOnSave": true + } +} diff --git a/stack-data-uploader/.vscode/settings.json b/stack-data-uploader/.vscode/settings.json new file mode 100644 index 00000000..c7d20e1f --- /dev/null +++ b/stack-data-uploader/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, + "[java]": { + "editor.formatOnSave": true + } +} diff --git a/stack-manager/.vscode/settings.json b/stack-manager/.vscode/settings.json new file mode 100644 index 00000000..c7d20e1f --- /dev/null +++ b/stack-manager/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, + "[java]": { + "editor.formatOnSave": true + } +} From a95a58a44f99f38bf3b7782404488db07b8f4817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ois=C3=ADn?= Date: Thu, 22 Jan 2026 12:17:03 +0000 Subject: [PATCH 28/28] fix the provider in traefik.yaml --- .../stack/services/traefik/configs/traefik.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stack-clients/src/main/resources/com/cmclinnovations/stack/services/traefik/configs/traefik.yml b/stack-clients/src/main/resources/com/cmclinnovations/stack/services/traefik/configs/traefik.yml index 78e77013..8c0a18a7 100644 --- a/stack-clients/src/main/resources/com/cmclinnovations/stack/services/traefik/configs/traefik.yml +++ b/stack-clients/src/main/resources/com/cmclinnovations/stack/services/traefik/configs/traefik.yml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://json.schemastore.org/traefik-v3.json api: dashboard: true insecure: true @@ -11,10 +12,9 @@ entryPoints: address: ":8080" providers: - docker: + swarm: endpoint: "unix:///var/run/docker.sock" exposedByDefault: false - swarmMode: true network: "${STACK_NAME}" log: