diff --git a/CLAUDE.md b/CLAUDE.md index 0c5f0fde..c9fadf78 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -110,7 +110,7 @@ java -jar cameleer-server-app/target/cameleer-server-app-1.0-SNAPSHOT.jar **runtime/** — Docker orchestration - `DockerRuntimeOrchestrator` — implements RuntimeOrchestrator; Docker Java client (zerodep transport), container lifecycle -- `DeploymentExecutor` — @Async staged deploy: PRE_FLIGHT -> PULL_IMAGE -> CREATE_NETWORK -> START_REPLICAS -> HEALTH_CHECK -> SWAP_TRAFFIC -> COMPLETE. Container names are `{tenantId}-{envSlug}-{appSlug}-{replicaIndex}` (globally unique on Docker daemon). Primary network for app containers is set via `CAMELEER_SERVER_RUNTIME_DOCKERNETWORK` env var (in SaaS mode: `cameleer-tenant-{slug}`); apps also connect to `cameleer-traefik` (routing) and `cameleer-env-{tenantId}-{envSlug}` (per-environment discovery) as additional networks. Resolves `runtimeType: auto` to concrete type from `AppVersion.detectedRuntimeType` at PRE_FLIGHT (fails deployment if unresolvable). Builds framework-specific Docker entrypoint per runtime type (Spring Boot PropertiesLauncher, Quarkus `-jar`, plain Java classpath, native binary). Sets per-replica `CAMELEER_AGENT_INSTANCEID` env var to `{envSlug}-{appSlug}-{replicaIndex}` so container logs and agent logs share the same instance identity. Sets `CAMELEER_AGENT_*` env vars from `ResolvedContainerConfig` (routeControlEnabled, replayEnabled, health port). These are startup-only agent properties — changing them requires redeployment. +- `DeploymentExecutor` — @Async staged deploy: PRE_FLIGHT -> PULL_IMAGE -> CREATE_NETWORK -> START_REPLICAS -> HEALTH_CHECK -> SWAP_TRAFFIC -> COMPLETE. Container names are `{tenantId}-{envSlug}-{appSlug}-{replicaIndex}` (globally unique on Docker daemon). Primary network for app containers is set via `CAMELEER_SERVER_RUNTIME_DOCKERNETWORK` env var (in SaaS mode: `cameleer-tenant-{slug}`); apps also connect to `cameleer-traefik` (routing) and `cameleer-env-{tenantId}-{envSlug}` (per-environment discovery) as additional networks. Resolves `runtimeType: auto` to concrete type from `AppVersion.detectedRuntimeType` at PRE_FLIGHT (fails deployment if unresolvable). Builds Docker entrypoint per runtime type (all JVM types use `-javaagent:/app/agent.jar -jar`, plain Java uses `-cp` with main class, native runs binary directly). Sets per-replica `CAMELEER_AGENT_INSTANCEID` env var to `{envSlug}-{appSlug}-{replicaIndex}` so container logs and agent logs share the same instance identity. Sets `CAMELEER_AGENT_*` env vars from `ResolvedContainerConfig` (routeControlEnabled, replayEnabled, health port). These are startup-only agent properties — changing them requires redeployment. - `DockerNetworkManager` — ensures bridge networks (cameleer-traefik, cameleer-env-{slug}), connects containers - `DockerEventMonitor` — persistent Docker event stream listener (die, oom, start, stop), updates deployment status - `TraefikLabelBuilder` — generates Traefik Docker labels for path-based or subdomain routing. Also emits `cameleer.replica` and `cameleer.instance-id` labels per container for labels-first identity. @@ -293,11 +293,11 @@ Deployments move through these statuses: ### Runtime Type Detection -The server detects the app framework from uploaded JARs and builds framework-specific Docker entrypoints: +The server detects the app framework from uploaded JARs and builds Docker entrypoints. The agent shaded JAR bundles the log appender, so no separate `cameleer-log-appender.jar` or `PropertiesLauncher` is needed: - **Detection** (`RuntimeDetector`): runs at JAR upload time. Checks ZIP magic bytes (non-ZIP = native binary), then probes `META-INF/MANIFEST.MF` Main-Class: Spring Boot loader prefix → `spring-boot`, Quarkus entry point → `quarkus`, other Main-Class → `plain-java` (extracts class name). Results stored on `AppVersion` (`detected_runtime_type`, `detected_main_class`). - **Runtime types** (`RuntimeType` enum): `AUTO`, `SPRING_BOOT`, `QUARKUS`, `PLAIN_JAVA`, `NATIVE`. Configurable per app/environment via `containerConfig.runtimeType` (default `"auto"`). -- **Entrypoint per type**: Spring Boot uses `PropertiesLauncher` with `-Dloader.path` for log appender; Quarkus uses `-jar` (appender compiled in); plain Java uses classpath with appender JAR; native runs binary directly (agent compiled in). All JVM types get `-javaagent:/app/agent.jar`. +- **Entrypoint per type**: The agent shaded JAR bundles the log appender, so all JVM types use the same simple pattern: `java -javaagent:/app/agent.jar -jar app.jar`. Plain Java uses `-cp` with explicit main class instead of `-jar`. Native runs the binary directly. All JVM types get `-javaagent:/app/agent.jar`. - **Custom arguments** (`containerConfig.customArgs`): freeform string appended to the start command. Validated against a strict pattern to prevent shell injection (entrypoint uses `sh -c`). - **AUTO resolution**: at deploy time (PRE_FLIGHT), `"auto"` resolves to the detected type from `AppVersion`. Fails deployment if detection was unsuccessful — user must set type explicitly. - **UI**: Resources tab shows Runtime Type dropdown (with detection hint from latest uploaded version) and Custom Arguments text field. @@ -399,7 +399,7 @@ Mean processing time = `camel.route.policy.total_time / camel.route.policy.count # GitNexus — Code Intelligence -This project is indexed by GitNexus as **cameleer-server** (6301 symbols, 15892 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **cameleer-server** (6304 symbols, 15892 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. diff --git a/cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/DockerRuntimeOrchestrator.java b/cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/DockerRuntimeOrchestrator.java index 37c90024..01c2f3e9 100644 --- a/cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/DockerRuntimeOrchestrator.java +++ b/cameleer-server-app/src/main/java/com/cameleer/server/app/runtime/DockerRuntimeOrchestrator.java @@ -117,16 +117,11 @@ public class DockerRuntimeOrchestrator implements RuntimeOrchestrator { 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/cameleer-log-appender.jar" + customArgs + " " + request.mainClass(); + case "plain-java" -> "exec java -javaagent:/app/agent.jar" + customArgs + + " -cp " + appJarPath + " " + request.mainClass(); case "native" -> "exec " + appJarPath + customArgs; - default -> { // spring-boot (default) - String launcher = request.mainClass() != null ? request.mainClass() - : "org.springframework.boot.loader.launch.PropertiesLauncher"; - yield "exec java -javaagent:/app/agent.jar -Dloader.path=/app/cameleer-log-appender.jar" + - customArgs + " -cp " + appJarPath + " " + launcher; - } + default -> // spring-boot, quarkus, and others all use -jar + "exec java -javaagent:/app/agent.jar" + customArgs + " -jar " + appJarPath; }; createCmd.withEntrypoint("sh", "-c", entrypoint);