diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DeploymentExecutor.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DeploymentExecutor.java index 384832e1..f56e91aa 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DeploymentExecutor.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DeploymentExecutor.java @@ -104,6 +104,28 @@ public class DeploymentExecutor { updateStage(deployment.id(), DeployStage.PRE_FLIGHT); preFlightChecks(jarPath, config); + // Resolve runtime type + String resolvedRuntimeType = config.runtimeType(); + String mainClass = null; + if ("auto".equalsIgnoreCase(resolvedRuntimeType)) { + AppVersion appVersion = appService.getVersion(deployment.appVersionId()); + if (appVersion.detectedRuntimeType() == null) { + throw new IllegalStateException( + "Could not detect runtime type for JAR '" + appVersion.jarFilename() + + "'. Set runtimeType explicitly in app configuration."); + } + resolvedRuntimeType = appVersion.detectedRuntimeType(); + mainClass = appVersion.detectedMainClass(); + } else if ("plain-java".equals(resolvedRuntimeType)) { + AppVersion appVersion = appService.getVersion(deployment.appVersionId()); + mainClass = appVersion.detectedMainClass(); + if (mainClass == null) { + throw new IllegalStateException( + "Runtime type 'plain-java' requires a Main-Class in the JAR manifest, " + + "but none was detected for '" + appVersion.jarFilename() + "'."); + } + } + // === PULL IMAGE === updateStage(deployment.id(), DeployStage.PULL_IMAGE); // Docker pulls on create if not present locally @@ -147,7 +169,8 @@ public class DeploymentExecutor { config.memoryLimitBytes(), config.memoryReserveBytes(), config.dockerCpuShares(), config.dockerCpuQuota(), config.exposedPorts(), agentHealthPort, - "on-failure", 3 + "on-failure", 3, + resolvedRuntimeType, config.customArgs(), mainClass ); String containerId = orchestrator.startContainer(request); @@ -277,6 +300,8 @@ public class DeploymentExecutor { envVars.put("CAMELEER_AGENT_EXPORT_ENDPOINT", config.serverUrl()); envVars.put("CAMELEER_AGENT_ROUTECONTROL_ENABLED", String.valueOf(config.routeControlEnabled())); envVars.put("CAMELEER_AGENT_REPLAY_ENABLED", String.valueOf(config.replayEnabled())); + envVars.put("CAMELEER_AGENT_HEALTH_ENABLED", "true"); + envVars.put("CAMELEER_AGENT_HEALTH_PORT", String.valueOf(agentHealthPort)); if (bootstrapToken != null && !bootstrapToken.isBlank()) { envVars.put("CAMELEER_AGENT_AUTH_TOKEN", bootstrapToken); } @@ -343,6 +368,8 @@ public class DeploymentExecutor { map.put("serverUrl", config.serverUrl()); map.put("replicas", config.replicas()); map.put("deploymentStrategy", config.deploymentStrategy()); + map.put("runtimeType", config.runtimeType()); + map.put("customArgs", config.customArgs()); return map; } } diff --git a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DockerRuntimeOrchestrator.java b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DockerRuntimeOrchestrator.java index 7076c82b..201ffb5b 100644 --- a/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DockerRuntimeOrchestrator.java +++ b/cameleer3-server-app/src/main/java/com/cameleer3/server/app/runtime/DockerRuntimeOrchestrator.java @@ -84,10 +84,12 @@ public class DockerRuntimeOrchestrator implements RuntimeOrchestrator { hostConfig.withCpuQuota(request.cpuQuota()); } - // When using volume mount, the JAR is at the original path, not /app/app.jar + // Resolve the JAR path for the entrypoint + String appJarPath; if (request.jarVolumeName() != null && !request.jarVolumeName().isBlank()) { - envList = new ArrayList<>(envList); - envList.add("CAMELEER_APP_JAR=" + request.jarPath()); + appJarPath = request.jarPath(); + } else { + appJarPath = "/app/app.jar"; } var createCmd = dockerClient.createContainerCmd(request.baseImage()) @@ -103,21 +105,19 @@ public class DockerRuntimeOrchestrator implements RuntimeOrchestrator { .withRetries(3) .withStartPeriod(30_000_000_000L)); - // Override entrypoint to use the volume-mounted JAR path - if (request.jarVolumeName() != null && !request.jarVolumeName().isBlank()) { - createCmd.withEntrypoint("sh", "-c", - "exec java -javaagent:/app/agent.jar " + - "-Dcameleer.export.type=${CAMELEER_EXPORT_TYPE:-HTTP} " + - "-Dcameleer.export.endpoint=${CAMELEER_SERVER_URL} " + - "-Dcameleer.agent.name=${HOSTNAME} " + - "-Dcameleer.agent.application=${CAMELEER_APPLICATION_ID:-default} " + - "-Dcameleer.agent.environment=${CAMELEER_ENVIRONMENT_ID:-default} " + - "-Dcameleer.routeControl.enabled=${CAMELEER_ROUTE_CONTROL_ENABLED:-false} " + - "-Dcameleer.replay.enabled=${CAMELEER_REPLAY_ENABLED:-false} " + - "-Dcameleer.health.enabled=true " + - "-Dcameleer.health.port=9464 " + - "-jar ${CAMELEER_APP_JAR}"); - } + // Build entrypoint based on runtime type + String customArgs = request.customArgs() != null && !request.customArgs().isBlank() + ? " " + request.customArgs() : ""; + String entrypoint = switch (request.runtimeType()) { + case "quarkus" -> "exec java -javaagent:/app/agent.jar" + customArgs + " -jar " + appJarPath; + case "plain-java" -> "exec java -javaagent:/app/agent.jar -cp " + appJarPath + + ":/app/cameleer3-log-appender.jar" + customArgs + " " + request.mainClass(); + case "native" -> "exec " + appJarPath + customArgs; + default -> // spring-boot (default) + "exec java -javaagent:/app/agent.jar -Dloader.path=/app/cameleer3-log-appender.jar" + + customArgs + " -cp " + appJarPath + " org.springframework.boot.loader.launch.PropertiesLauncher"; + }; + createCmd.withEntrypoint("sh", "-c", entrypoint); if (request.exposedPorts() != null && !request.exposedPorts().isEmpty()) { var ports = request.exposedPorts().stream() diff --git a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/AppService.java b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/AppService.java index 49d26e0a..5c220e2b 100644 --- a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/AppService.java +++ b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/AppService.java @@ -32,6 +32,11 @@ public class AppService { public App getBySlug(String slug) { return appRepo.findBySlug(slug).orElseThrow(() -> new IllegalArgumentException("App not found: " + slug)); } public List listVersions(UUID appId) { return versionRepo.findByAppId(appId); } + public AppVersion getVersion(UUID versionId) { + return versionRepo.findById(versionId) + .orElseThrow(() -> new IllegalArgumentException("AppVersion not found: " + versionId)); + } + public void updateContainerConfig(UUID id, Map containerConfig) { getById(id); // verify exists appRepo.updateContainerConfig(id, containerConfig); diff --git a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/ContainerRequest.java b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/ContainerRequest.java index 71c207bf..8e8b5e7d 100644 --- a/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/ContainerRequest.java +++ b/cameleer3-server-core/src/main/java/com/cameleer3/server/core/runtime/ContainerRequest.java @@ -20,5 +20,8 @@ public record ContainerRequest( List exposedPorts, int healthCheckPort, String restartPolicyName, - int restartPolicyMaxRetries + int restartPolicyMaxRetries, + String runtimeType, + String customArgs, + String mainClass ) {}