@better-openclaw/core 1.0.23 → 1.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addon-stack.cjs +673 -0
- package/dist/addon-stack.cjs.map +1 -0
- package/dist/addon-stack.d.cts +23 -0
- package/dist/addon-stack.d.cts.map +1 -0
- package/dist/addon-stack.d.mts +23 -0
- package/dist/addon-stack.d.mts.map +1 -0
- package/dist/addon-stack.mjs +671 -0
- package/dist/addon-stack.mjs.map +1 -0
- package/dist/addon-stack.test.cjs +349 -0
- package/dist/addon-stack.test.cjs.map +1 -0
- package/dist/addon-stack.test.d.cts +1 -0
- package/dist/addon-stack.test.d.mts +1 -0
- package/dist/addon-stack.test.mjs +349 -0
- package/dist/addon-stack.test.mjs.map +1 -0
- package/dist/bare-metal-partition.test.cjs +20 -21
- package/dist/bare-metal-partition.test.cjs.map +1 -1
- package/dist/bare-metal-partition.test.mjs +4 -5
- package/dist/bare-metal-partition.test.mjs.map +1 -1
- package/dist/composer.cjs +17 -1
- package/dist/composer.cjs.map +1 -1
- package/dist/composer.d.cts +24 -1
- package/dist/composer.d.cts.map +1 -1
- package/dist/composer.d.mts +24 -1
- package/dist/composer.d.mts.map +1 -1
- package/dist/composer.mjs +14 -2
- package/dist/composer.mjs.map +1 -1
- package/dist/composer.snapshot.test.cjs +20 -20
- package/dist/composer.snapshot.test.cjs.map +1 -1
- package/dist/composer.snapshot.test.mjs +2 -2
- package/dist/composer.test.cjs +53 -52
- package/dist/composer.test.cjs.map +1 -1
- package/dist/composer.test.mjs +4 -3
- package/dist/composer.test.mjs.map +1 -1
- package/dist/deployers/strip-host-ports.test.cjs +26 -26
- package/dist/deployers/strip-host-ports.test.cjs.map +1 -1
- package/dist/deployers/strip-host-ports.test.mjs +1 -1
- package/dist/generate.cjs +8 -4
- package/dist/generate.cjs.map +1 -1
- package/dist/generate.d.cts.map +1 -1
- package/dist/generate.d.mts.map +1 -1
- package/dist/generate.mjs +9 -5
- package/dist/generate.mjs.map +1 -1
- package/dist/generate.test.cjs +55 -55
- package/dist/generate.test.cjs.map +1 -1
- package/dist/generate.test.mjs +2 -2
- package/dist/generate.test.mjs.map +1 -1
- package/dist/generators/bare-metal-install.test.cjs +18 -18
- package/dist/generators/bare-metal-install.test.cjs.map +1 -1
- package/dist/generators/bare-metal-install.test.mjs +1 -1
- package/dist/generators/caddy.test.cjs +13 -13
- package/dist/generators/caddy.test.cjs.map +1 -1
- package/dist/generators/caddy.test.mjs +1 -1
- package/dist/generators/clone-repos.cjs +140 -0
- package/dist/generators/clone-repos.cjs.map +1 -0
- package/dist/generators/clone-repos.d.cts +11 -0
- package/dist/generators/clone-repos.d.cts.map +1 -0
- package/dist/generators/clone-repos.d.mts +11 -0
- package/dist/generators/clone-repos.d.mts.map +1 -0
- package/dist/generators/clone-repos.mjs +139 -0
- package/dist/generators/clone-repos.mjs.map +1 -0
- package/dist/generators/clone-repos.test.cjs +140 -0
- package/dist/generators/clone-repos.test.cjs.map +1 -0
- package/dist/generators/clone-repos.test.d.cts +1 -0
- package/dist/generators/clone-repos.test.d.mts +1 -0
- package/dist/generators/clone-repos.test.mjs +141 -0
- package/dist/generators/clone-repos.test.mjs.map +1 -0
- package/dist/generators/env.test.cjs +17 -17
- package/dist/generators/env.test.cjs.map +1 -1
- package/dist/generators/env.test.mjs +1 -1
- package/dist/generators/health-check.test.cjs +39 -39
- package/dist/generators/health-check.test.cjs.map +1 -1
- package/dist/generators/health-check.test.mjs +1 -1
- package/dist/generators/postgres-init.cjs +20 -0
- package/dist/generators/postgres-init.cjs.map +1 -1
- package/dist/generators/postgres-init.d.cts.map +1 -1
- package/dist/generators/postgres-init.d.mts.map +1 -1
- package/dist/generators/postgres-init.mjs +20 -0
- package/dist/generators/postgres-init.mjs.map +1 -1
- package/dist/generators/scripts.cjs +332 -3
- package/dist/generators/scripts.cjs.map +1 -1
- package/dist/generators/scripts.d.cts +3 -1
- package/dist/generators/scripts.d.cts.map +1 -1
- package/dist/generators/scripts.d.mts +3 -1
- package/dist/generators/scripts.d.mts.map +1 -1
- package/dist/generators/scripts.mjs +332 -3
- package/dist/generators/scripts.mjs.map +1 -1
- package/dist/generators/scripts.test.cjs +57 -23
- package/dist/generators/scripts.test.cjs.map +1 -1
- package/dist/generators/scripts.test.mjs +39 -5
- package/dist/generators/scripts.test.mjs.map +1 -1
- package/dist/generators/stack-manifest.cjs +1 -0
- package/dist/generators/stack-manifest.cjs.map +1 -1
- package/dist/generators/stack-manifest.d.cts +3 -2
- package/dist/generators/stack-manifest.d.cts.map +1 -1
- package/dist/generators/stack-manifest.d.mts +3 -2
- package/dist/generators/stack-manifest.d.mts.map +1 -1
- package/dist/generators/stack-manifest.mjs +1 -0
- package/dist/generators/stack-manifest.mjs.map +1 -1
- package/dist/generators/traefik.test.cjs +32 -32
- package/dist/generators/traefik.test.cjs.map +1 -1
- package/dist/generators/traefik.test.mjs +1 -1
- package/dist/index.cjs +28 -5
- package/dist/index.d.cts +7 -4
- package/dist/index.d.mts +7 -4
- package/dist/index.mjs +10 -7
- package/dist/migrations.test.cjs +16 -16
- package/dist/migrations.test.cjs.map +1 -1
- package/dist/migrations.test.mjs +1 -1
- package/dist/presets/registry.cjs.map +1 -1
- package/dist/presets/registry.d.cts.map +1 -1
- package/dist/presets/registry.d.mts.map +1 -1
- package/dist/presets/registry.mjs.map +1 -1
- package/dist/presets/registry.test.cjs +14 -14
- package/dist/presets/registry.test.cjs.map +1 -1
- package/dist/presets/registry.test.mjs +1 -1
- package/dist/resolver.cjs +8 -0
- package/dist/resolver.cjs.map +1 -1
- package/dist/resolver.mjs +9 -1
- package/dist/resolver.mjs.map +1 -1
- package/dist/resolver.test.cjs +125 -90
- package/dist/resolver.test.cjs.map +1 -1
- package/dist/resolver.test.mjs +47 -12
- package/dist/resolver.test.mjs.map +1 -1
- package/dist/{schema-B4c64P8N.d.cts → schema-CKBRu-Rt.d.cts} +355 -8
- package/dist/schema-CKBRu-Rt.d.cts.map +1 -0
- package/dist/{schema-CXNhYci1.d.mts → schema-Dn-_Jpb6.d.mts} +355 -8
- package/dist/schema-Dn-_Jpb6.d.mts.map +1 -0
- package/dist/schema.cjs +160 -5
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.d.cts +2 -2
- package/dist/schema.d.mts +2 -2
- package/dist/schema.mjs +150 -6
- package/dist/schema.mjs.map +1 -1
- package/dist/schema.test.cjs +86 -86
- package/dist/schema.test.cjs.map +1 -1
- package/dist/schema.test.mjs +1 -1
- package/dist/services/definitions/apptension-saas.cjs +87 -0
- package/dist/services/definitions/apptension-saas.cjs.map +1 -0
- package/dist/services/definitions/apptension-saas.d.cts +7 -0
- package/dist/services/definitions/apptension-saas.d.cts.map +1 -0
- package/dist/services/definitions/apptension-saas.d.mts +7 -0
- package/dist/services/definitions/apptension-saas.d.mts.map +1 -0
- package/dist/services/definitions/apptension-saas.mjs +86 -0
- package/dist/services/definitions/apptension-saas.mjs.map +1 -0
- package/dist/services/definitions/boxyhq-saas.cjs +88 -0
- package/dist/services/definitions/boxyhq-saas.cjs.map +1 -0
- package/dist/services/definitions/boxyhq-saas.d.cts +7 -0
- package/dist/services/definitions/boxyhq-saas.d.cts.map +1 -0
- package/dist/services/definitions/boxyhq-saas.d.mts +7 -0
- package/dist/services/definitions/boxyhq-saas.d.mts.map +1 -0
- package/dist/services/definitions/boxyhq-saas.mjs +87 -0
- package/dist/services/definitions/boxyhq-saas.mjs.map +1 -0
- package/dist/services/definitions/browserless.cjs +4 -1
- package/dist/services/definitions/browserless.cjs.map +1 -1
- package/dist/services/definitions/browserless.mjs +4 -1
- package/dist/services/definitions/browserless.mjs.map +1 -1
- package/dist/services/definitions/cmsaas-starter.cjs +86 -0
- package/dist/services/definitions/cmsaas-starter.cjs.map +1 -0
- package/dist/services/definitions/cmsaas-starter.d.cts +7 -0
- package/dist/services/definitions/cmsaas-starter.d.cts.map +1 -0
- package/dist/services/definitions/cmsaas-starter.d.mts +7 -0
- package/dist/services/definitions/cmsaas-starter.d.mts.map +1 -0
- package/dist/services/definitions/cmsaas-starter.mjs +85 -0
- package/dist/services/definitions/cmsaas-starter.mjs.map +1 -0
- package/dist/services/definitions/convex.cjs +43 -1
- package/dist/services/definitions/convex.cjs.map +1 -1
- package/dist/services/definitions/convex.mjs +43 -1
- package/dist/services/definitions/convex.mjs.map +1 -1
- package/dist/services/definitions/grafana.cjs +11 -1
- package/dist/services/definitions/grafana.cjs.map +1 -1
- package/dist/services/definitions/grafana.mjs +11 -1
- package/dist/services/definitions/grafana.mjs.map +1 -1
- package/dist/services/definitions/index.cjs +51 -36
- package/dist/services/definitions/index.cjs.map +1 -1
- package/dist/services/definitions/index.d.cts +30 -25
- package/dist/services/definitions/index.d.cts.map +1 -1
- package/dist/services/definitions/index.d.mts +30 -25
- package/dist/services/definitions/index.d.mts.map +1 -1
- package/dist/services/definitions/index.mjs +47 -37
- package/dist/services/definitions/index.mjs.map +1 -1
- package/dist/services/definitions/ixartz-saas.cjs +88 -0
- package/dist/services/definitions/ixartz-saas.cjs.map +1 -0
- package/dist/services/definitions/ixartz-saas.d.cts +7 -0
- package/dist/services/definitions/ixartz-saas.d.cts.map +1 -0
- package/dist/services/definitions/ixartz-saas.d.mts +7 -0
- package/dist/services/definitions/ixartz-saas.d.mts.map +1 -0
- package/dist/services/definitions/ixartz-saas.mjs +87 -0
- package/dist/services/definitions/ixartz-saas.mjs.map +1 -0
- package/dist/services/definitions/meilisearch.cjs +11 -1
- package/dist/services/definitions/meilisearch.cjs.map +1 -1
- package/dist/services/definitions/meilisearch.mjs +11 -1
- package/dist/services/definitions/meilisearch.mjs.map +1 -1
- package/dist/services/definitions/minio.cjs +3 -1
- package/dist/services/definitions/minio.cjs.map +1 -1
- package/dist/services/definitions/minio.mjs +3 -1
- package/dist/services/definitions/minio.mjs.map +1 -1
- package/dist/services/definitions/mission-control.cjs +16 -2
- package/dist/services/definitions/mission-control.cjs.map +1 -1
- package/dist/services/definitions/mission-control.mjs +16 -2
- package/dist/services/definitions/mission-control.mjs.map +1 -1
- package/dist/services/definitions/n8n.cjs +11 -1
- package/dist/services/definitions/n8n.cjs.map +1 -1
- package/dist/services/definitions/n8n.mjs +11 -1
- package/dist/services/definitions/n8n.mjs.map +1 -1
- package/dist/services/definitions/ollama.cjs +3 -1
- package/dist/services/definitions/ollama.cjs.map +1 -1
- package/dist/services/definitions/ollama.mjs +3 -1
- package/dist/services/definitions/ollama.mjs.map +1 -1
- package/dist/services/definitions/open-saas.cjs +81 -0
- package/dist/services/definitions/open-saas.cjs.map +1 -0
- package/dist/services/definitions/open-saas.d.cts +7 -0
- package/dist/services/definitions/open-saas.d.cts.map +1 -0
- package/dist/services/definitions/open-saas.d.mts +7 -0
- package/dist/services/definitions/open-saas.d.mts.map +1 -0
- package/dist/services/definitions/open-saas.mjs +80 -0
- package/dist/services/definitions/open-saas.mjs.map +1 -0
- package/dist/services/definitions/qdrant.cjs +3 -1
- package/dist/services/definitions/qdrant.cjs.map +1 -1
- package/dist/services/definitions/qdrant.mjs +3 -1
- package/dist/services/definitions/qdrant.mjs.map +1 -1
- package/dist/services/definitions/searxng.cjs +8 -1
- package/dist/services/definitions/searxng.cjs.map +1 -1
- package/dist/services/definitions/searxng.mjs +8 -1
- package/dist/services/definitions/searxng.mjs.map +1 -1
- package/dist/services/definitions/uptime-kuma.cjs +8 -1
- package/dist/services/definitions/uptime-kuma.cjs.map +1 -1
- package/dist/services/definitions/uptime-kuma.mjs +8 -1
- package/dist/services/definitions/uptime-kuma.mjs.map +1 -1
- package/dist/services/registry.cjs +3 -0
- package/dist/services/registry.cjs.map +1 -1
- package/dist/services/registry.d.cts.map +1 -1
- package/dist/services/registry.d.mts.map +1 -1
- package/dist/services/registry.mjs +3 -0
- package/dist/services/registry.mjs.map +1 -1
- package/dist/services/registry.test.cjs +40 -33
- package/dist/services/registry.test.cjs.map +1 -1
- package/dist/services/registry.test.mjs +8 -1
- package/dist/services/registry.test.mjs.map +1 -1
- package/dist/{skill-manifest-BVUXU0__.mjs → skill-manifest-6XhrhWsG.mjs} +49 -1
- package/dist/{skill-manifest--IgY9REK.cjs.map → skill-manifest-6XhrhWsG.mjs.map} +1 -1
- package/dist/{skill-manifest--IgY9REK.cjs → skill-manifest-B8znSsym.cjs} +49 -1
- package/dist/{skill-manifest-BVUXU0__.mjs.map → skill-manifest-B8znSsym.cjs.map} +1 -1
- package/dist/skills/registry.cjs +3 -3
- package/dist/skills/registry.cjs.map +1 -1
- package/dist/skills/registry.mjs +3 -3
- package/dist/skills/registry.mjs.map +1 -1
- package/dist/skills/skill-manifest.cjs +1 -1
- package/dist/skills/skill-manifest.mjs +1 -1
- package/dist/{vi.2VT5v0um-DvC3SVNc.mjs → test.CTcmp4Su-ClCHJ3FA.mjs} +6793 -6403
- package/dist/test.CTcmp4Su-ClCHJ3FA.mjs.map +1 -0
- package/dist/{vi.2VT5v0um-CRqXre87.cjs → test.CTcmp4Su-DlzTarwH.cjs} +6793 -6403
- package/dist/test.CTcmp4Su-DlzTarwH.cjs.map +1 -0
- package/dist/track-analytics.cjs +50 -0
- package/dist/track-analytics.cjs.map +1 -0
- package/dist/track-analytics.d.cts +34 -0
- package/dist/track-analytics.d.cts.map +1 -0
- package/dist/track-analytics.d.mts +34 -0
- package/dist/track-analytics.d.mts.map +1 -0
- package/dist/track-analytics.mjs +48 -0
- package/dist/track-analytics.mjs.map +1 -0
- package/dist/track-analytics.test.cjs +91 -0
- package/dist/track-analytics.test.cjs.map +1 -0
- package/dist/track-analytics.test.d.cts +1 -0
- package/dist/track-analytics.test.d.mts +1 -0
- package/dist/track-analytics.test.mjs +92 -0
- package/dist/track-analytics.test.mjs.map +1 -0
- package/dist/types.cjs +7 -0
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +12 -2
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +12 -2
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs +7 -0
- package/dist/types.mjs.map +1 -1
- package/dist/validator.test.cjs +15 -15
- package/dist/validator.test.cjs.map +1 -1
- package/dist/validator.test.mjs +2 -2
- package/dist/version-manager.cjs +1 -1
- package/dist/version-manager.cjs.map +1 -1
- package/dist/version-manager.mjs +1 -1
- package/dist/version-manager.mjs.map +1 -1
- package/dist/version-manager.test.cjs +40 -38
- package/dist/version-manager.test.cjs.map +1 -1
- package/dist/version-manager.test.mjs +7 -5
- package/dist/version-manager.test.mjs.map +1 -1
- package/package.json +4 -4
- package/src/__snapshots__/composer.snapshot.test.ts.snap +160 -0
- package/src/addon-stack.test.ts +490 -0
- package/src/addon-stack.ts +998 -0
- package/src/bare-metal-partition.test.ts +4 -3
- package/src/composer.test.ts +4 -2
- package/src/composer.ts +24 -5
- package/src/generate.test.ts +2 -1
- package/src/generate.ts +10 -1
- package/src/generators/clone-repos.test.ts +154 -0
- package/src/generators/clone-repos.ts +159 -0
- package/src/generators/postgres-init.ts +17 -0
- package/src/generators/scripts.test.ts +52 -4
- package/src/generators/scripts.ts +351 -3
- package/src/generators/stack-manifest.ts +4 -2
- package/src/index.ts +28 -2
- package/src/presets/registry.ts +241 -329
- package/src/resolver.test.ts +53 -15
- package/src/resolver.ts +13 -1
- package/src/schema.ts +216 -4
- package/src/services/definitions/apptension-saas.ts +84 -0
- package/src/services/definitions/boxyhq-saas.ts +84 -0
- package/src/services/definitions/browserless.ts +3 -0
- package/src/services/definitions/cmsaas-starter.ts +84 -0
- package/src/services/definitions/convex.ts +31 -0
- package/src/services/definitions/grafana.ts +9 -0
- package/src/services/definitions/index.ts +90 -70
- package/src/services/definitions/ixartz-saas.ts +84 -0
- package/src/services/definitions/meilisearch.ts +9 -0
- package/src/services/definitions/minio.ts +2 -0
- package/src/services/definitions/mission-control.ts +19 -2
- package/src/services/definitions/n8n.ts +9 -0
- package/src/services/definitions/ollama.ts +2 -0
- package/src/services/definitions/open-saas.ts +79 -0
- package/src/services/definitions/qdrant.ts +2 -0
- package/src/services/definitions/searxng.ts +3 -0
- package/src/services/definitions/uptime-kuma.ts +3 -0
- package/src/services/registry.test.ts +8 -0
- package/src/services/registry.ts +7 -0
- package/src/skills/manifest.json +64 -0
- package/src/skills/registry.ts +3 -3
- package/src/track-analytics.test.ts +82 -0
- package/src/track-analytics.ts +76 -0
- package/src/types.ts +29 -0
- package/src/version-manager.test.ts +10 -5
- package/src/version-manager.ts +1 -1
- package/dist/schema-B4c64P8N.d.cts.map +0 -1
- package/dist/schema-CXNhYci1.d.mts.map +0 -1
- package/dist/vi.2VT5v0um-CRqXre87.cjs.map +0 -1
- package/dist/vi.2VT5v0um-DvC3SVNc.mjs.map +0 -1
package/dist/composer.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composer.cjs","names":["Scalar","getDbRequirements"],"sources":["../src/composer.ts"],"sourcesContent":["import { Scalar, stringify } from \"yaml\";\nimport { getDbRequirements } from \"./generators/postgres-init.js\";\nimport type {\n\tComposeOptions,\n\tOpenclawImageVariant,\n\tResolverOutput,\n\tServiceCategory,\n} from \"./types.js\";\n\n/** Maps image variant to the Docker image string. */\nconst IMAGE_VARIANTS: Record<OpenclawImageVariant, string> = {\n\tofficial: \"ghcr.io/openclaw/openclaw\",\n\tcoolify: \"coollabsio/openclaw\",\n\talpine: \"alpine/openclaw\",\n};\n\n/** Returns the OpenClaw image string with version tag for a given variant. */\nfunction getOpenclawImage(variant: OpenclawImageVariant, version: string): string {\n\tconst base = IMAGE_VARIANTS[variant];\n\t// Coolify and alpine images use :latest by default\n\tconst tag = variant === \"official\" ? version : \"latest\";\n\treturn `${base}:${tag}`;\n}\n\n/** Creates a YAML scalar that is always quoted — avoids YAML 1.1 bare `no` → false. */\nfunction quotedStr(value: string): Scalar {\n\tconst s = new Scalar(value);\n\ts.type = Scalar.QUOTE_DOUBLE;\n\treturn s;\n}\n\n// ── Public Types ────────────────────────────────────────────────────────────\n\nexport interface ComposeResult {\n\tfiles: Record<string, string>; // filename -> YAML content\n\tmainFile: string; // \"docker-compose.yml\"\n\tprofiles: string[]; // list of profiles used\n}\n\n// ── Category → Profile Mapping ──────────────────────────────────────────────\n\nconst CATEGORY_PROFILE_MAP: Partial<Record<ServiceCategory, { file: string; profile: string }>> = {\n\tai: { file: \"docker-compose.ai.yml\", profile: \"ai\" },\n\t\"ai-platform\": { file: \"docker-compose.ai.yml\", profile: \"ai\" },\n\tmedia: { file: \"docker-compose.media.yml\", profile: \"media\" },\n\tmonitoring: { file: \"docker-compose.monitoring.yml\", profile: \"monitoring\" },\n\tanalytics: { file: \"docker-compose.monitoring.yml\", profile: \"monitoring\" },\n\t\"dev-tools\": { file: \"docker-compose.tools.yml\", profile: \"tools\" },\n\t\"coding-agent\": { file: \"docker-compose.tools.yml\", profile: \"tools\" },\n\t\"social-media\": { file: \"docker-compose.social.yml\", profile: \"social\" },\n\tknowledge: { file: \"docker-compose.knowledge.yml\", profile: \"knowledge\" },\n\tcommunication: { file: \"docker-compose.communication.yml\", profile: \"communication\" },\n};\n\nconst YAML_OPTIONS = { lineWidth: 120, nullStr: \"\" };\n\n// ── Shared Gateway Builder ──────────────────────────────────────────────────\n\ninterface GatewayBuildResult {\n\tgatewayService: Record<string, unknown>;\n\tcliService: Record<string, unknown>;\n\tallVolumes: Set<string>;\n}\n\n/**\n * Builds the OpenClaw gateway and CLI service entries.\n * Matches the real OpenClaw docker-compose.yml structure:\n * - Bridge port 18790 for ACP/WebSocket\n * - Bind-mount volumes (not named volumes)\n * - Claude web-provider session env vars\n * - Gateway bind mode (--bind lan)\n * - CLI companion service with stdin/tty\n */\nfunction buildGatewayServices(\n\tresolved: ResolverOutput,\n\toptions: ComposeOptions,\n\tdependsOn?: Record<string, { condition: string }>,\n): GatewayBuildResult {\n\tconst allVolumes = new Set<string>();\n\n\t// Gateway environment\n\tconst gatewayEnv: Record<string, string> = {\n\t\tHOME: \"/home/node\",\n\t\tTERM: \"xterm-256color\",\n\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\t// Claude web-provider session vars (optional, user fills in .env)\n\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t};\n\n\t// Add AI provider API keys to gateway environment\n\tconst providerKeys = [\n\t\t\"OPENAI_API_KEY\",\n\t\t\"ANTHROPIC_API_KEY\",\n\t\t\"GOOGLE_API_KEY\",\n\t\t\"XAI_API_KEY\",\n\t\t\"DEEPSEEK_API_KEY\",\n\t\t\"GROQ_API_KEY\",\n\t\t\"OPENROUTER_API_KEY\",\n\t\t\"MISTRAL_API_KEY\",\n\t\t\"TOGETHER_API_KEY\",\n\t\t\"OLLAMA_API_KEY\",\n\t];\n\tfor (const key of providerKeys) {\n\t\tgatewayEnv[key] = `\\${${key}}`;\n\t}\n\n\t// Gateway volumes (bind-mount style matching real docker-setup.sh)\n\tconst gatewayVolumes: string[] = [\n\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t];\n\n\t// Collect env vars and volume mounts from companion services\n\tfor (const { definition: def } of resolved.services) {\n\t\tfor (const env of def.openclawEnvVars) {\n\t\t\tgatewayEnv[env.key] = env.secret ? `\\${${env.key}}` : env.defaultValue;\n\t\t}\n\t\tif (def.openclawVolumeMounts) {\n\t\t\tfor (const vol of def.openclawVolumeMounts) {\n\t\t\t\tconst isBindMount =\n\t\t\t\t\tvol.name.startsWith(\"./\") || vol.name.startsWith(\"/\") || vol.name.startsWith(\"~\");\n\t\t\t\tif (!isBindMount) {\n\t\t\t\t\tallVolumes.add(vol.name);\n\t\t\t\t}\n\t\t\t\tgatewayVolumes.push(`${vol.name}:${vol.containerPath}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Gateway service\n\tconst defaultImage = getOpenclawImage(\n\t\toptions.openclawImage ?? \"official\",\n\t\toptions.openclawVersion,\n\t);\n\tconst gateway: Record<string, unknown> = {\n\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\tenvironment: gatewayEnv,\n\t\tvolumes: gatewayVolumes,\n\t\tports: [\"${OPENCLAW_GATEWAY_PORT:-18789}:18789\", \"${OPENCLAW_BRIDGE_PORT:-18790}:18790\"],\n\t\tnetworks: [\"openclaw-network\"],\n\t\tinit: true,\n\t\trestart: \"unless-stopped\",\n\t\tcommand: [\n\t\t\t\"node\",\n\t\t\t\"dist/index.js\",\n\t\t\t\"gateway\",\n\t\t\t\"--bind\",\n\t\t\t\"${OPENCLAW_GATEWAY_BIND:-lan}\",\n\t\t\t\"--port\",\n\t\t\t\"18789\",\n\t\t\t\"--allow-unconfigured\",\n\t\t],\n\t\thealthcheck: {\n\t\t\ttest: [\n\t\t\t\t\"CMD\",\n\t\t\t\t\"node\",\n\t\t\t\t\"-e\",\n\t\t\t\t\"fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\",\n\t\t\t],\n\t\t\tinterval: \"30s\",\n\t\t\ttimeout: \"5s\",\n\t\t\tretries: 5,\n\t\t\tstart_period: \"20s\",\n\t\t},\n\t};\n\n\t// Traefik labels for the gateway\n\tconst gwTraefikLabels = options.traefikLabels?.get(\"openclaw-gateway\");\n\tif (gwTraefikLabels) {\n\t\tgateway.labels = gwTraefikLabels;\n\t}\n\n\tif (options.bareMetalNativeHost) {\n\t\tgateway.extra_hosts = [\"host.docker.internal:host-gateway\"];\n\t}\n\n\tif (dependsOn && Object.keys(dependsOn).length > 0) {\n\t\tgateway.depends_on = dependsOn;\n\t}\n\n\t// CLI companion service (matching real OpenClaw docker-compose.yml)\n\t// Build CLI environment with same keys as gateway for consistency\n\tconst cliEnv: Record<string, string> = {\n\t\tHOME: \"/home/node\",\n\t\tTERM: \"xterm-256color\",\n\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\tBROWSER: \"echo\",\n\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t};\n\n\t// Add same AI provider API keys to CLI for direct AI interactions\n\tfor (const key of providerKeys) {\n\t\tcliEnv[key] = `\\${${key}}`;\n\t}\n\n\tconst cliService: Record<string, unknown> = {\n\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\tnetwork_mode: \"service:openclaw-gateway\",\n\t\tcap_drop: [\"NET_RAW\", \"NET_ADMIN\"],\n\t\tsecurity_opt: [\"no-new-privileges:true\"],\n\t\tenvironment: cliEnv,\n\t\tvolumes: [\n\t\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t\t],\n\t\tstdin_open: true,\n\t\ttty: true,\n\t\tinit: true,\n\t\tentrypoint: [\"node\", \"dist/index.js\"],\n\t\tdepends_on: [\"openclaw-gateway\"],\n\t};\n\n\treturn { gatewayService: gateway, cliService: cliService, allVolumes };\n}\n\n// ── Shared Companion Service Builder ────────────────────────────────────────\n\nfunction buildCompanionService(\n\tdef: ResolverOutput[\"services\"][number][\"definition\"],\n\tresolved: ResolverOutput,\n\toptions: ComposeOptions,\n\tallVolumes: Set<string>,\n): { entry: Record<string, unknown>; volumeNames: string[] } {\n\tconst svc: Record<string, unknown> = {};\n\tconst volumeNames: string[] = [];\n\n\tsvc.image = `${def.image}:${def.imageTag}`;\n\n\tif (def.environment.length > 0) {\n\t\tconst env: Record<string, string> = {};\n\t\tfor (const e of def.environment) {\n\t\t\tenv[e.key] = e.secret ? `\\${${e.key}}` : e.defaultValue;\n\t\t}\n\t\tsvc.environment = env;\n\t}\n\n\tconst exposedPorts = def.ports.filter((p) => p.exposed);\n\tif (exposedPorts.length > 0) {\n\t\tconst prefix = def.id.toUpperCase().replace(/-/g, \"_\");\n\t\tsvc.ports = exposedPorts.map((p, i) => {\n\t\t\tconst suffix = exposedPorts.length > 1 ? `_${i}` : \"\";\n\t\t\tlet defaultPort = p.host;\n\n\t\t\t// Override proxy ports if custom ports are specified\n\t\t\tif (\n\t\t\t\t(def.id === \"caddy\" || def.id === \"traefik\") &&\n\t\t\t\toptions.proxyHttpPort !== undefined &&\n\t\t\t\tp.container === 80\n\t\t\t) {\n\t\t\t\tdefaultPort = options.proxyHttpPort;\n\t\t\t}\n\t\t\tif (\n\t\t\t\t(def.id === \"caddy\" || def.id === \"traefik\") &&\n\t\t\t\toptions.proxyHttpsPort !== undefined &&\n\t\t\t\tp.container === 443\n\t\t\t) {\n\t\t\t\tdefaultPort = options.proxyHttpsPort;\n\t\t\t}\n\n\t\t\t// Apply global port overrides if specified\n\t\t\tif (options.portOverrides?.[def.id]?.[p.host] !== undefined) {\n\t\t\t\tdefaultPort = options.portOverrides?.[def.id]?.[p.host] || p.host;\n\t\t\t}\n\n\t\t\t// Use _EXTERNAL_PORT to avoid colliding with openclawEnvVars _PORT keys.\n\t\t\t// openclawEnvVars define the container port (e.g. GRAFANA_PORT=3000 for\n\t\t\t// internal Docker networking), while these are host port mappings (e.g. 3150).\n\t\t\t// Without this, GRAFANA_PORT=3000 from .env would override the default\n\t\t\t// 3150 in ${GRAFANA_PORT:-3150}:3000, mapping host port 3000 instead.\n\t\t\treturn `\\${${prefix}_EXTERNAL_PORT${suffix}:-${defaultPort}}:${p.container}`;\n\t\t});\n\t}\n\n\tif (def.volumes.length > 0) {\n\t\tsvc.volumes = def.volumes.map((v) => {\n\t\t\tconst isBindMount =\n\t\t\t\tv.name.startsWith(\"./\") || v.name.startsWith(\"/\") || v.name.startsWith(\"~\");\n\n\t\t\tif (!isBindMount) {\n\t\t\t\tallVolumes.add(v.name);\n\t\t\t\tvolumeNames.push(v.name);\n\t\t\t}\n\t\t\treturn `${v.name}:${v.containerPath}`;\n\t\t});\n\t}\n\n\t// PostgreSQL: mount the init script and pass per-service DB passwords\n\tif (def.id === \"postgresql\") {\n\t\tif (!svc.volumes) svc.volumes = [];\n\t\t(svc.volumes as string[]).push(\n\t\t\t\"./postgres/init-databases.sh:/docker-entrypoint-initdb.d/init-databases.sh:ro\",\n\t\t);\n\n\t\t// Pass per-service database password env vars so the init script can use them\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tif (dbReqs.length > 0) {\n\t\t\tconst env = (svc.environment ?? {}) as Record<string, string>;\n\t\t\tfor (const req of dbReqs) {\n\t\t\t\tenv[req.passwordEnvVar] = `\\${${req.passwordEnvVar}}`;\n\t\t\t}\n\t\t\tsvc.environment = env;\n\t\t}\n\t}\n\n\tif (def.healthcheck) {\n\t\tconst hc: Record<string, unknown> = {\n\t\t\ttest: [\"CMD-SHELL\", def.healthcheck.test],\n\t\t\tinterval: def.healthcheck.interval,\n\t\t\ttimeout: def.healthcheck.timeout,\n\t\t\tretries: def.healthcheck.retries,\n\t\t};\n\t\tif (def.healthcheck.startPeriod) {\n\t\t\thc.start_period = def.healthcheck.startPeriod;\n\t\t}\n\t\tsvc.healthcheck = hc;\n\t}\n\n\tsvc.restart = def.restartPolicy;\n\tsvc.networks = def.networks;\n\n\tif (def.command) svc.command = def.command;\n\tif (def.entrypoint) svc.entrypoint = def.entrypoint;\n\n\t// Labels: merge static definition labels with dynamic Traefik labels\n\tconst mergedLabels: Record<string, string> = {};\n\tif (def.labels) Object.assign(mergedLabels, def.labels);\n\tconst traefikLabels = options.traefikLabels?.get(def.id);\n\tif (traefikLabels) Object.assign(mergedLabels, traefikLabels);\n\tif (Object.keys(mergedLabels).length > 0) svc.labels = mergedLabels;\n\n\t// Traefik: bind-mount static config and Docker socket\n\tif (def.id === \"traefik\" && options.traefikLabels) {\n\t\tif (!svc.volumes) svc.volumes = [];\n\t\t(svc.volumes as string[]).push(\n\t\t\t\"./traefik/traefik.yml:/etc/traefik/traefik.yml:ro\",\n\t\t\t\"/var/run/docker.sock:/var/run/docker.sock:ro\",\n\t\t);\n\t}\n\n\tlet deploy: Record<string, unknown> | undefined;\n\tif (def.deploy) {\n\t\tdeploy = JSON.parse(JSON.stringify(def.deploy)) as Record<string, unknown>;\n\t}\n\tif (options.gpu && def.gpuRequired) {\n\t\tconst base = deploy ?? {};\n\t\tconst resources = (base.resources ?? {}) as Record<string, unknown>;\n\t\tdeploy = {\n\t\t\t...base,\n\t\t\tresources: {\n\t\t\t\t...resources,\n\t\t\t\treservations: {\n\t\t\t\t\t...((resources.reservations as Record<string, unknown>) ?? {}),\n\t\t\t\t\tdevices: [{ driver: \"nvidia\", count: \"all\", capabilities: [\"gpu\"] }],\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\t// Memory limits from estimatedMemoryMB\n\tif (def.minMemoryMB && options.hardened) {\n\t\tconst base = deploy ?? {};\n\t\tconst resources = (base.resources ?? {}) as Record<string, unknown>;\n\t\tconst limits = (resources.limits as Record<string, unknown>) ?? {};\n\t\tdeploy = {\n\t\t\t...base,\n\t\t\tresources: {\n\t\t\t\t...resources,\n\t\t\t\tlimits: {\n\t\t\t\t\t...limits,\n\t\t\t\t\tmemory: `${def.minMemoryMB * 2}m`, // 2x min as safe limit\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\tif (deploy) svc.deploy = deploy;\n\n\t// Security hardening (when enabled)\n\tif (options.hardened) {\n\t\tsvc.cap_drop = [\"ALL\"];\n\t\tsvc.security_opt = [\"no-new-privileges:true\"];\n\n\t\t// Services that need specific capabilities\n\t\tconst capAddMap: Record<string, string[]> = {\n\t\t\tcaddy: [\"NET_BIND_SERVICE\"],\n\t\t\ttraefik: [\"NET_BIND_SERVICE\"],\n\t\t\tcrowdsec: [\"NET_BIND_SERVICE\", \"DAC_READ_SEARCH\"],\n\t\t};\n\t\tif (capAddMap[def.id]) {\n\t\t\tsvc.cap_add = capAddMap[def.id];\n\t\t}\n\t}\n\n\t// Merge both dependsOn and requires to ensure proper Docker startup ordering\n\tconst depIds = [...new Set([...def.dependsOn, ...def.requires])].filter((id) =>\n\t\tresolved.services.some((s) => s.definition.id === id),\n\t);\n\tif (depIds.length > 0) {\n\t\tconst dependsOn: Record<string, { condition: string }> = {};\n\t\tfor (const depId of depIds) {\n\t\t\tconst dep = resolved.services.find((s) => s.definition.id === depId);\n\t\t\tdependsOn[depId] = {\n\t\t\t\tcondition: dep?.definition.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t\t};\n\t\t}\n\t\tsvc.depends_on = dependsOn;\n\t}\n\n\treturn { entry: svc, volumeNames };\n}\n\n// ── PostgreSQL Setup Init Container ─────────────────────────────────────────\n\n/**\n * Builds a one-shot init container that creates databases and users for\n * services that need their own PostgreSQL database. Runs AFTER PostgreSQL\n * is healthy, on every `docker compose up`, and is idempotent.\n *\n * Uses standard PG* environment variables (PGHOST, PGUSER, PGDATABASE,\n * PGPASSWORD) so psql/createuser/createdb automatically connect without\n * needing explicit -h/-U/-d flags — simpler and avoids YAML escaping issues.\n *\n * Returns null when no setup is needed (no PostgreSQL or no DB requirements).\n */\nfunction buildPostgresSetup(resolved: ResolverOutput): Record<string, unknown> | null {\n\tconst hasPostgres = resolved.services.some((s) => s.definition.id === \"postgresql\");\n\tif (!hasPostgres) return null;\n\n\tconst dbReqs = getDbRequirements(resolved);\n\tif (dbReqs.length === 0) return null;\n\n\t// Build a shell script with one command per line.\n\t// Uses $$ to escape $ from Docker Compose's variable substitution —\n\t// Docker Compose converts $$ → $ before passing to the container.\n\t// NO set -e: we handle errors explicitly so one failed service doesn't block others.\n\tconst scriptLines = [\"echo '=== PostgreSQL database setup ==='\", \"FAILED=0\"];\n\n\tfor (const req of dbReqs) {\n\t\t// Each service's setup is wrapped so a failure doesn't block the others.\n\t\t// Uses psql -v ON_ERROR_STOP=0 so SQL errors don't abort psql.\n\t\tscriptLines.push(\n\t\t\t`echo \"Setting up database '${req.dbName}' with user '${req.dbUser}'...\"`,\n\t\t\t// Create user if not exists (pure SQL, no createuser binary quirks)\n\t\t\t`psql -c \"SELECT 1 FROM pg_roles WHERE rolname='${req.dbUser}'\" | grep -q 1 || psql -c \"CREATE ROLE ${req.dbUser} WITH LOGIN PASSWORD '$$${req.passwordEnvVar}'\"`,\n\t\t\t// Always sync the password to match current env\n\t\t\t`psql -c \"ALTER ROLE ${req.dbUser} WITH LOGIN PASSWORD '$$${req.passwordEnvVar}'\"`,\n\t\t\t// Create database if not exists\n\t\t\t`psql -tc \"SELECT 1 FROM pg_database WHERE datname='${req.dbName}'\" | grep -q 1 || psql -c \"CREATE DATABASE ${req.dbName} OWNER ${req.dbUser}\"`,\n\t\t\t// Grant privileges (idempotent)\n\t\t\t`psql -c \"GRANT ALL PRIVILEGES ON DATABASE ${req.dbName} TO ${req.dbUser}\" || FAILED=1`,\n\t\t\t`echo \" Done: ${req.dbName}\"`,\n\t\t);\n\t}\n\n\tscriptLines.push(\"echo '=== All databases ready ==='\", \"exit $$FAILED\");\n\n\t// Standard PG* env vars: psql/createuser/createdb use these automatically\n\tconst env: Record<string, string> = {\n\t\tPGHOST: \"postgresql\",\n\t\tPGUSER: \"${POSTGRES_USER:-openclaw}\",\n\t\tPGDATABASE: \"${POSTGRES_DB:-openclaw}\",\n\t\tPGPASSWORD: \"${POSTGRES_PASSWORD}\",\n\t};\n\tfor (const req of dbReqs) {\n\t\tenv[req.passwordEnvVar] = `\\${${req.passwordEnvVar}}`;\n\t}\n\n\treturn {\n\t\timage: \"postgres:17-alpine\",\n\t\tdepends_on: {\n\t\t\tpostgresql: { condition: \"service_healthy\" },\n\t\t},\n\t\tenvironment: env,\n\t\t// command MUST be a single-element array so the entire script is passed\n\t\t// as ONE argument to `sh -c`. A plain string gets shlex-split by Docker\n\t\t// Compose into multiple args, breaking multi-line scripts.\n\t\tentrypoint: [\"/bin/sh\", \"-c\"],\n\t\tcommand: [scriptLines.join(\"\\n\")],\n\t\trestart: quotedStr(\"no\"),\n\t\tnetworks: [\"openclaw-network\"],\n\t};\n}\n\n// ── Single-File Compose ─────────────────────────────────────────────────────\n\n/**\n * Generates a single Docker Compose YAML string with ALL services.\n * Backward-compatible signature.\n */\nexport function compose(resolved: ResolverOutput, options: ComposeOptions): string {\n\tconst isDirectInstall = options.openclawInstallMethod === \"direct\";\n\n\t// Build depends_on for ALL companion services\n\tconst gatewayDependsOn: Record<string, { condition: string }> = {};\n\tfor (const { definition: def } of resolved.services) {\n\t\tgatewayDependsOn[def.id] = {\n\t\t\tcondition: def.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t};\n\t}\n\n\tconst services: Record<string, Record<string, unknown>> = {};\n\tlet allVolumes = new Set<string>();\n\n\tif (!isDirectInstall) {\n\t\tconst {\n\t\t\tgatewayService,\n\t\t\tcliService,\n\t\t\tallVolumes: gwVolumes,\n\t\t} = buildGatewayServices(resolved, options, gatewayDependsOn);\n\t\tallVolumes = gwVolumes;\n\t\tservices[\"openclaw-gateway\"] = gatewayService;\n\t\t// CLI service added after companions\n\t\t// Determine which services need DB setup so we can redirect their depends_on\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tconst { entry } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\t\tif (deps.postgresql) {\n\t\t\t\t\tdelete deps.postgresql;\n\t\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\tservices[def.id] = entry;\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tservices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\n\t\tservices[\"openclaw-cli\"] = cliService;\n\t} else {\n\t\t// Direct install: no gateway/CLI containers, just companion services\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tconst { entry } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\t\tif (deps.postgresql) {\n\t\t\t\t\tdelete deps.postgresql;\n\t\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\tservices[def.id] = entry;\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tservices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\t}\n\n\tconst volumes: Record<string, null> = {};\n\tfor (const v of [...allVolumes].sort()) {\n\t\tvolumes[v] = null;\n\t}\n\n\tconst networks = { \"openclaw-network\": { driver: \"bridge\" } };\n\n\treturn stringify({ services, volumes, networks }, YAML_OPTIONS);\n}\n\n// ── Multi-File Compose ──────────────────────────────────────────────────────\n\ninterface ServiceInfo {\n\tid: string;\n\tcategory: ServiceCategory;\n\tentry: Record<string, unknown>;\n\tvolumeNames: string[];\n}\n\n/**\n * Generates multiple Docker Compose files, splitting services into profile-based\n * override files by category.\n */\nexport function composeMultiFile(resolved: ResolverOutput, options: ComposeOptions): ComposeResult {\n\tconst isDirectInstall = options.openclawInstallMethod === \"direct\";\n\tconst allVolumes = new Set<string>();\n\n\t// Build all companion service entries & classify by category\n\tconst serviceInfos: ServiceInfo[] = [];\n\tconst dbReqs = getDbRequirements(resolved);\n\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\tfor (const { definition: def } of resolved.services) {\n\t\tconst { entry, volumeNames } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t// Redirect DB-dependent services to depend on postgres-setup\n\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\tif (deps.postgresql) {\n\t\t\t\tdelete deps.postgresql;\n\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t}\n\t\t}\n\t\tserviceInfos.push({ id: def.id, category: def.category, entry, volumeNames });\n\t}\n\n\t// Partition services into base vs. profile files\n\tconst baseServiceIds = new Set<string>();\n\tconst profileFileMap: Record<string, { profile: string; services: ServiceInfo[] }> = {};\n\n\tfor (const info of serviceInfos) {\n\t\tconst mapping = CATEGORY_PROFILE_MAP[info.category];\n\t\tif (mapping) {\n\t\t\tif (!profileFileMap[mapping.file]) {\n\t\t\t\tprofileFileMap[mapping.file] = { profile: mapping.profile, services: [] };\n\t\t\t}\n\t\t\tprofileFileMap[mapping.file]!.services.push(info);\n\t\t} else {\n\t\t\tbaseServiceIds.add(info.id);\n\t\t}\n\t}\n\n\tconst baseServices: Record<string, Record<string, unknown>> = {};\n\n\tif (!isDirectInstall) {\n\t\t// Gateway depends_on (only base services)\n\t\tconst gatewayDependsOn: Record<string, { condition: string }> = {};\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tif (baseServiceIds.has(def.id)) {\n\t\t\t\tgatewayDependsOn[def.id] = {\n\t\t\t\t\tcondition: def.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst {\n\t\t\tgatewayService,\n\t\t\tcliService,\n\t\t\tallVolumes: gwVolumes,\n\t\t} = buildGatewayServices(resolved, options, gatewayDependsOn);\n\n\t\t// Merge gateway volumes into allVolumes\n\t\tfor (const v of gwVolumes) allVolumes.add(v);\n\n\t\tbaseServices[\"openclaw-gateway\"] = gatewayService;\n\n\t\tfor (const info of serviceInfos) {\n\t\t\tif (baseServiceIds.has(info.id)) {\n\t\t\t\tbaseServices[info.id] = info.entry;\n\t\t\t}\n\t\t}\n\n\t\t// Add postgres-setup init container if needed\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tbaseServices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\n\t\tbaseServices[\"openclaw-cli\"] = cliService;\n\t} else {\n\t\t// Direct install: no gateway/CLI containers\n\t\tfor (const info of serviceInfos) {\n\t\t\tif (baseServiceIds.has(info.id)) {\n\t\t\t\tbaseServices[info.id] = info.entry;\n\t\t\t}\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tbaseServices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\t}\n\n\tconst sortedAllVolumes: Record<string, null> = {};\n\tfor (const v of [...allVolumes].sort()) {\n\t\tsortedAllVolumes[v] = null;\n\t}\n\n\tconst networks = { \"openclaw-network\": { driver: \"bridge\" } };\n\n\tconst files: Record<string, string> = {};\n\tfiles[\"docker-compose.yml\"] = stringify(\n\t\t{ services: baseServices, volumes: sortedAllVolumes, networks },\n\t\tYAML_OPTIONS,\n\t);\n\n\t// Profile override files\n\tconst usedProfiles = new Set<string>();\n\n\tfor (const [fileName, { profile, services }] of Object.entries(profileFileMap)) {\n\t\tusedProfiles.add(profile);\n\n\t\tconst profileServices: Record<string, Record<string, unknown>> = {};\n\t\tconst profileVolumes = new Set<string>();\n\n\t\tfor (const info of services) {\n\t\t\tprofileServices[info.id] = { ...info.entry, profiles: [profile] };\n\t\t\tfor (const vName of info.volumeNames) {\n\t\t\t\tprofileVolumes.add(vName);\n\t\t\t}\n\t\t}\n\n\t\tconst fileContent: Record<string, unknown> = { services: profileServices };\n\n\t\tif (profileVolumes.size > 0) {\n\t\t\tconst sortedProfileVolumes: Record<string, null> = {};\n\t\t\tfor (const v of [...profileVolumes].sort()) {\n\t\t\t\tsortedProfileVolumes[v] = null;\n\t\t\t}\n\t\t\tfileContent.volumes = sortedProfileVolumes;\n\t\t}\n\n\t\tfiles[fileName] = stringify(fileContent, YAML_OPTIONS);\n\t}\n\n\treturn {\n\t\tfiles,\n\t\tmainFile: \"docker-compose.yml\",\n\t\tprofiles: [...usedProfiles].sort(),\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"composer.cjs","names":["Scalar","getDbRequirements"],"sources":["../src/composer.ts"],"sourcesContent":["import { Scalar, stringify } from \"yaml\";\nimport { getDbRequirements } from \"./generators/postgres-init.js\";\nimport type {\n\tComposeOptions,\n\tOpenclawImageVariant,\n\tResolverOutput,\n\tServiceCategory,\n} from \"./types.js\";\n\n/** Maps image variant to the Docker image string. */\nconst IMAGE_VARIANTS: Record<OpenclawImageVariant, string> = {\n\tofficial: \"ghcr.io/openclaw/openclaw\",\n\tcoolify: \"coollabsio/openclaw\",\n\talpine: \"alpine/openclaw\",\n};\n\n/** Returns the OpenClaw image string with version tag for a given variant. */\nfunction getOpenclawImage(variant: OpenclawImageVariant, version: string): string {\n\tconst base = IMAGE_VARIANTS[variant];\n\t// Coolify and alpine images use :latest by default\n\tconst tag = variant === \"official\" ? version : \"latest\";\n\treturn `${base}:${tag}`;\n}\n\n/** Creates a YAML scalar that is always quoted — avoids YAML 1.1 bare `no` → false. */\nexport function quotedStr(value: string): Scalar {\n\tconst s = new Scalar(value);\n\ts.type = Scalar.QUOTE_DOUBLE;\n\treturn s;\n}\n\n// ── Public Types ────────────────────────────────────────────────────────────\n\nexport interface ComposeResult {\n\tfiles: Record<string, string>; // filename -> YAML content\n\tmainFile: string; // \"docker-compose.yml\"\n\tprofiles: string[]; // list of profiles used\n}\n\n// ── Category → Profile Mapping ──────────────────────────────────────────────\n\nconst CATEGORY_PROFILE_MAP: Partial<Record<ServiceCategory, { file: string; profile: string }>> = {\n\tai: { file: \"docker-compose.ai.yml\", profile: \"ai\" },\n\t\"ai-platform\": { file: \"docker-compose.ai.yml\", profile: \"ai\" },\n\tmedia: { file: \"docker-compose.media.yml\", profile: \"media\" },\n\tmonitoring: { file: \"docker-compose.monitoring.yml\", profile: \"monitoring\" },\n\tanalytics: { file: \"docker-compose.monitoring.yml\", profile: \"monitoring\" },\n\t\"dev-tools\": { file: \"docker-compose.tools.yml\", profile: \"tools\" },\n\t\"coding-agent\": { file: \"docker-compose.tools.yml\", profile: \"tools\" },\n\t\"social-media\": { file: \"docker-compose.social.yml\", profile: \"social\" },\n\tknowledge: { file: \"docker-compose.knowledge.yml\", profile: \"knowledge\" },\n\tcommunication: { file: \"docker-compose.communication.yml\", profile: \"communication\" },\n\t\"saas-boilerplate\": { file: \"docker-compose.saas.yml\", profile: \"saas\" },\n};\n\nexport const YAML_OPTIONS = { lineWidth: 120, nullStr: \"\" };\n\n// ── Shared Gateway Builder ──────────────────────────────────────────────────\n\ninterface GatewayBuildResult {\n\tgatewayService: Record<string, unknown>;\n\tcliService: Record<string, unknown>;\n\tallVolumes: Set<string>;\n}\n\n/**\n * Builds the OpenClaw gateway and CLI service entries.\n * Matches the real OpenClaw docker-compose.yml structure:\n * - Bridge port 18790 for ACP/WebSocket\n * - Bind-mount volumes (not named volumes)\n * - Claude web-provider session env vars\n * - Gateway bind mode (--bind lan)\n * - CLI companion service with stdin/tty\n */\nfunction buildGatewayServices(\n\tresolved: ResolverOutput,\n\toptions: ComposeOptions,\n\tdependsOn?: Record<string, { condition: string }>,\n): GatewayBuildResult {\n\tconst allVolumes = new Set<string>();\n\n\t// Gateway environment\n\tconst gatewayEnv: Record<string, string> = {\n\t\tHOME: \"/home/node\",\n\t\tTERM: \"xterm-256color\",\n\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\t// Claude web-provider session vars (optional, user fills in .env)\n\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t};\n\n\t// Add AI provider API keys to gateway environment\n\tconst providerKeys = [\n\t\t\"OPENAI_API_KEY\",\n\t\t\"ANTHROPIC_API_KEY\",\n\t\t\"GOOGLE_API_KEY\",\n\t\t\"XAI_API_KEY\",\n\t\t\"DEEPSEEK_API_KEY\",\n\t\t\"GROQ_API_KEY\",\n\t\t\"OPENROUTER_API_KEY\",\n\t\t\"MISTRAL_API_KEY\",\n\t\t\"TOGETHER_API_KEY\",\n\t\t\"OLLAMA_API_KEY\",\n\t];\n\tfor (const key of providerKeys) {\n\t\tgatewayEnv[key] = `\\${${key}}`;\n\t}\n\n\t// Gateway volumes (bind-mount style matching real docker-setup.sh)\n\tconst gatewayVolumes: string[] = [\n\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t];\n\n\t// Collect env vars and volume mounts from companion services\n\tfor (const { definition: def } of resolved.services) {\n\t\tfor (const env of def.openclawEnvVars) {\n\t\t\tgatewayEnv[env.key] = env.secret ? `\\${${env.key}}` : env.defaultValue;\n\t\t}\n\t\tif (def.openclawVolumeMounts) {\n\t\t\tfor (const vol of def.openclawVolumeMounts) {\n\t\t\t\tconst isBindMount =\n\t\t\t\t\tvol.name.startsWith(\"./\") || vol.name.startsWith(\"/\") || vol.name.startsWith(\"~\");\n\t\t\t\tif (!isBindMount) {\n\t\t\t\t\tallVolumes.add(vol.name);\n\t\t\t\t}\n\t\t\t\tgatewayVolumes.push(`${vol.name}:${vol.containerPath}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Gateway service\n\tconst defaultImage = getOpenclawImage(\n\t\toptions.openclawImage ?? \"official\",\n\t\toptions.openclawVersion,\n\t);\n\tconst gateway: Record<string, unknown> = {\n\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\tenvironment: gatewayEnv,\n\t\tvolumes: gatewayVolumes,\n\t\tports: [\"${OPENCLAW_GATEWAY_PORT:-18789}:18789\", \"${OPENCLAW_BRIDGE_PORT:-18790}:18790\"],\n\t\tnetworks: [\"openclaw-network\"],\n\t\tinit: true,\n\t\trestart: \"unless-stopped\",\n\t\tcommand: [\n\t\t\t\"node\",\n\t\t\t\"dist/index.js\",\n\t\t\t\"gateway\",\n\t\t\t\"--bind\",\n\t\t\t\"${OPENCLAW_GATEWAY_BIND:-lan}\",\n\t\t\t\"--port\",\n\t\t\t\"18789\",\n\t\t\t\"--allow-unconfigured\",\n\t\t],\n\t\thealthcheck: {\n\t\t\ttest: [\n\t\t\t\t\"CMD\",\n\t\t\t\t\"node\",\n\t\t\t\t\"-e\",\n\t\t\t\t\"fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\",\n\t\t\t],\n\t\t\tinterval: \"30s\",\n\t\t\ttimeout: \"5s\",\n\t\t\tretries: 5,\n\t\t\tstart_period: \"20s\",\n\t\t},\n\t};\n\n\t// Traefik labels for the gateway\n\tconst gwTraefikLabels = options.traefikLabels?.get(\"openclaw-gateway\");\n\tif (gwTraefikLabels) {\n\t\tgateway.labels = gwTraefikLabels;\n\t}\n\n\tif (options.bareMetalNativeHost) {\n\t\tgateway.extra_hosts = [\"host.docker.internal:host-gateway\"];\n\t}\n\n\tif (dependsOn && Object.keys(dependsOn).length > 0) {\n\t\tgateway.depends_on = dependsOn;\n\t}\n\n\t// CLI companion service (matching real OpenClaw docker-compose.yml)\n\t// Build CLI environment with same keys as gateway for consistency\n\tconst cliEnv: Record<string, string> = {\n\t\tHOME: \"/home/node\",\n\t\tTERM: \"xterm-256color\",\n\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\tBROWSER: \"echo\",\n\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t};\n\n\t// Add same AI provider API keys to CLI for direct AI interactions\n\tfor (const key of providerKeys) {\n\t\tcliEnv[key] = `\\${${key}}`;\n\t}\n\n\tconst cliService: Record<string, unknown> = {\n\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\tnetwork_mode: \"service:openclaw-gateway\",\n\t\tcap_drop: [\"NET_RAW\", \"NET_ADMIN\"],\n\t\tsecurity_opt: [\"no-new-privileges:true\"],\n\t\tenvironment: cliEnv,\n\t\tvolumes: [\n\t\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t\t],\n\t\tstdin_open: true,\n\t\ttty: true,\n\t\tinit: true,\n\t\tentrypoint: [\"node\", \"dist/index.js\"],\n\t\tdepends_on: [\"openclaw-gateway\"],\n\t};\n\n\treturn { gatewayService: gateway, cliService: cliService, allVolumes };\n}\n\n// ── Shared Companion Service Builder ────────────────────────────────────────\n\nexport function buildCompanionService(\n\tdef: ResolverOutput[\"services\"][number][\"definition\"],\n\tresolved: ResolverOutput,\n\toptions: ComposeOptions,\n\tallVolumes: Set<string>,\n): { entry: Record<string, unknown>; volumeNames: string[] } {\n\tconst svc: Record<string, unknown> = {};\n\tconst volumeNames: string[] = [];\n\n\t// Git-based services use build: context; image-based services use image:\n\tif (def.gitSource && def.buildContext) {\n\t\tconst subdir = def.gitSource.subdirectory || \".\";\n\t\tconst ctxPath = def.buildContext.context || \".\";\n\t\tconst contextFull = subdir === \".\" ? `./repos/${def.id}/${ctxPath}` : `./repos/${def.id}/${subdir}/${ctxPath}`;\n\t\tconst buildBlock: Record<string, unknown> = { context: contextFull };\n\t\tif (def.buildContext.dockerfile) {\n\t\t\tbuildBlock.dockerfile = def.buildContext.dockerfile;\n\t\t}\n\t\tif (def.buildContext.args && Object.keys(def.buildContext.args).length > 0) {\n\t\t\tbuildBlock.args = def.buildContext.args;\n\t\t}\n\t\tif (def.buildContext.target) {\n\t\t\tbuildBlock.target = def.buildContext.target;\n\t\t}\n\t\tsvc.build = buildBlock;\n\t} else {\n\t\tsvc.image = `${def.image}:${def.imageTag}`;\n\t}\n\n\tif (def.environment.length > 0) {\n\t\tconst env: Record<string, string> = {};\n\t\tfor (const e of def.environment) {\n\t\t\tenv[e.key] = e.secret ? `\\${${e.key}}` : e.defaultValue;\n\t\t}\n\t\tsvc.environment = env;\n\t}\n\n\tconst exposedPorts = def.ports.filter((p) => p.exposed);\n\tif (exposedPorts.length > 0) {\n\t\tconst prefix = def.id.toUpperCase().replace(/-/g, \"_\");\n\t\tsvc.ports = exposedPorts.map((p, i) => {\n\t\t\tconst suffix = exposedPorts.length > 1 ? `_${i}` : \"\";\n\t\t\tlet defaultPort = p.host;\n\n\t\t\t// Override proxy ports if custom ports are specified\n\t\t\tif (\n\t\t\t\t(def.id === \"caddy\" || def.id === \"traefik\") &&\n\t\t\t\toptions.proxyHttpPort !== undefined &&\n\t\t\t\tp.container === 80\n\t\t\t) {\n\t\t\t\tdefaultPort = options.proxyHttpPort;\n\t\t\t}\n\t\t\tif (\n\t\t\t\t(def.id === \"caddy\" || def.id === \"traefik\") &&\n\t\t\t\toptions.proxyHttpsPort !== undefined &&\n\t\t\t\tp.container === 443\n\t\t\t) {\n\t\t\t\tdefaultPort = options.proxyHttpsPort;\n\t\t\t}\n\n\t\t\t// Apply global port overrides if specified\n\t\t\tif (options.portOverrides?.[def.id]?.[p.host] !== undefined) {\n\t\t\t\tdefaultPort = options.portOverrides?.[def.id]?.[p.host] || p.host;\n\t\t\t}\n\n\t\t\t// Use _EXTERNAL_PORT to avoid colliding with openclawEnvVars _PORT keys.\n\t\t\t// openclawEnvVars define the container port (e.g. GRAFANA_PORT=3000 for\n\t\t\t// internal Docker networking), while these are host port mappings (e.g. 3150).\n\t\t\t// Without this, GRAFANA_PORT=3000 from .env would override the default\n\t\t\t// 3150 in ${GRAFANA_PORT:-3150}:3000, mapping host port 3000 instead.\n\t\t\treturn `\\${${prefix}_EXTERNAL_PORT${suffix}:-${defaultPort}}:${p.container}`;\n\t\t});\n\t}\n\n\tif (def.volumes.length > 0) {\n\t\tsvc.volumes = def.volumes.map((v) => {\n\t\t\tconst isBindMount =\n\t\t\t\tv.name.startsWith(\"./\") || v.name.startsWith(\"/\") || v.name.startsWith(\"~\");\n\n\t\t\tif (!isBindMount) {\n\t\t\t\tallVolumes.add(v.name);\n\t\t\t\tvolumeNames.push(v.name);\n\t\t\t}\n\t\t\treturn `${v.name}:${v.containerPath}`;\n\t\t});\n\t}\n\n\t// PostgreSQL: mount the init script and pass per-service DB passwords\n\tif (def.id === \"postgresql\") {\n\t\tif (!svc.volumes) svc.volumes = [];\n\t\t(svc.volumes as string[]).push(\n\t\t\t\"./postgres/init-databases.sh:/docker-entrypoint-initdb.d/init-databases.sh:ro\",\n\t\t);\n\n\t\t// Pass per-service database password env vars so the init script can use them\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tif (dbReqs.length > 0) {\n\t\t\tconst env = (svc.environment ?? {}) as Record<string, string>;\n\t\t\tfor (const req of dbReqs) {\n\t\t\t\tenv[req.passwordEnvVar] = `\\${${req.passwordEnvVar}}`;\n\t\t\t}\n\t\t\tsvc.environment = env;\n\t\t}\n\t}\n\n\tif (def.healthcheck) {\n\t\tconst hc: Record<string, unknown> = {\n\t\t\ttest: [\"CMD-SHELL\", def.healthcheck.test],\n\t\t\tinterval: def.healthcheck.interval,\n\t\t\ttimeout: def.healthcheck.timeout,\n\t\t\tretries: def.healthcheck.retries,\n\t\t};\n\t\tif (def.healthcheck.startPeriod) {\n\t\t\thc.start_period = def.healthcheck.startPeriod;\n\t\t}\n\t\tsvc.healthcheck = hc;\n\t}\n\n\tsvc.restart = def.restartPolicy;\n\tsvc.networks = def.networks;\n\n\tif (def.command) svc.command = def.command;\n\tif (def.entrypoint) svc.entrypoint = def.entrypoint;\n\n\t// Labels: merge static definition labels with dynamic Traefik labels\n\tconst mergedLabels: Record<string, string> = {};\n\tif (def.labels) Object.assign(mergedLabels, def.labels);\n\tconst traefikLabels = options.traefikLabels?.get(def.id);\n\tif (traefikLabels) Object.assign(mergedLabels, traefikLabels);\n\tif (Object.keys(mergedLabels).length > 0) svc.labels = mergedLabels;\n\n\t// Traefik: bind-mount static config and Docker socket\n\tif (def.id === \"traefik\" && options.traefikLabels) {\n\t\tif (!svc.volumes) svc.volumes = [];\n\t\t(svc.volumes as string[]).push(\n\t\t\t\"./traefik/traefik.yml:/etc/traefik/traefik.yml:ro\",\n\t\t\t\"/var/run/docker.sock:/var/run/docker.sock:ro\",\n\t\t);\n\t}\n\n\tlet deploy: Record<string, unknown> | undefined;\n\tif (def.deploy) {\n\t\tdeploy = JSON.parse(JSON.stringify(def.deploy)) as Record<string, unknown>;\n\t}\n\tif (options.gpu && def.gpuRequired) {\n\t\tconst base = deploy ?? {};\n\t\tconst resources = (base.resources ?? {}) as Record<string, unknown>;\n\t\tdeploy = {\n\t\t\t...base,\n\t\t\tresources: {\n\t\t\t\t...resources,\n\t\t\t\treservations: {\n\t\t\t\t\t...((resources.reservations as Record<string, unknown>) ?? {}),\n\t\t\t\t\tdevices: [{ driver: \"nvidia\", count: \"all\", capabilities: [\"gpu\"] }],\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\t// Memory limits from estimatedMemoryMB\n\tif (def.minMemoryMB && options.hardened) {\n\t\tconst base = deploy ?? {};\n\t\tconst resources = (base.resources ?? {}) as Record<string, unknown>;\n\t\tconst limits = (resources.limits as Record<string, unknown>) ?? {};\n\t\tdeploy = {\n\t\t\t...base,\n\t\t\tresources: {\n\t\t\t\t...resources,\n\t\t\t\tlimits: {\n\t\t\t\t\t...limits,\n\t\t\t\t\tmemory: `${def.minMemoryMB * 2}m`, // 2x min as safe limit\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\tif (deploy) svc.deploy = deploy;\n\n\t// Security hardening (when enabled)\n\tif (options.hardened) {\n\t\tsvc.cap_drop = [\"ALL\"];\n\t\tsvc.security_opt = [\"no-new-privileges:true\"];\n\n\t\t// Services that need specific capabilities\n\t\tconst capAddMap: Record<string, string[]> = {\n\t\t\tcaddy: [\"NET_BIND_SERVICE\"],\n\t\t\ttraefik: [\"NET_BIND_SERVICE\"],\n\t\t\tcrowdsec: [\"NET_BIND_SERVICE\", \"DAC_READ_SEARCH\"],\n\t\t};\n\t\tif (capAddMap[def.id]) {\n\t\t\tsvc.cap_add = capAddMap[def.id];\n\t\t}\n\t}\n\n\t// Merge both dependsOn and requires to ensure proper Docker startup ordering\n\tconst depIds = [...new Set([...def.dependsOn, ...def.requires])].filter((id) =>\n\t\tresolved.services.some((s) => s.definition.id === id),\n\t);\n\tif (depIds.length > 0) {\n\t\tconst dependsOn: Record<string, { condition: string }> = {};\n\t\tfor (const depId of depIds) {\n\t\t\tconst dep = resolved.services.find((s) => s.definition.id === depId);\n\t\t\tdependsOn[depId] = {\n\t\t\t\tcondition: dep?.definition.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t\t};\n\t\t}\n\t\tsvc.depends_on = dependsOn;\n\t}\n\n\treturn { entry: svc, volumeNames };\n}\n\n// ── PostgreSQL Setup Init Container ─────────────────────────────────────────\n\n/**\n * Builds a one-shot init container that creates databases and users for\n * services that need their own PostgreSQL database. Runs AFTER PostgreSQL\n * is healthy, on every `docker compose up`, and is idempotent.\n *\n * Uses standard PG* environment variables (PGHOST, PGUSER, PGDATABASE,\n * PGPASSWORD) so psql/createuser/createdb automatically connect without\n * needing explicit -h/-U/-d flags — simpler and avoids YAML escaping issues.\n *\n * Returns null when no setup is needed (no PostgreSQL or no DB requirements).\n */\nexport function buildPostgresSetup(resolved: ResolverOutput): Record<string, unknown> | null {\n\tconst hasPostgres = resolved.services.some((s) => s.definition.id === \"postgresql\");\n\tif (!hasPostgres) return null;\n\n\tconst dbReqs = getDbRequirements(resolved);\n\tif (dbReqs.length === 0) return null;\n\n\t// Build a shell script with one command per line.\n\t// Uses $$ to escape $ from Docker Compose's variable substitution —\n\t// Docker Compose converts $$ → $ before passing to the container.\n\t// NO set -e: we handle errors explicitly so one failed service doesn't block others.\n\tconst scriptLines = [\"echo '=== PostgreSQL database setup ==='\", \"FAILED=0\"];\n\n\tfor (const req of dbReqs) {\n\t\t// Each service's setup is wrapped so a failure doesn't block the others.\n\t\t// Uses psql -v ON_ERROR_STOP=0 so SQL errors don't abort psql.\n\t\tscriptLines.push(\n\t\t\t`echo \"Setting up database '${req.dbName}' with user '${req.dbUser}'...\"`,\n\t\t\t// Create user if not exists (pure SQL, no createuser binary quirks)\n\t\t\t`psql -c \"SELECT 1 FROM pg_roles WHERE rolname='${req.dbUser}'\" | grep -q 1 || psql -c \"CREATE ROLE ${req.dbUser} WITH LOGIN PASSWORD '$$${req.passwordEnvVar}'\"`,\n\t\t\t// Always sync the password to match current env\n\t\t\t`psql -c \"ALTER ROLE ${req.dbUser} WITH LOGIN PASSWORD '$$${req.passwordEnvVar}'\"`,\n\t\t\t// Create database if not exists\n\t\t\t`psql -tc \"SELECT 1 FROM pg_database WHERE datname='${req.dbName}'\" | grep -q 1 || psql -c \"CREATE DATABASE ${req.dbName} OWNER ${req.dbUser}\"`,\n\t\t\t// Grant privileges (idempotent)\n\t\t\t`psql -c \"GRANT ALL PRIVILEGES ON DATABASE ${req.dbName} TO ${req.dbUser}\" || FAILED=1`,\n\t\t\t`echo \" Done: ${req.dbName}\"`,\n\t\t);\n\t}\n\n\tscriptLines.push(\"echo '=== All databases ready ==='\", \"exit $$FAILED\");\n\n\t// Standard PG* env vars: psql/createuser/createdb use these automatically\n\tconst env: Record<string, string> = {\n\t\tPGHOST: \"postgresql\",\n\t\tPGUSER: \"${POSTGRES_USER:-openclaw}\",\n\t\tPGDATABASE: \"${POSTGRES_DB:-openclaw}\",\n\t\tPGPASSWORD: \"${POSTGRES_PASSWORD}\",\n\t};\n\tfor (const req of dbReqs) {\n\t\tenv[req.passwordEnvVar] = `\\${${req.passwordEnvVar}}`;\n\t}\n\n\treturn {\n\t\timage: \"postgres:17-alpine\",\n\t\tdepends_on: {\n\t\t\tpostgresql: { condition: \"service_healthy\" },\n\t\t},\n\t\tenvironment: env,\n\t\t// command MUST be a single-element array so the entire script is passed\n\t\t// as ONE argument to `sh -c`. A plain string gets shlex-split by Docker\n\t\t// Compose into multiple args, breaking multi-line scripts.\n\t\tentrypoint: [\"/bin/sh\", \"-c\"],\n\t\tcommand: [scriptLines.join(\"\\n\")],\n\t\trestart: quotedStr(\"no\"),\n\t\tnetworks: [\"openclaw-network\"],\n\t};\n}\n\n// ── Single-File Compose ─────────────────────────────────────────────────────\n\n/**\n * Generates a single Docker Compose YAML string with ALL services.\n * Backward-compatible signature.\n */\nexport function compose(resolved: ResolverOutput, options: ComposeOptions): string {\n\tconst isDirectInstall = options.openclawInstallMethod === \"direct\";\n\n\t// Build depends_on for ALL companion services\n\tconst gatewayDependsOn: Record<string, { condition: string }> = {};\n\tfor (const { definition: def } of resolved.services) {\n\t\tgatewayDependsOn[def.id] = {\n\t\t\tcondition: def.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t};\n\t}\n\n\tconst services: Record<string, Record<string, unknown>> = {};\n\tlet allVolumes = new Set<string>();\n\n\tif (!isDirectInstall) {\n\t\tconst {\n\t\t\tgatewayService,\n\t\t\tcliService,\n\t\t\tallVolumes: gwVolumes,\n\t\t} = buildGatewayServices(resolved, options, gatewayDependsOn);\n\t\tallVolumes = gwVolumes;\n\t\tservices[\"openclaw-gateway\"] = gatewayService;\n\t\t// CLI service added after companions\n\t\t// Determine which services need DB setup so we can redirect their depends_on\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tconst { entry } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\t\tif (deps.postgresql) {\n\t\t\t\t\tdelete deps.postgresql;\n\t\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\tservices[def.id] = entry;\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tservices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\n\t\tservices[\"openclaw-cli\"] = cliService;\n\t} else {\n\t\t// Direct install: no gateway/CLI containers, just companion services\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tconst { entry } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\t\tif (deps.postgresql) {\n\t\t\t\t\tdelete deps.postgresql;\n\t\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\tservices[def.id] = entry;\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tservices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\t}\n\n\tconst volumes: Record<string, null> = {};\n\tfor (const v of [...allVolumes].sort()) {\n\t\tvolumes[v] = null;\n\t}\n\n\tconst networks = { \"openclaw-network\": { driver: \"bridge\" } };\n\n\treturn stringify({ services, volumes, networks }, YAML_OPTIONS);\n}\n\n// ── Multi-File Compose ──────────────────────────────────────────────────────\n\ninterface ServiceInfo {\n\tid: string;\n\tcategory: ServiceCategory;\n\tentry: Record<string, unknown>;\n\tvolumeNames: string[];\n}\n\n/**\n * Generates multiple Docker Compose files, splitting services into profile-based\n * override files by category.\n */\nexport function composeMultiFile(resolved: ResolverOutput, options: ComposeOptions): ComposeResult {\n\tconst isDirectInstall = options.openclawInstallMethod === \"direct\";\n\tconst allVolumes = new Set<string>();\n\n\t// Build all companion service entries & classify by category\n\tconst serviceInfos: ServiceInfo[] = [];\n\tconst dbReqs = getDbRequirements(resolved);\n\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\tfor (const { definition: def } of resolved.services) {\n\t\tconst { entry, volumeNames } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t// Redirect DB-dependent services to depend on postgres-setup\n\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\tif (deps.postgresql) {\n\t\t\t\tdelete deps.postgresql;\n\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t}\n\t\t}\n\t\tserviceInfos.push({ id: def.id, category: def.category, entry, volumeNames });\n\t}\n\n\t// Partition services into base vs. profile files\n\tconst baseServiceIds = new Set<string>();\n\tconst profileFileMap: Record<string, { profile: string; services: ServiceInfo[] }> = {};\n\n\tfor (const info of serviceInfos) {\n\t\tconst mapping = CATEGORY_PROFILE_MAP[info.category];\n\t\tif (mapping) {\n\t\t\tif (!profileFileMap[mapping.file]) {\n\t\t\t\tprofileFileMap[mapping.file] = { profile: mapping.profile, services: [] };\n\t\t\t}\n\t\t\tprofileFileMap[mapping.file]!.services.push(info);\n\t\t} else {\n\t\t\tbaseServiceIds.add(info.id);\n\t\t}\n\t}\n\n\tconst baseServices: Record<string, Record<string, unknown>> = {};\n\n\tif (!isDirectInstall) {\n\t\t// Gateway depends_on (only base services)\n\t\tconst gatewayDependsOn: Record<string, { condition: string }> = {};\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tif (baseServiceIds.has(def.id)) {\n\t\t\t\tgatewayDependsOn[def.id] = {\n\t\t\t\t\tcondition: def.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst {\n\t\t\tgatewayService,\n\t\t\tcliService,\n\t\t\tallVolumes: gwVolumes,\n\t\t} = buildGatewayServices(resolved, options, gatewayDependsOn);\n\n\t\t// Merge gateway volumes into allVolumes\n\t\tfor (const v of gwVolumes) allVolumes.add(v);\n\n\t\tbaseServices[\"openclaw-gateway\"] = gatewayService;\n\n\t\tfor (const info of serviceInfos) {\n\t\t\tif (baseServiceIds.has(info.id)) {\n\t\t\t\tbaseServices[info.id] = info.entry;\n\t\t\t}\n\t\t}\n\n\t\t// Add postgres-setup init container if needed\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tbaseServices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\n\t\tbaseServices[\"openclaw-cli\"] = cliService;\n\t} else {\n\t\t// Direct install: no gateway/CLI containers\n\t\tfor (const info of serviceInfos) {\n\t\t\tif (baseServiceIds.has(info.id)) {\n\t\t\t\tbaseServices[info.id] = info.entry;\n\t\t\t}\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tbaseServices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\t}\n\n\tconst sortedAllVolumes: Record<string, null> = {};\n\tfor (const v of [...allVolumes].sort()) {\n\t\tsortedAllVolumes[v] = null;\n\t}\n\n\tconst networks = { \"openclaw-network\": { driver: \"bridge\" } };\n\n\tconst files: Record<string, string> = {};\n\tfiles[\"docker-compose.yml\"] = stringify(\n\t\t{ services: baseServices, volumes: sortedAllVolumes, networks },\n\t\tYAML_OPTIONS,\n\t);\n\n\t// Profile override files\n\tconst usedProfiles = new Set<string>();\n\n\tfor (const [fileName, { profile, services }] of Object.entries(profileFileMap)) {\n\t\tusedProfiles.add(profile);\n\n\t\tconst profileServices: Record<string, Record<string, unknown>> = {};\n\t\tconst profileVolumes = new Set<string>();\n\n\t\tfor (const info of services) {\n\t\t\tprofileServices[info.id] = { ...info.entry, profiles: [profile] };\n\t\t\tfor (const vName of info.volumeNames) {\n\t\t\t\tprofileVolumes.add(vName);\n\t\t\t}\n\t\t}\n\n\t\tconst fileContent: Record<string, unknown> = { services: profileServices };\n\n\t\tif (profileVolumes.size > 0) {\n\t\t\tconst sortedProfileVolumes: Record<string, null> = {};\n\t\t\tfor (const v of [...profileVolumes].sort()) {\n\t\t\t\tsortedProfileVolumes[v] = null;\n\t\t\t}\n\t\t\tfileContent.volumes = sortedProfileVolumes;\n\t\t}\n\n\t\tfiles[fileName] = stringify(fileContent, YAML_OPTIONS);\n\t}\n\n\treturn {\n\t\tfiles,\n\t\tmainFile: \"docker-compose.yml\",\n\t\tprofiles: [...usedProfiles].sort(),\n\t};\n}\n"],"mappings":"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"}
|
package/dist/composer.d.cts
CHANGED
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
import { ComposeOptions, ResolverOutput } from "./types.cjs";
|
|
2
|
+
import { Scalar } from "yaml";
|
|
2
3
|
|
|
3
4
|
//#region src/composer.d.ts
|
|
5
|
+
/** Creates a YAML scalar that is always quoted — avoids YAML 1.1 bare `no` → false. */
|
|
6
|
+
declare function quotedStr(value: string): Scalar;
|
|
4
7
|
interface ComposeResult {
|
|
5
8
|
files: Record<string, string>;
|
|
6
9
|
mainFile: string;
|
|
7
10
|
profiles: string[];
|
|
8
11
|
}
|
|
12
|
+
declare const YAML_OPTIONS: {
|
|
13
|
+
lineWidth: number;
|
|
14
|
+
nullStr: string;
|
|
15
|
+
};
|
|
16
|
+
declare function buildCompanionService(def: ResolverOutput["services"][number]["definition"], resolved: ResolverOutput, options: ComposeOptions, allVolumes: Set<string>): {
|
|
17
|
+
entry: Record<string, unknown>;
|
|
18
|
+
volumeNames: string[];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Builds a one-shot init container that creates databases and users for
|
|
22
|
+
* services that need their own PostgreSQL database. Runs AFTER PostgreSQL
|
|
23
|
+
* is healthy, on every `docker compose up`, and is idempotent.
|
|
24
|
+
*
|
|
25
|
+
* Uses standard PG* environment variables (PGHOST, PGUSER, PGDATABASE,
|
|
26
|
+
* PGPASSWORD) so psql/createuser/createdb automatically connect without
|
|
27
|
+
* needing explicit -h/-U/-d flags — simpler and avoids YAML escaping issues.
|
|
28
|
+
*
|
|
29
|
+
* Returns null when no setup is needed (no PostgreSQL or no DB requirements).
|
|
30
|
+
*/
|
|
31
|
+
declare function buildPostgresSetup(resolved: ResolverOutput): Record<string, unknown> | null;
|
|
9
32
|
/**
|
|
10
33
|
* Generates a single Docker Compose YAML string with ALL services.
|
|
11
34
|
* Backward-compatible signature.
|
|
@@ -17,5 +40,5 @@ declare function compose(resolved: ResolverOutput, options: ComposeOptions): str
|
|
|
17
40
|
*/
|
|
18
41
|
declare function composeMultiFile(resolved: ResolverOutput, options: ComposeOptions): ComposeResult;
|
|
19
42
|
//#endregion
|
|
20
|
-
export { ComposeResult, compose, composeMultiFile };
|
|
43
|
+
export { ComposeResult, YAML_OPTIONS, buildCompanionService, buildPostgresSetup, compose, composeMultiFile, quotedStr };
|
|
21
44
|
//# sourceMappingURL=composer.d.cts.map
|
package/dist/composer.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composer.d.cts","names":[],"sources":["../src/composer.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"composer.d.cts","names":[],"sources":["../src/composer.ts"],"mappings":";;;;;iBAyBgB,SAAA,CAAU,KAAA,WAAgB,MAAA;AAAA,UAQzB,aAAA;EAChB,KAAA,EAAO,MAAA;EACP,QAAA;EACA,QAAA;AAAA;AAAA,cAmBY,YAAA;EAA8C,SAAA;EAAA,OAAA;AAAA;AAAA,iBAyK3C,qBAAA,CACf,GAAA,EAAK,cAAA,oCACL,QAAA,EAAU,cAAA,EACV,OAAA,EAAS,cAAA,EACT,UAAA,EAAY,GAAA;EACR,KAAA,EAAO,MAAA;EAAyB,WAAA;AAAA;;;AA9KrC;;;;;AAyKA;;;;iBA+NgB,kBAAA,CAAmB,QAAA,EAAU,cAAA,GAAiB,MAAA;;;;;iBAiE9C,OAAA,CAAQ,QAAA,EAAU,cAAA,EAAgB,OAAA,EAAS,cAAA;;;;;iBA2F3C,gBAAA,CAAiB,QAAA,EAAU,cAAA,EAAgB,OAAA,EAAS,cAAA,GAAiB,aAAA"}
|
package/dist/composer.d.mts
CHANGED
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
import { ComposeOptions, ResolverOutput } from "./types.mjs";
|
|
2
|
+
import { Scalar } from "yaml";
|
|
2
3
|
|
|
3
4
|
//#region src/composer.d.ts
|
|
5
|
+
/** Creates a YAML scalar that is always quoted — avoids YAML 1.1 bare `no` → false. */
|
|
6
|
+
declare function quotedStr(value: string): Scalar;
|
|
4
7
|
interface ComposeResult {
|
|
5
8
|
files: Record<string, string>;
|
|
6
9
|
mainFile: string;
|
|
7
10
|
profiles: string[];
|
|
8
11
|
}
|
|
12
|
+
declare const YAML_OPTIONS: {
|
|
13
|
+
lineWidth: number;
|
|
14
|
+
nullStr: string;
|
|
15
|
+
};
|
|
16
|
+
declare function buildCompanionService(def: ResolverOutput["services"][number]["definition"], resolved: ResolverOutput, options: ComposeOptions, allVolumes: Set<string>): {
|
|
17
|
+
entry: Record<string, unknown>;
|
|
18
|
+
volumeNames: string[];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Builds a one-shot init container that creates databases and users for
|
|
22
|
+
* services that need their own PostgreSQL database. Runs AFTER PostgreSQL
|
|
23
|
+
* is healthy, on every `docker compose up`, and is idempotent.
|
|
24
|
+
*
|
|
25
|
+
* Uses standard PG* environment variables (PGHOST, PGUSER, PGDATABASE,
|
|
26
|
+
* PGPASSWORD) so psql/createuser/createdb automatically connect without
|
|
27
|
+
* needing explicit -h/-U/-d flags — simpler and avoids YAML escaping issues.
|
|
28
|
+
*
|
|
29
|
+
* Returns null when no setup is needed (no PostgreSQL or no DB requirements).
|
|
30
|
+
*/
|
|
31
|
+
declare function buildPostgresSetup(resolved: ResolverOutput): Record<string, unknown> | null;
|
|
9
32
|
/**
|
|
10
33
|
* Generates a single Docker Compose YAML string with ALL services.
|
|
11
34
|
* Backward-compatible signature.
|
|
@@ -17,5 +40,5 @@ declare function compose(resolved: ResolverOutput, options: ComposeOptions): str
|
|
|
17
40
|
*/
|
|
18
41
|
declare function composeMultiFile(resolved: ResolverOutput, options: ComposeOptions): ComposeResult;
|
|
19
42
|
//#endregion
|
|
20
|
-
export { ComposeResult, compose, composeMultiFile };
|
|
43
|
+
export { ComposeResult, YAML_OPTIONS, buildCompanionService, buildPostgresSetup, compose, composeMultiFile, quotedStr };
|
|
21
44
|
//# sourceMappingURL=composer.d.mts.map
|
package/dist/composer.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composer.d.mts","names":[],"sources":["../src/composer.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"composer.d.mts","names":[],"sources":["../src/composer.ts"],"mappings":";;;;;iBAyBgB,SAAA,CAAU,KAAA,WAAgB,MAAA;AAAA,UAQzB,aAAA;EAChB,KAAA,EAAO,MAAA;EACP,QAAA;EACA,QAAA;AAAA;AAAA,cAmBY,YAAA;EAA8C,SAAA;EAAA,OAAA;AAAA;AAAA,iBAyK3C,qBAAA,CACf,GAAA,EAAK,cAAA,oCACL,QAAA,EAAU,cAAA,EACV,OAAA,EAAS,cAAA,EACT,UAAA,EAAY,GAAA;EACR,KAAA,EAAO,MAAA;EAAyB,WAAA;AAAA;;;;AA9KrC;;;;;AAyKA;;;iBA+NgB,kBAAA,CAAmB,QAAA,EAAU,cAAA,GAAiB,MAAA;;;;;iBAiE9C,OAAA,CAAQ,QAAA,EAAU,cAAA,EAAgB,OAAA,EAAS,cAAA;;;;;iBA2F3C,gBAAA,CAAiB,QAAA,EAAU,cAAA,EAAgB,OAAA,EAAS,cAAA,GAAiB,aAAA"}
|
package/dist/composer.mjs
CHANGED
|
@@ -57,6 +57,10 @@ const CATEGORY_PROFILE_MAP = {
|
|
|
57
57
|
communication: {
|
|
58
58
|
file: "docker-compose.communication.yml",
|
|
59
59
|
profile: "communication"
|
|
60
|
+
},
|
|
61
|
+
"saas-boilerplate": {
|
|
62
|
+
file: "docker-compose.saas.yml",
|
|
63
|
+
profile: "saas"
|
|
60
64
|
}
|
|
61
65
|
};
|
|
62
66
|
const YAML_OPTIONS = {
|
|
@@ -172,7 +176,15 @@ function buildGatewayServices(resolved, options, dependsOn) {
|
|
|
172
176
|
function buildCompanionService(def, resolved, options, allVolumes) {
|
|
173
177
|
const svc = {};
|
|
174
178
|
const volumeNames = [];
|
|
175
|
-
|
|
179
|
+
if (def.gitSource && def.buildContext) {
|
|
180
|
+
const subdir = def.gitSource.subdirectory || ".";
|
|
181
|
+
const ctxPath = def.buildContext.context || ".";
|
|
182
|
+
const buildBlock = { context: subdir === "." ? `./repos/${def.id}/${ctxPath}` : `./repos/${def.id}/${subdir}/${ctxPath}` };
|
|
183
|
+
if (def.buildContext.dockerfile) buildBlock.dockerfile = def.buildContext.dockerfile;
|
|
184
|
+
if (def.buildContext.args && Object.keys(def.buildContext.args).length > 0) buildBlock.args = def.buildContext.args;
|
|
185
|
+
if (def.buildContext.target) buildBlock.target = def.buildContext.target;
|
|
186
|
+
svc.build = buildBlock;
|
|
187
|
+
} else svc.image = `${def.image}:${def.imageTag}`;
|
|
176
188
|
if (def.environment.length > 0) {
|
|
177
189
|
const env = {};
|
|
178
190
|
for (const e of def.environment) env[e.key] = e.secret ? `\${${e.key}}` : e.defaultValue;
|
|
@@ -467,6 +479,6 @@ function composeMultiFile(resolved, options) {
|
|
|
467
479
|
};
|
|
468
480
|
}
|
|
469
481
|
//#endregion
|
|
470
|
-
export { compose, composeMultiFile };
|
|
482
|
+
export { YAML_OPTIONS, buildCompanionService, buildPostgresSetup, compose, composeMultiFile, quotedStr };
|
|
471
483
|
|
|
472
484
|
//# sourceMappingURL=composer.mjs.map
|
package/dist/composer.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composer.mjs","names":[],"sources":["../src/composer.ts"],"sourcesContent":["import { Scalar, stringify } from \"yaml\";\nimport { getDbRequirements } from \"./generators/postgres-init.js\";\nimport type {\n\tComposeOptions,\n\tOpenclawImageVariant,\n\tResolverOutput,\n\tServiceCategory,\n} from \"./types.js\";\n\n/** Maps image variant to the Docker image string. */\nconst IMAGE_VARIANTS: Record<OpenclawImageVariant, string> = {\n\tofficial: \"ghcr.io/openclaw/openclaw\",\n\tcoolify: \"coollabsio/openclaw\",\n\talpine: \"alpine/openclaw\",\n};\n\n/** Returns the OpenClaw image string with version tag for a given variant. */\nfunction getOpenclawImage(variant: OpenclawImageVariant, version: string): string {\n\tconst base = IMAGE_VARIANTS[variant];\n\t// Coolify and alpine images use :latest by default\n\tconst tag = variant === \"official\" ? version : \"latest\";\n\treturn `${base}:${tag}`;\n}\n\n/** Creates a YAML scalar that is always quoted — avoids YAML 1.1 bare `no` → false. */\nfunction quotedStr(value: string): Scalar {\n\tconst s = new Scalar(value);\n\ts.type = Scalar.QUOTE_DOUBLE;\n\treturn s;\n}\n\n// ── Public Types ────────────────────────────────────────────────────────────\n\nexport interface ComposeResult {\n\tfiles: Record<string, string>; // filename -> YAML content\n\tmainFile: string; // \"docker-compose.yml\"\n\tprofiles: string[]; // list of profiles used\n}\n\n// ── Category → Profile Mapping ──────────────────────────────────────────────\n\nconst CATEGORY_PROFILE_MAP: Partial<Record<ServiceCategory, { file: string; profile: string }>> = {\n\tai: { file: \"docker-compose.ai.yml\", profile: \"ai\" },\n\t\"ai-platform\": { file: \"docker-compose.ai.yml\", profile: \"ai\" },\n\tmedia: { file: \"docker-compose.media.yml\", profile: \"media\" },\n\tmonitoring: { file: \"docker-compose.monitoring.yml\", profile: \"monitoring\" },\n\tanalytics: { file: \"docker-compose.monitoring.yml\", profile: \"monitoring\" },\n\t\"dev-tools\": { file: \"docker-compose.tools.yml\", profile: \"tools\" },\n\t\"coding-agent\": { file: \"docker-compose.tools.yml\", profile: \"tools\" },\n\t\"social-media\": { file: \"docker-compose.social.yml\", profile: \"social\" },\n\tknowledge: { file: \"docker-compose.knowledge.yml\", profile: \"knowledge\" },\n\tcommunication: { file: \"docker-compose.communication.yml\", profile: \"communication\" },\n};\n\nconst YAML_OPTIONS = { lineWidth: 120, nullStr: \"\" };\n\n// ── Shared Gateway Builder ──────────────────────────────────────────────────\n\ninterface GatewayBuildResult {\n\tgatewayService: Record<string, unknown>;\n\tcliService: Record<string, unknown>;\n\tallVolumes: Set<string>;\n}\n\n/**\n * Builds the OpenClaw gateway and CLI service entries.\n * Matches the real OpenClaw docker-compose.yml structure:\n * - Bridge port 18790 for ACP/WebSocket\n * - Bind-mount volumes (not named volumes)\n * - Claude web-provider session env vars\n * - Gateway bind mode (--bind lan)\n * - CLI companion service with stdin/tty\n */\nfunction buildGatewayServices(\n\tresolved: ResolverOutput,\n\toptions: ComposeOptions,\n\tdependsOn?: Record<string, { condition: string }>,\n): GatewayBuildResult {\n\tconst allVolumes = new Set<string>();\n\n\t// Gateway environment\n\tconst gatewayEnv: Record<string, string> = {\n\t\tHOME: \"/home/node\",\n\t\tTERM: \"xterm-256color\",\n\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\t// Claude web-provider session vars (optional, user fills in .env)\n\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t};\n\n\t// Add AI provider API keys to gateway environment\n\tconst providerKeys = [\n\t\t\"OPENAI_API_KEY\",\n\t\t\"ANTHROPIC_API_KEY\",\n\t\t\"GOOGLE_API_KEY\",\n\t\t\"XAI_API_KEY\",\n\t\t\"DEEPSEEK_API_KEY\",\n\t\t\"GROQ_API_KEY\",\n\t\t\"OPENROUTER_API_KEY\",\n\t\t\"MISTRAL_API_KEY\",\n\t\t\"TOGETHER_API_KEY\",\n\t\t\"OLLAMA_API_KEY\",\n\t];\n\tfor (const key of providerKeys) {\n\t\tgatewayEnv[key] = `\\${${key}}`;\n\t}\n\n\t// Gateway volumes (bind-mount style matching real docker-setup.sh)\n\tconst gatewayVolumes: string[] = [\n\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t];\n\n\t// Collect env vars and volume mounts from companion services\n\tfor (const { definition: def } of resolved.services) {\n\t\tfor (const env of def.openclawEnvVars) {\n\t\t\tgatewayEnv[env.key] = env.secret ? `\\${${env.key}}` : env.defaultValue;\n\t\t}\n\t\tif (def.openclawVolumeMounts) {\n\t\t\tfor (const vol of def.openclawVolumeMounts) {\n\t\t\t\tconst isBindMount =\n\t\t\t\t\tvol.name.startsWith(\"./\") || vol.name.startsWith(\"/\") || vol.name.startsWith(\"~\");\n\t\t\t\tif (!isBindMount) {\n\t\t\t\t\tallVolumes.add(vol.name);\n\t\t\t\t}\n\t\t\t\tgatewayVolumes.push(`${vol.name}:${vol.containerPath}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Gateway service\n\tconst defaultImage = getOpenclawImage(\n\t\toptions.openclawImage ?? \"official\",\n\t\toptions.openclawVersion,\n\t);\n\tconst gateway: Record<string, unknown> = {\n\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\tenvironment: gatewayEnv,\n\t\tvolumes: gatewayVolumes,\n\t\tports: [\"${OPENCLAW_GATEWAY_PORT:-18789}:18789\", \"${OPENCLAW_BRIDGE_PORT:-18790}:18790\"],\n\t\tnetworks: [\"openclaw-network\"],\n\t\tinit: true,\n\t\trestart: \"unless-stopped\",\n\t\tcommand: [\n\t\t\t\"node\",\n\t\t\t\"dist/index.js\",\n\t\t\t\"gateway\",\n\t\t\t\"--bind\",\n\t\t\t\"${OPENCLAW_GATEWAY_BIND:-lan}\",\n\t\t\t\"--port\",\n\t\t\t\"18789\",\n\t\t\t\"--allow-unconfigured\",\n\t\t],\n\t\thealthcheck: {\n\t\t\ttest: [\n\t\t\t\t\"CMD\",\n\t\t\t\t\"node\",\n\t\t\t\t\"-e\",\n\t\t\t\t\"fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\",\n\t\t\t],\n\t\t\tinterval: \"30s\",\n\t\t\ttimeout: \"5s\",\n\t\t\tretries: 5,\n\t\t\tstart_period: \"20s\",\n\t\t},\n\t};\n\n\t// Traefik labels for the gateway\n\tconst gwTraefikLabels = options.traefikLabels?.get(\"openclaw-gateway\");\n\tif (gwTraefikLabels) {\n\t\tgateway.labels = gwTraefikLabels;\n\t}\n\n\tif (options.bareMetalNativeHost) {\n\t\tgateway.extra_hosts = [\"host.docker.internal:host-gateway\"];\n\t}\n\n\tif (dependsOn && Object.keys(dependsOn).length > 0) {\n\t\tgateway.depends_on = dependsOn;\n\t}\n\n\t// CLI companion service (matching real OpenClaw docker-compose.yml)\n\t// Build CLI environment with same keys as gateway for consistency\n\tconst cliEnv: Record<string, string> = {\n\t\tHOME: \"/home/node\",\n\t\tTERM: \"xterm-256color\",\n\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\tBROWSER: \"echo\",\n\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t};\n\n\t// Add same AI provider API keys to CLI for direct AI interactions\n\tfor (const key of providerKeys) {\n\t\tcliEnv[key] = `\\${${key}}`;\n\t}\n\n\tconst cliService: Record<string, unknown> = {\n\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\tnetwork_mode: \"service:openclaw-gateway\",\n\t\tcap_drop: [\"NET_RAW\", \"NET_ADMIN\"],\n\t\tsecurity_opt: [\"no-new-privileges:true\"],\n\t\tenvironment: cliEnv,\n\t\tvolumes: [\n\t\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t\t],\n\t\tstdin_open: true,\n\t\ttty: true,\n\t\tinit: true,\n\t\tentrypoint: [\"node\", \"dist/index.js\"],\n\t\tdepends_on: [\"openclaw-gateway\"],\n\t};\n\n\treturn { gatewayService: gateway, cliService: cliService, allVolumes };\n}\n\n// ── Shared Companion Service Builder ────────────────────────────────────────\n\nfunction buildCompanionService(\n\tdef: ResolverOutput[\"services\"][number][\"definition\"],\n\tresolved: ResolverOutput,\n\toptions: ComposeOptions,\n\tallVolumes: Set<string>,\n): { entry: Record<string, unknown>; volumeNames: string[] } {\n\tconst svc: Record<string, unknown> = {};\n\tconst volumeNames: string[] = [];\n\n\tsvc.image = `${def.image}:${def.imageTag}`;\n\n\tif (def.environment.length > 0) {\n\t\tconst env: Record<string, string> = {};\n\t\tfor (const e of def.environment) {\n\t\t\tenv[e.key] = e.secret ? `\\${${e.key}}` : e.defaultValue;\n\t\t}\n\t\tsvc.environment = env;\n\t}\n\n\tconst exposedPorts = def.ports.filter((p) => p.exposed);\n\tif (exposedPorts.length > 0) {\n\t\tconst prefix = def.id.toUpperCase().replace(/-/g, \"_\");\n\t\tsvc.ports = exposedPorts.map((p, i) => {\n\t\t\tconst suffix = exposedPorts.length > 1 ? `_${i}` : \"\";\n\t\t\tlet defaultPort = p.host;\n\n\t\t\t// Override proxy ports if custom ports are specified\n\t\t\tif (\n\t\t\t\t(def.id === \"caddy\" || def.id === \"traefik\") &&\n\t\t\t\toptions.proxyHttpPort !== undefined &&\n\t\t\t\tp.container === 80\n\t\t\t) {\n\t\t\t\tdefaultPort = options.proxyHttpPort;\n\t\t\t}\n\t\t\tif (\n\t\t\t\t(def.id === \"caddy\" || def.id === \"traefik\") &&\n\t\t\t\toptions.proxyHttpsPort !== undefined &&\n\t\t\t\tp.container === 443\n\t\t\t) {\n\t\t\t\tdefaultPort = options.proxyHttpsPort;\n\t\t\t}\n\n\t\t\t// Apply global port overrides if specified\n\t\t\tif (options.portOverrides?.[def.id]?.[p.host] !== undefined) {\n\t\t\t\tdefaultPort = options.portOverrides?.[def.id]?.[p.host] || p.host;\n\t\t\t}\n\n\t\t\t// Use _EXTERNAL_PORT to avoid colliding with openclawEnvVars _PORT keys.\n\t\t\t// openclawEnvVars define the container port (e.g. GRAFANA_PORT=3000 for\n\t\t\t// internal Docker networking), while these are host port mappings (e.g. 3150).\n\t\t\t// Without this, GRAFANA_PORT=3000 from .env would override the default\n\t\t\t// 3150 in ${GRAFANA_PORT:-3150}:3000, mapping host port 3000 instead.\n\t\t\treturn `\\${${prefix}_EXTERNAL_PORT${suffix}:-${defaultPort}}:${p.container}`;\n\t\t});\n\t}\n\n\tif (def.volumes.length > 0) {\n\t\tsvc.volumes = def.volumes.map((v) => {\n\t\t\tconst isBindMount =\n\t\t\t\tv.name.startsWith(\"./\") || v.name.startsWith(\"/\") || v.name.startsWith(\"~\");\n\n\t\t\tif (!isBindMount) {\n\t\t\t\tallVolumes.add(v.name);\n\t\t\t\tvolumeNames.push(v.name);\n\t\t\t}\n\t\t\treturn `${v.name}:${v.containerPath}`;\n\t\t});\n\t}\n\n\t// PostgreSQL: mount the init script and pass per-service DB passwords\n\tif (def.id === \"postgresql\") {\n\t\tif (!svc.volumes) svc.volumes = [];\n\t\t(svc.volumes as string[]).push(\n\t\t\t\"./postgres/init-databases.sh:/docker-entrypoint-initdb.d/init-databases.sh:ro\",\n\t\t);\n\n\t\t// Pass per-service database password env vars so the init script can use them\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tif (dbReqs.length > 0) {\n\t\t\tconst env = (svc.environment ?? {}) as Record<string, string>;\n\t\t\tfor (const req of dbReqs) {\n\t\t\t\tenv[req.passwordEnvVar] = `\\${${req.passwordEnvVar}}`;\n\t\t\t}\n\t\t\tsvc.environment = env;\n\t\t}\n\t}\n\n\tif (def.healthcheck) {\n\t\tconst hc: Record<string, unknown> = {\n\t\t\ttest: [\"CMD-SHELL\", def.healthcheck.test],\n\t\t\tinterval: def.healthcheck.interval,\n\t\t\ttimeout: def.healthcheck.timeout,\n\t\t\tretries: def.healthcheck.retries,\n\t\t};\n\t\tif (def.healthcheck.startPeriod) {\n\t\t\thc.start_period = def.healthcheck.startPeriod;\n\t\t}\n\t\tsvc.healthcheck = hc;\n\t}\n\n\tsvc.restart = def.restartPolicy;\n\tsvc.networks = def.networks;\n\n\tif (def.command) svc.command = def.command;\n\tif (def.entrypoint) svc.entrypoint = def.entrypoint;\n\n\t// Labels: merge static definition labels with dynamic Traefik labels\n\tconst mergedLabels: Record<string, string> = {};\n\tif (def.labels) Object.assign(mergedLabels, def.labels);\n\tconst traefikLabels = options.traefikLabels?.get(def.id);\n\tif (traefikLabels) Object.assign(mergedLabels, traefikLabels);\n\tif (Object.keys(mergedLabels).length > 0) svc.labels = mergedLabels;\n\n\t// Traefik: bind-mount static config and Docker socket\n\tif (def.id === \"traefik\" && options.traefikLabels) {\n\t\tif (!svc.volumes) svc.volumes = [];\n\t\t(svc.volumes as string[]).push(\n\t\t\t\"./traefik/traefik.yml:/etc/traefik/traefik.yml:ro\",\n\t\t\t\"/var/run/docker.sock:/var/run/docker.sock:ro\",\n\t\t);\n\t}\n\n\tlet deploy: Record<string, unknown> | undefined;\n\tif (def.deploy) {\n\t\tdeploy = JSON.parse(JSON.stringify(def.deploy)) as Record<string, unknown>;\n\t}\n\tif (options.gpu && def.gpuRequired) {\n\t\tconst base = deploy ?? {};\n\t\tconst resources = (base.resources ?? {}) as Record<string, unknown>;\n\t\tdeploy = {\n\t\t\t...base,\n\t\t\tresources: {\n\t\t\t\t...resources,\n\t\t\t\treservations: {\n\t\t\t\t\t...((resources.reservations as Record<string, unknown>) ?? {}),\n\t\t\t\t\tdevices: [{ driver: \"nvidia\", count: \"all\", capabilities: [\"gpu\"] }],\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\t// Memory limits from estimatedMemoryMB\n\tif (def.minMemoryMB && options.hardened) {\n\t\tconst base = deploy ?? {};\n\t\tconst resources = (base.resources ?? {}) as Record<string, unknown>;\n\t\tconst limits = (resources.limits as Record<string, unknown>) ?? {};\n\t\tdeploy = {\n\t\t\t...base,\n\t\t\tresources: {\n\t\t\t\t...resources,\n\t\t\t\tlimits: {\n\t\t\t\t\t...limits,\n\t\t\t\t\tmemory: `${def.minMemoryMB * 2}m`, // 2x min as safe limit\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\tif (deploy) svc.deploy = deploy;\n\n\t// Security hardening (when enabled)\n\tif (options.hardened) {\n\t\tsvc.cap_drop = [\"ALL\"];\n\t\tsvc.security_opt = [\"no-new-privileges:true\"];\n\n\t\t// Services that need specific capabilities\n\t\tconst capAddMap: Record<string, string[]> = {\n\t\t\tcaddy: [\"NET_BIND_SERVICE\"],\n\t\t\ttraefik: [\"NET_BIND_SERVICE\"],\n\t\t\tcrowdsec: [\"NET_BIND_SERVICE\", \"DAC_READ_SEARCH\"],\n\t\t};\n\t\tif (capAddMap[def.id]) {\n\t\t\tsvc.cap_add = capAddMap[def.id];\n\t\t}\n\t}\n\n\t// Merge both dependsOn and requires to ensure proper Docker startup ordering\n\tconst depIds = [...new Set([...def.dependsOn, ...def.requires])].filter((id) =>\n\t\tresolved.services.some((s) => s.definition.id === id),\n\t);\n\tif (depIds.length > 0) {\n\t\tconst dependsOn: Record<string, { condition: string }> = {};\n\t\tfor (const depId of depIds) {\n\t\t\tconst dep = resolved.services.find((s) => s.definition.id === depId);\n\t\t\tdependsOn[depId] = {\n\t\t\t\tcondition: dep?.definition.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t\t};\n\t\t}\n\t\tsvc.depends_on = dependsOn;\n\t}\n\n\treturn { entry: svc, volumeNames };\n}\n\n// ── PostgreSQL Setup Init Container ─────────────────────────────────────────\n\n/**\n * Builds a one-shot init container that creates databases and users for\n * services that need their own PostgreSQL database. Runs AFTER PostgreSQL\n * is healthy, on every `docker compose up`, and is idempotent.\n *\n * Uses standard PG* environment variables (PGHOST, PGUSER, PGDATABASE,\n * PGPASSWORD) so psql/createuser/createdb automatically connect without\n * needing explicit -h/-U/-d flags — simpler and avoids YAML escaping issues.\n *\n * Returns null when no setup is needed (no PostgreSQL or no DB requirements).\n */\nfunction buildPostgresSetup(resolved: ResolverOutput): Record<string, unknown> | null {\n\tconst hasPostgres = resolved.services.some((s) => s.definition.id === \"postgresql\");\n\tif (!hasPostgres) return null;\n\n\tconst dbReqs = getDbRequirements(resolved);\n\tif (dbReqs.length === 0) return null;\n\n\t// Build a shell script with one command per line.\n\t// Uses $$ to escape $ from Docker Compose's variable substitution —\n\t// Docker Compose converts $$ → $ before passing to the container.\n\t// NO set -e: we handle errors explicitly so one failed service doesn't block others.\n\tconst scriptLines = [\"echo '=== PostgreSQL database setup ==='\", \"FAILED=0\"];\n\n\tfor (const req of dbReqs) {\n\t\t// Each service's setup is wrapped so a failure doesn't block the others.\n\t\t// Uses psql -v ON_ERROR_STOP=0 so SQL errors don't abort psql.\n\t\tscriptLines.push(\n\t\t\t`echo \"Setting up database '${req.dbName}' with user '${req.dbUser}'...\"`,\n\t\t\t// Create user if not exists (pure SQL, no createuser binary quirks)\n\t\t\t`psql -c \"SELECT 1 FROM pg_roles WHERE rolname='${req.dbUser}'\" | grep -q 1 || psql -c \"CREATE ROLE ${req.dbUser} WITH LOGIN PASSWORD '$$${req.passwordEnvVar}'\"`,\n\t\t\t// Always sync the password to match current env\n\t\t\t`psql -c \"ALTER ROLE ${req.dbUser} WITH LOGIN PASSWORD '$$${req.passwordEnvVar}'\"`,\n\t\t\t// Create database if not exists\n\t\t\t`psql -tc \"SELECT 1 FROM pg_database WHERE datname='${req.dbName}'\" | grep -q 1 || psql -c \"CREATE DATABASE ${req.dbName} OWNER ${req.dbUser}\"`,\n\t\t\t// Grant privileges (idempotent)\n\t\t\t`psql -c \"GRANT ALL PRIVILEGES ON DATABASE ${req.dbName} TO ${req.dbUser}\" || FAILED=1`,\n\t\t\t`echo \" Done: ${req.dbName}\"`,\n\t\t);\n\t}\n\n\tscriptLines.push(\"echo '=== All databases ready ==='\", \"exit $$FAILED\");\n\n\t// Standard PG* env vars: psql/createuser/createdb use these automatically\n\tconst env: Record<string, string> = {\n\t\tPGHOST: \"postgresql\",\n\t\tPGUSER: \"${POSTGRES_USER:-openclaw}\",\n\t\tPGDATABASE: \"${POSTGRES_DB:-openclaw}\",\n\t\tPGPASSWORD: \"${POSTGRES_PASSWORD}\",\n\t};\n\tfor (const req of dbReqs) {\n\t\tenv[req.passwordEnvVar] = `\\${${req.passwordEnvVar}}`;\n\t}\n\n\treturn {\n\t\timage: \"postgres:17-alpine\",\n\t\tdepends_on: {\n\t\t\tpostgresql: { condition: \"service_healthy\" },\n\t\t},\n\t\tenvironment: env,\n\t\t// command MUST be a single-element array so the entire script is passed\n\t\t// as ONE argument to `sh -c`. A plain string gets shlex-split by Docker\n\t\t// Compose into multiple args, breaking multi-line scripts.\n\t\tentrypoint: [\"/bin/sh\", \"-c\"],\n\t\tcommand: [scriptLines.join(\"\\n\")],\n\t\trestart: quotedStr(\"no\"),\n\t\tnetworks: [\"openclaw-network\"],\n\t};\n}\n\n// ── Single-File Compose ─────────────────────────────────────────────────────\n\n/**\n * Generates a single Docker Compose YAML string with ALL services.\n * Backward-compatible signature.\n */\nexport function compose(resolved: ResolverOutput, options: ComposeOptions): string {\n\tconst isDirectInstall = options.openclawInstallMethod === \"direct\";\n\n\t// Build depends_on for ALL companion services\n\tconst gatewayDependsOn: Record<string, { condition: string }> = {};\n\tfor (const { definition: def } of resolved.services) {\n\t\tgatewayDependsOn[def.id] = {\n\t\t\tcondition: def.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t};\n\t}\n\n\tconst services: Record<string, Record<string, unknown>> = {};\n\tlet allVolumes = new Set<string>();\n\n\tif (!isDirectInstall) {\n\t\tconst {\n\t\t\tgatewayService,\n\t\t\tcliService,\n\t\t\tallVolumes: gwVolumes,\n\t\t} = buildGatewayServices(resolved, options, gatewayDependsOn);\n\t\tallVolumes = gwVolumes;\n\t\tservices[\"openclaw-gateway\"] = gatewayService;\n\t\t// CLI service added after companions\n\t\t// Determine which services need DB setup so we can redirect their depends_on\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tconst { entry } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\t\tif (deps.postgresql) {\n\t\t\t\t\tdelete deps.postgresql;\n\t\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\tservices[def.id] = entry;\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tservices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\n\t\tservices[\"openclaw-cli\"] = cliService;\n\t} else {\n\t\t// Direct install: no gateway/CLI containers, just companion services\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tconst { entry } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\t\tif (deps.postgresql) {\n\t\t\t\t\tdelete deps.postgresql;\n\t\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\tservices[def.id] = entry;\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tservices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\t}\n\n\tconst volumes: Record<string, null> = {};\n\tfor (const v of [...allVolumes].sort()) {\n\t\tvolumes[v] = null;\n\t}\n\n\tconst networks = { \"openclaw-network\": { driver: \"bridge\" } };\n\n\treturn stringify({ services, volumes, networks }, YAML_OPTIONS);\n}\n\n// ── Multi-File Compose ──────────────────────────────────────────────────────\n\ninterface ServiceInfo {\n\tid: string;\n\tcategory: ServiceCategory;\n\tentry: Record<string, unknown>;\n\tvolumeNames: string[];\n}\n\n/**\n * Generates multiple Docker Compose files, splitting services into profile-based\n * override files by category.\n */\nexport function composeMultiFile(resolved: ResolverOutput, options: ComposeOptions): ComposeResult {\n\tconst isDirectInstall = options.openclawInstallMethod === \"direct\";\n\tconst allVolumes = new Set<string>();\n\n\t// Build all companion service entries & classify by category\n\tconst serviceInfos: ServiceInfo[] = [];\n\tconst dbReqs = getDbRequirements(resolved);\n\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\tfor (const { definition: def } of resolved.services) {\n\t\tconst { entry, volumeNames } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t// Redirect DB-dependent services to depend on postgres-setup\n\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\tif (deps.postgresql) {\n\t\t\t\tdelete deps.postgresql;\n\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t}\n\t\t}\n\t\tserviceInfos.push({ id: def.id, category: def.category, entry, volumeNames });\n\t}\n\n\t// Partition services into base vs. profile files\n\tconst baseServiceIds = new Set<string>();\n\tconst profileFileMap: Record<string, { profile: string; services: ServiceInfo[] }> = {};\n\n\tfor (const info of serviceInfos) {\n\t\tconst mapping = CATEGORY_PROFILE_MAP[info.category];\n\t\tif (mapping) {\n\t\t\tif (!profileFileMap[mapping.file]) {\n\t\t\t\tprofileFileMap[mapping.file] = { profile: mapping.profile, services: [] };\n\t\t\t}\n\t\t\tprofileFileMap[mapping.file]!.services.push(info);\n\t\t} else {\n\t\t\tbaseServiceIds.add(info.id);\n\t\t}\n\t}\n\n\tconst baseServices: Record<string, Record<string, unknown>> = {};\n\n\tif (!isDirectInstall) {\n\t\t// Gateway depends_on (only base services)\n\t\tconst gatewayDependsOn: Record<string, { condition: string }> = {};\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tif (baseServiceIds.has(def.id)) {\n\t\t\t\tgatewayDependsOn[def.id] = {\n\t\t\t\t\tcondition: def.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst {\n\t\t\tgatewayService,\n\t\t\tcliService,\n\t\t\tallVolumes: gwVolumes,\n\t\t} = buildGatewayServices(resolved, options, gatewayDependsOn);\n\n\t\t// Merge gateway volumes into allVolumes\n\t\tfor (const v of gwVolumes) allVolumes.add(v);\n\n\t\tbaseServices[\"openclaw-gateway\"] = gatewayService;\n\n\t\tfor (const info of serviceInfos) {\n\t\t\tif (baseServiceIds.has(info.id)) {\n\t\t\t\tbaseServices[info.id] = info.entry;\n\t\t\t}\n\t\t}\n\n\t\t// Add postgres-setup init container if needed\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tbaseServices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\n\t\tbaseServices[\"openclaw-cli\"] = cliService;\n\t} else {\n\t\t// Direct install: no gateway/CLI containers\n\t\tfor (const info of serviceInfos) {\n\t\t\tif (baseServiceIds.has(info.id)) {\n\t\t\t\tbaseServices[info.id] = info.entry;\n\t\t\t}\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tbaseServices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\t}\n\n\tconst sortedAllVolumes: Record<string, null> = {};\n\tfor (const v of [...allVolumes].sort()) {\n\t\tsortedAllVolumes[v] = null;\n\t}\n\n\tconst networks = { \"openclaw-network\": { driver: \"bridge\" } };\n\n\tconst files: Record<string, string> = {};\n\tfiles[\"docker-compose.yml\"] = stringify(\n\t\t{ services: baseServices, volumes: sortedAllVolumes, networks },\n\t\tYAML_OPTIONS,\n\t);\n\n\t// Profile override files\n\tconst usedProfiles = new Set<string>();\n\n\tfor (const [fileName, { profile, services }] of Object.entries(profileFileMap)) {\n\t\tusedProfiles.add(profile);\n\n\t\tconst profileServices: Record<string, Record<string, unknown>> = {};\n\t\tconst profileVolumes = new Set<string>();\n\n\t\tfor (const info of services) {\n\t\t\tprofileServices[info.id] = { ...info.entry, profiles: [profile] };\n\t\t\tfor (const vName of info.volumeNames) {\n\t\t\t\tprofileVolumes.add(vName);\n\t\t\t}\n\t\t}\n\n\t\tconst fileContent: Record<string, unknown> = { services: profileServices };\n\n\t\tif (profileVolumes.size > 0) {\n\t\t\tconst sortedProfileVolumes: Record<string, null> = {};\n\t\t\tfor (const v of [...profileVolumes].sort()) {\n\t\t\t\tsortedProfileVolumes[v] = null;\n\t\t\t}\n\t\t\tfileContent.volumes = sortedProfileVolumes;\n\t\t}\n\n\t\tfiles[fileName] = stringify(fileContent, YAML_OPTIONS);\n\t}\n\n\treturn {\n\t\tfiles,\n\t\tmainFile: \"docker-compose.yml\",\n\t\tprofiles: [...usedProfiles].sort(),\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"composer.mjs","names":[],"sources":["../src/composer.ts"],"sourcesContent":["import { Scalar, stringify } from \"yaml\";\nimport { getDbRequirements } from \"./generators/postgres-init.js\";\nimport type {\n\tComposeOptions,\n\tOpenclawImageVariant,\n\tResolverOutput,\n\tServiceCategory,\n} from \"./types.js\";\n\n/** Maps image variant to the Docker image string. */\nconst IMAGE_VARIANTS: Record<OpenclawImageVariant, string> = {\n\tofficial: \"ghcr.io/openclaw/openclaw\",\n\tcoolify: \"coollabsio/openclaw\",\n\talpine: \"alpine/openclaw\",\n};\n\n/** Returns the OpenClaw image string with version tag for a given variant. */\nfunction getOpenclawImage(variant: OpenclawImageVariant, version: string): string {\n\tconst base = IMAGE_VARIANTS[variant];\n\t// Coolify and alpine images use :latest by default\n\tconst tag = variant === \"official\" ? version : \"latest\";\n\treturn `${base}:${tag}`;\n}\n\n/** Creates a YAML scalar that is always quoted — avoids YAML 1.1 bare `no` → false. */\nexport function quotedStr(value: string): Scalar {\n\tconst s = new Scalar(value);\n\ts.type = Scalar.QUOTE_DOUBLE;\n\treturn s;\n}\n\n// ── Public Types ────────────────────────────────────────────────────────────\n\nexport interface ComposeResult {\n\tfiles: Record<string, string>; // filename -> YAML content\n\tmainFile: string; // \"docker-compose.yml\"\n\tprofiles: string[]; // list of profiles used\n}\n\n// ── Category → Profile Mapping ──────────────────────────────────────────────\n\nconst CATEGORY_PROFILE_MAP: Partial<Record<ServiceCategory, { file: string; profile: string }>> = {\n\tai: { file: \"docker-compose.ai.yml\", profile: \"ai\" },\n\t\"ai-platform\": { file: \"docker-compose.ai.yml\", profile: \"ai\" },\n\tmedia: { file: \"docker-compose.media.yml\", profile: \"media\" },\n\tmonitoring: { file: \"docker-compose.monitoring.yml\", profile: \"monitoring\" },\n\tanalytics: { file: \"docker-compose.monitoring.yml\", profile: \"monitoring\" },\n\t\"dev-tools\": { file: \"docker-compose.tools.yml\", profile: \"tools\" },\n\t\"coding-agent\": { file: \"docker-compose.tools.yml\", profile: \"tools\" },\n\t\"social-media\": { file: \"docker-compose.social.yml\", profile: \"social\" },\n\tknowledge: { file: \"docker-compose.knowledge.yml\", profile: \"knowledge\" },\n\tcommunication: { file: \"docker-compose.communication.yml\", profile: \"communication\" },\n\t\"saas-boilerplate\": { file: \"docker-compose.saas.yml\", profile: \"saas\" },\n};\n\nexport const YAML_OPTIONS = { lineWidth: 120, nullStr: \"\" };\n\n// ── Shared Gateway Builder ──────────────────────────────────────────────────\n\ninterface GatewayBuildResult {\n\tgatewayService: Record<string, unknown>;\n\tcliService: Record<string, unknown>;\n\tallVolumes: Set<string>;\n}\n\n/**\n * Builds the OpenClaw gateway and CLI service entries.\n * Matches the real OpenClaw docker-compose.yml structure:\n * - Bridge port 18790 for ACP/WebSocket\n * - Bind-mount volumes (not named volumes)\n * - Claude web-provider session env vars\n * - Gateway bind mode (--bind lan)\n * - CLI companion service with stdin/tty\n */\nfunction buildGatewayServices(\n\tresolved: ResolverOutput,\n\toptions: ComposeOptions,\n\tdependsOn?: Record<string, { condition: string }>,\n): GatewayBuildResult {\n\tconst allVolumes = new Set<string>();\n\n\t// Gateway environment\n\tconst gatewayEnv: Record<string, string> = {\n\t\tHOME: \"/home/node\",\n\t\tTERM: \"xterm-256color\",\n\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\t// Claude web-provider session vars (optional, user fills in .env)\n\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t};\n\n\t// Add AI provider API keys to gateway environment\n\tconst providerKeys = [\n\t\t\"OPENAI_API_KEY\",\n\t\t\"ANTHROPIC_API_KEY\",\n\t\t\"GOOGLE_API_KEY\",\n\t\t\"XAI_API_KEY\",\n\t\t\"DEEPSEEK_API_KEY\",\n\t\t\"GROQ_API_KEY\",\n\t\t\"OPENROUTER_API_KEY\",\n\t\t\"MISTRAL_API_KEY\",\n\t\t\"TOGETHER_API_KEY\",\n\t\t\"OLLAMA_API_KEY\",\n\t];\n\tfor (const key of providerKeys) {\n\t\tgatewayEnv[key] = `\\${${key}}`;\n\t}\n\n\t// Gateway volumes (bind-mount style matching real docker-setup.sh)\n\tconst gatewayVolumes: string[] = [\n\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t];\n\n\t// Collect env vars and volume mounts from companion services\n\tfor (const { definition: def } of resolved.services) {\n\t\tfor (const env of def.openclawEnvVars) {\n\t\t\tgatewayEnv[env.key] = env.secret ? `\\${${env.key}}` : env.defaultValue;\n\t\t}\n\t\tif (def.openclawVolumeMounts) {\n\t\t\tfor (const vol of def.openclawVolumeMounts) {\n\t\t\t\tconst isBindMount =\n\t\t\t\t\tvol.name.startsWith(\"./\") || vol.name.startsWith(\"/\") || vol.name.startsWith(\"~\");\n\t\t\t\tif (!isBindMount) {\n\t\t\t\t\tallVolumes.add(vol.name);\n\t\t\t\t}\n\t\t\t\tgatewayVolumes.push(`${vol.name}:${vol.containerPath}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Gateway service\n\tconst defaultImage = getOpenclawImage(\n\t\toptions.openclawImage ?? \"official\",\n\t\toptions.openclawVersion,\n\t);\n\tconst gateway: Record<string, unknown> = {\n\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\tenvironment: gatewayEnv,\n\t\tvolumes: gatewayVolumes,\n\t\tports: [\"${OPENCLAW_GATEWAY_PORT:-18789}:18789\", \"${OPENCLAW_BRIDGE_PORT:-18790}:18790\"],\n\t\tnetworks: [\"openclaw-network\"],\n\t\tinit: true,\n\t\trestart: \"unless-stopped\",\n\t\tcommand: [\n\t\t\t\"node\",\n\t\t\t\"dist/index.js\",\n\t\t\t\"gateway\",\n\t\t\t\"--bind\",\n\t\t\t\"${OPENCLAW_GATEWAY_BIND:-lan}\",\n\t\t\t\"--port\",\n\t\t\t\"18789\",\n\t\t\t\"--allow-unconfigured\",\n\t\t],\n\t\thealthcheck: {\n\t\t\ttest: [\n\t\t\t\t\"CMD\",\n\t\t\t\t\"node\",\n\t\t\t\t\"-e\",\n\t\t\t\t\"fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\",\n\t\t\t],\n\t\t\tinterval: \"30s\",\n\t\t\ttimeout: \"5s\",\n\t\t\tretries: 5,\n\t\t\tstart_period: \"20s\",\n\t\t},\n\t};\n\n\t// Traefik labels for the gateway\n\tconst gwTraefikLabels = options.traefikLabels?.get(\"openclaw-gateway\");\n\tif (gwTraefikLabels) {\n\t\tgateway.labels = gwTraefikLabels;\n\t}\n\n\tif (options.bareMetalNativeHost) {\n\t\tgateway.extra_hosts = [\"host.docker.internal:host-gateway\"];\n\t}\n\n\tif (dependsOn && Object.keys(dependsOn).length > 0) {\n\t\tgateway.depends_on = dependsOn;\n\t}\n\n\t// CLI companion service (matching real OpenClaw docker-compose.yml)\n\t// Build CLI environment with same keys as gateway for consistency\n\tconst cliEnv: Record<string, string> = {\n\t\tHOME: \"/home/node\",\n\t\tTERM: \"xterm-256color\",\n\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\tBROWSER: \"echo\",\n\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t};\n\n\t// Add same AI provider API keys to CLI for direct AI interactions\n\tfor (const key of providerKeys) {\n\t\tcliEnv[key] = `\\${${key}}`;\n\t}\n\n\tconst cliService: Record<string, unknown> = {\n\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\tnetwork_mode: \"service:openclaw-gateway\",\n\t\tcap_drop: [\"NET_RAW\", \"NET_ADMIN\"],\n\t\tsecurity_opt: [\"no-new-privileges:true\"],\n\t\tenvironment: cliEnv,\n\t\tvolumes: [\n\t\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t\t],\n\t\tstdin_open: true,\n\t\ttty: true,\n\t\tinit: true,\n\t\tentrypoint: [\"node\", \"dist/index.js\"],\n\t\tdepends_on: [\"openclaw-gateway\"],\n\t};\n\n\treturn { gatewayService: gateway, cliService: cliService, allVolumes };\n}\n\n// ── Shared Companion Service Builder ────────────────────────────────────────\n\nexport function buildCompanionService(\n\tdef: ResolverOutput[\"services\"][number][\"definition\"],\n\tresolved: ResolverOutput,\n\toptions: ComposeOptions,\n\tallVolumes: Set<string>,\n): { entry: Record<string, unknown>; volumeNames: string[] } {\n\tconst svc: Record<string, unknown> = {};\n\tconst volumeNames: string[] = [];\n\n\t// Git-based services use build: context; image-based services use image:\n\tif (def.gitSource && def.buildContext) {\n\t\tconst subdir = def.gitSource.subdirectory || \".\";\n\t\tconst ctxPath = def.buildContext.context || \".\";\n\t\tconst contextFull = subdir === \".\" ? `./repos/${def.id}/${ctxPath}` : `./repos/${def.id}/${subdir}/${ctxPath}`;\n\t\tconst buildBlock: Record<string, unknown> = { context: contextFull };\n\t\tif (def.buildContext.dockerfile) {\n\t\t\tbuildBlock.dockerfile = def.buildContext.dockerfile;\n\t\t}\n\t\tif (def.buildContext.args && Object.keys(def.buildContext.args).length > 0) {\n\t\t\tbuildBlock.args = def.buildContext.args;\n\t\t}\n\t\tif (def.buildContext.target) {\n\t\t\tbuildBlock.target = def.buildContext.target;\n\t\t}\n\t\tsvc.build = buildBlock;\n\t} else {\n\t\tsvc.image = `${def.image}:${def.imageTag}`;\n\t}\n\n\tif (def.environment.length > 0) {\n\t\tconst env: Record<string, string> = {};\n\t\tfor (const e of def.environment) {\n\t\t\tenv[e.key] = e.secret ? `\\${${e.key}}` : e.defaultValue;\n\t\t}\n\t\tsvc.environment = env;\n\t}\n\n\tconst exposedPorts = def.ports.filter((p) => p.exposed);\n\tif (exposedPorts.length > 0) {\n\t\tconst prefix = def.id.toUpperCase().replace(/-/g, \"_\");\n\t\tsvc.ports = exposedPorts.map((p, i) => {\n\t\t\tconst suffix = exposedPorts.length > 1 ? `_${i}` : \"\";\n\t\t\tlet defaultPort = p.host;\n\n\t\t\t// Override proxy ports if custom ports are specified\n\t\t\tif (\n\t\t\t\t(def.id === \"caddy\" || def.id === \"traefik\") &&\n\t\t\t\toptions.proxyHttpPort !== undefined &&\n\t\t\t\tp.container === 80\n\t\t\t) {\n\t\t\t\tdefaultPort = options.proxyHttpPort;\n\t\t\t}\n\t\t\tif (\n\t\t\t\t(def.id === \"caddy\" || def.id === \"traefik\") &&\n\t\t\t\toptions.proxyHttpsPort !== undefined &&\n\t\t\t\tp.container === 443\n\t\t\t) {\n\t\t\t\tdefaultPort = options.proxyHttpsPort;\n\t\t\t}\n\n\t\t\t// Apply global port overrides if specified\n\t\t\tif (options.portOverrides?.[def.id]?.[p.host] !== undefined) {\n\t\t\t\tdefaultPort = options.portOverrides?.[def.id]?.[p.host] || p.host;\n\t\t\t}\n\n\t\t\t// Use _EXTERNAL_PORT to avoid colliding with openclawEnvVars _PORT keys.\n\t\t\t// openclawEnvVars define the container port (e.g. GRAFANA_PORT=3000 for\n\t\t\t// internal Docker networking), while these are host port mappings (e.g. 3150).\n\t\t\t// Without this, GRAFANA_PORT=3000 from .env would override the default\n\t\t\t// 3150 in ${GRAFANA_PORT:-3150}:3000, mapping host port 3000 instead.\n\t\t\treturn `\\${${prefix}_EXTERNAL_PORT${suffix}:-${defaultPort}}:${p.container}`;\n\t\t});\n\t}\n\n\tif (def.volumes.length > 0) {\n\t\tsvc.volumes = def.volumes.map((v) => {\n\t\t\tconst isBindMount =\n\t\t\t\tv.name.startsWith(\"./\") || v.name.startsWith(\"/\") || v.name.startsWith(\"~\");\n\n\t\t\tif (!isBindMount) {\n\t\t\t\tallVolumes.add(v.name);\n\t\t\t\tvolumeNames.push(v.name);\n\t\t\t}\n\t\t\treturn `${v.name}:${v.containerPath}`;\n\t\t});\n\t}\n\n\t// PostgreSQL: mount the init script and pass per-service DB passwords\n\tif (def.id === \"postgresql\") {\n\t\tif (!svc.volumes) svc.volumes = [];\n\t\t(svc.volumes as string[]).push(\n\t\t\t\"./postgres/init-databases.sh:/docker-entrypoint-initdb.d/init-databases.sh:ro\",\n\t\t);\n\n\t\t// Pass per-service database password env vars so the init script can use them\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tif (dbReqs.length > 0) {\n\t\t\tconst env = (svc.environment ?? {}) as Record<string, string>;\n\t\t\tfor (const req of dbReqs) {\n\t\t\t\tenv[req.passwordEnvVar] = `\\${${req.passwordEnvVar}}`;\n\t\t\t}\n\t\t\tsvc.environment = env;\n\t\t}\n\t}\n\n\tif (def.healthcheck) {\n\t\tconst hc: Record<string, unknown> = {\n\t\t\ttest: [\"CMD-SHELL\", def.healthcheck.test],\n\t\t\tinterval: def.healthcheck.interval,\n\t\t\ttimeout: def.healthcheck.timeout,\n\t\t\tretries: def.healthcheck.retries,\n\t\t};\n\t\tif (def.healthcheck.startPeriod) {\n\t\t\thc.start_period = def.healthcheck.startPeriod;\n\t\t}\n\t\tsvc.healthcheck = hc;\n\t}\n\n\tsvc.restart = def.restartPolicy;\n\tsvc.networks = def.networks;\n\n\tif (def.command) svc.command = def.command;\n\tif (def.entrypoint) svc.entrypoint = def.entrypoint;\n\n\t// Labels: merge static definition labels with dynamic Traefik labels\n\tconst mergedLabels: Record<string, string> = {};\n\tif (def.labels) Object.assign(mergedLabels, def.labels);\n\tconst traefikLabels = options.traefikLabels?.get(def.id);\n\tif (traefikLabels) Object.assign(mergedLabels, traefikLabels);\n\tif (Object.keys(mergedLabels).length > 0) svc.labels = mergedLabels;\n\n\t// Traefik: bind-mount static config and Docker socket\n\tif (def.id === \"traefik\" && options.traefikLabels) {\n\t\tif (!svc.volumes) svc.volumes = [];\n\t\t(svc.volumes as string[]).push(\n\t\t\t\"./traefik/traefik.yml:/etc/traefik/traefik.yml:ro\",\n\t\t\t\"/var/run/docker.sock:/var/run/docker.sock:ro\",\n\t\t);\n\t}\n\n\tlet deploy: Record<string, unknown> | undefined;\n\tif (def.deploy) {\n\t\tdeploy = JSON.parse(JSON.stringify(def.deploy)) as Record<string, unknown>;\n\t}\n\tif (options.gpu && def.gpuRequired) {\n\t\tconst base = deploy ?? {};\n\t\tconst resources = (base.resources ?? {}) as Record<string, unknown>;\n\t\tdeploy = {\n\t\t\t...base,\n\t\t\tresources: {\n\t\t\t\t...resources,\n\t\t\t\treservations: {\n\t\t\t\t\t...((resources.reservations as Record<string, unknown>) ?? {}),\n\t\t\t\t\tdevices: [{ driver: \"nvidia\", count: \"all\", capabilities: [\"gpu\"] }],\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\t// Memory limits from estimatedMemoryMB\n\tif (def.minMemoryMB && options.hardened) {\n\t\tconst base = deploy ?? {};\n\t\tconst resources = (base.resources ?? {}) as Record<string, unknown>;\n\t\tconst limits = (resources.limits as Record<string, unknown>) ?? {};\n\t\tdeploy = {\n\t\t\t...base,\n\t\t\tresources: {\n\t\t\t\t...resources,\n\t\t\t\tlimits: {\n\t\t\t\t\t...limits,\n\t\t\t\t\tmemory: `${def.minMemoryMB * 2}m`, // 2x min as safe limit\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\tif (deploy) svc.deploy = deploy;\n\n\t// Security hardening (when enabled)\n\tif (options.hardened) {\n\t\tsvc.cap_drop = [\"ALL\"];\n\t\tsvc.security_opt = [\"no-new-privileges:true\"];\n\n\t\t// Services that need specific capabilities\n\t\tconst capAddMap: Record<string, string[]> = {\n\t\t\tcaddy: [\"NET_BIND_SERVICE\"],\n\t\t\ttraefik: [\"NET_BIND_SERVICE\"],\n\t\t\tcrowdsec: [\"NET_BIND_SERVICE\", \"DAC_READ_SEARCH\"],\n\t\t};\n\t\tif (capAddMap[def.id]) {\n\t\t\tsvc.cap_add = capAddMap[def.id];\n\t\t}\n\t}\n\n\t// Merge both dependsOn and requires to ensure proper Docker startup ordering\n\tconst depIds = [...new Set([...def.dependsOn, ...def.requires])].filter((id) =>\n\t\tresolved.services.some((s) => s.definition.id === id),\n\t);\n\tif (depIds.length > 0) {\n\t\tconst dependsOn: Record<string, { condition: string }> = {};\n\t\tfor (const depId of depIds) {\n\t\t\tconst dep = resolved.services.find((s) => s.definition.id === depId);\n\t\t\tdependsOn[depId] = {\n\t\t\t\tcondition: dep?.definition.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t\t};\n\t\t}\n\t\tsvc.depends_on = dependsOn;\n\t}\n\n\treturn { entry: svc, volumeNames };\n}\n\n// ── PostgreSQL Setup Init Container ─────────────────────────────────────────\n\n/**\n * Builds a one-shot init container that creates databases and users for\n * services that need their own PostgreSQL database. Runs AFTER PostgreSQL\n * is healthy, on every `docker compose up`, and is idempotent.\n *\n * Uses standard PG* environment variables (PGHOST, PGUSER, PGDATABASE,\n * PGPASSWORD) so psql/createuser/createdb automatically connect without\n * needing explicit -h/-U/-d flags — simpler and avoids YAML escaping issues.\n *\n * Returns null when no setup is needed (no PostgreSQL or no DB requirements).\n */\nexport function buildPostgresSetup(resolved: ResolverOutput): Record<string, unknown> | null {\n\tconst hasPostgres = resolved.services.some((s) => s.definition.id === \"postgresql\");\n\tif (!hasPostgres) return null;\n\n\tconst dbReqs = getDbRequirements(resolved);\n\tif (dbReqs.length === 0) return null;\n\n\t// Build a shell script with one command per line.\n\t// Uses $$ to escape $ from Docker Compose's variable substitution —\n\t// Docker Compose converts $$ → $ before passing to the container.\n\t// NO set -e: we handle errors explicitly so one failed service doesn't block others.\n\tconst scriptLines = [\"echo '=== PostgreSQL database setup ==='\", \"FAILED=0\"];\n\n\tfor (const req of dbReqs) {\n\t\t// Each service's setup is wrapped so a failure doesn't block the others.\n\t\t// Uses psql -v ON_ERROR_STOP=0 so SQL errors don't abort psql.\n\t\tscriptLines.push(\n\t\t\t`echo \"Setting up database '${req.dbName}' with user '${req.dbUser}'...\"`,\n\t\t\t// Create user if not exists (pure SQL, no createuser binary quirks)\n\t\t\t`psql -c \"SELECT 1 FROM pg_roles WHERE rolname='${req.dbUser}'\" | grep -q 1 || psql -c \"CREATE ROLE ${req.dbUser} WITH LOGIN PASSWORD '$$${req.passwordEnvVar}'\"`,\n\t\t\t// Always sync the password to match current env\n\t\t\t`psql -c \"ALTER ROLE ${req.dbUser} WITH LOGIN PASSWORD '$$${req.passwordEnvVar}'\"`,\n\t\t\t// Create database if not exists\n\t\t\t`psql -tc \"SELECT 1 FROM pg_database WHERE datname='${req.dbName}'\" | grep -q 1 || psql -c \"CREATE DATABASE ${req.dbName} OWNER ${req.dbUser}\"`,\n\t\t\t// Grant privileges (idempotent)\n\t\t\t`psql -c \"GRANT ALL PRIVILEGES ON DATABASE ${req.dbName} TO ${req.dbUser}\" || FAILED=1`,\n\t\t\t`echo \" Done: ${req.dbName}\"`,\n\t\t);\n\t}\n\n\tscriptLines.push(\"echo '=== All databases ready ==='\", \"exit $$FAILED\");\n\n\t// Standard PG* env vars: psql/createuser/createdb use these automatically\n\tconst env: Record<string, string> = {\n\t\tPGHOST: \"postgresql\",\n\t\tPGUSER: \"${POSTGRES_USER:-openclaw}\",\n\t\tPGDATABASE: \"${POSTGRES_DB:-openclaw}\",\n\t\tPGPASSWORD: \"${POSTGRES_PASSWORD}\",\n\t};\n\tfor (const req of dbReqs) {\n\t\tenv[req.passwordEnvVar] = `\\${${req.passwordEnvVar}}`;\n\t}\n\n\treturn {\n\t\timage: \"postgres:17-alpine\",\n\t\tdepends_on: {\n\t\t\tpostgresql: { condition: \"service_healthy\" },\n\t\t},\n\t\tenvironment: env,\n\t\t// command MUST be a single-element array so the entire script is passed\n\t\t// as ONE argument to `sh -c`. A plain string gets shlex-split by Docker\n\t\t// Compose into multiple args, breaking multi-line scripts.\n\t\tentrypoint: [\"/bin/sh\", \"-c\"],\n\t\tcommand: [scriptLines.join(\"\\n\")],\n\t\trestart: quotedStr(\"no\"),\n\t\tnetworks: [\"openclaw-network\"],\n\t};\n}\n\n// ── Single-File Compose ─────────────────────────────────────────────────────\n\n/**\n * Generates a single Docker Compose YAML string with ALL services.\n * Backward-compatible signature.\n */\nexport function compose(resolved: ResolverOutput, options: ComposeOptions): string {\n\tconst isDirectInstall = options.openclawInstallMethod === \"direct\";\n\n\t// Build depends_on for ALL companion services\n\tconst gatewayDependsOn: Record<string, { condition: string }> = {};\n\tfor (const { definition: def } of resolved.services) {\n\t\tgatewayDependsOn[def.id] = {\n\t\t\tcondition: def.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t};\n\t}\n\n\tconst services: Record<string, Record<string, unknown>> = {};\n\tlet allVolumes = new Set<string>();\n\n\tif (!isDirectInstall) {\n\t\tconst {\n\t\t\tgatewayService,\n\t\t\tcliService,\n\t\t\tallVolumes: gwVolumes,\n\t\t} = buildGatewayServices(resolved, options, gatewayDependsOn);\n\t\tallVolumes = gwVolumes;\n\t\tservices[\"openclaw-gateway\"] = gatewayService;\n\t\t// CLI service added after companions\n\t\t// Determine which services need DB setup so we can redirect their depends_on\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tconst { entry } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\t\tif (deps.postgresql) {\n\t\t\t\t\tdelete deps.postgresql;\n\t\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\tservices[def.id] = entry;\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tservices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\n\t\tservices[\"openclaw-cli\"] = cliService;\n\t} else {\n\t\t// Direct install: no gateway/CLI containers, just companion services\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tconst { entry } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\t\tif (deps.postgresql) {\n\t\t\t\t\tdelete deps.postgresql;\n\t\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\tservices[def.id] = entry;\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tservices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\t}\n\n\tconst volumes: Record<string, null> = {};\n\tfor (const v of [...allVolumes].sort()) {\n\t\tvolumes[v] = null;\n\t}\n\n\tconst networks = { \"openclaw-network\": { driver: \"bridge\" } };\n\n\treturn stringify({ services, volumes, networks }, YAML_OPTIONS);\n}\n\n// ── Multi-File Compose ──────────────────────────────────────────────────────\n\ninterface ServiceInfo {\n\tid: string;\n\tcategory: ServiceCategory;\n\tentry: Record<string, unknown>;\n\tvolumeNames: string[];\n}\n\n/**\n * Generates multiple Docker Compose files, splitting services into profile-based\n * override files by category.\n */\nexport function composeMultiFile(resolved: ResolverOutput, options: ComposeOptions): ComposeResult {\n\tconst isDirectInstall = options.openclawInstallMethod === \"direct\";\n\tconst allVolumes = new Set<string>();\n\n\t// Build all companion service entries & classify by category\n\tconst serviceInfos: ServiceInfo[] = [];\n\tconst dbReqs = getDbRequirements(resolved);\n\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\tfor (const { definition: def } of resolved.services) {\n\t\tconst { entry, volumeNames } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t// Redirect DB-dependent services to depend on postgres-setup\n\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\tif (deps.postgresql) {\n\t\t\t\tdelete deps.postgresql;\n\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t}\n\t\t}\n\t\tserviceInfos.push({ id: def.id, category: def.category, entry, volumeNames });\n\t}\n\n\t// Partition services into base vs. profile files\n\tconst baseServiceIds = new Set<string>();\n\tconst profileFileMap: Record<string, { profile: string; services: ServiceInfo[] }> = {};\n\n\tfor (const info of serviceInfos) {\n\t\tconst mapping = CATEGORY_PROFILE_MAP[info.category];\n\t\tif (mapping) {\n\t\t\tif (!profileFileMap[mapping.file]) {\n\t\t\t\tprofileFileMap[mapping.file] = { profile: mapping.profile, services: [] };\n\t\t\t}\n\t\t\tprofileFileMap[mapping.file]!.services.push(info);\n\t\t} else {\n\t\t\tbaseServiceIds.add(info.id);\n\t\t}\n\t}\n\n\tconst baseServices: Record<string, Record<string, unknown>> = {};\n\n\tif (!isDirectInstall) {\n\t\t// Gateway depends_on (only base services)\n\t\tconst gatewayDependsOn: Record<string, { condition: string }> = {};\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tif (baseServiceIds.has(def.id)) {\n\t\t\t\tgatewayDependsOn[def.id] = {\n\t\t\t\t\tcondition: def.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst {\n\t\t\tgatewayService,\n\t\t\tcliService,\n\t\t\tallVolumes: gwVolumes,\n\t\t} = buildGatewayServices(resolved, options, gatewayDependsOn);\n\n\t\t// Merge gateway volumes into allVolumes\n\t\tfor (const v of gwVolumes) allVolumes.add(v);\n\n\t\tbaseServices[\"openclaw-gateway\"] = gatewayService;\n\n\t\tfor (const info of serviceInfos) {\n\t\t\tif (baseServiceIds.has(info.id)) {\n\t\t\t\tbaseServices[info.id] = info.entry;\n\t\t\t}\n\t\t}\n\n\t\t// Add postgres-setup init container if needed\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tbaseServices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\n\t\tbaseServices[\"openclaw-cli\"] = cliService;\n\t} else {\n\t\t// Direct install: no gateway/CLI containers\n\t\tfor (const info of serviceInfos) {\n\t\t\tif (baseServiceIds.has(info.id)) {\n\t\t\t\tbaseServices[info.id] = info.entry;\n\t\t\t}\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tbaseServices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\t}\n\n\tconst sortedAllVolumes: Record<string, null> = {};\n\tfor (const v of [...allVolumes].sort()) {\n\t\tsortedAllVolumes[v] = null;\n\t}\n\n\tconst networks = { \"openclaw-network\": { driver: \"bridge\" } };\n\n\tconst files: Record<string, string> = {};\n\tfiles[\"docker-compose.yml\"] = stringify(\n\t\t{ services: baseServices, volumes: sortedAllVolumes, networks },\n\t\tYAML_OPTIONS,\n\t);\n\n\t// Profile override files\n\tconst usedProfiles = new Set<string>();\n\n\tfor (const [fileName, { profile, services }] of Object.entries(profileFileMap)) {\n\t\tusedProfiles.add(profile);\n\n\t\tconst profileServices: Record<string, Record<string, unknown>> = {};\n\t\tconst profileVolumes = new Set<string>();\n\n\t\tfor (const info of services) {\n\t\t\tprofileServices[info.id] = { ...info.entry, profiles: [profile] };\n\t\t\tfor (const vName of info.volumeNames) {\n\t\t\t\tprofileVolumes.add(vName);\n\t\t\t}\n\t\t}\n\n\t\tconst fileContent: Record<string, unknown> = { services: profileServices };\n\n\t\tif (profileVolumes.size > 0) {\n\t\t\tconst sortedProfileVolumes: Record<string, null> = {};\n\t\t\tfor (const v of [...profileVolumes].sort()) {\n\t\t\t\tsortedProfileVolumes[v] = null;\n\t\t\t}\n\t\t\tfileContent.volumes = sortedProfileVolumes;\n\t\t}\n\n\t\tfiles[fileName] = stringify(fileContent, YAML_OPTIONS);\n\t}\n\n\treturn {\n\t\tfiles,\n\t\tmainFile: \"docker-compose.yml\",\n\t\tprofiles: [...usedProfiles].sort(),\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const
|
|
2
|
-
const require_resolver = require("./resolver.cjs");
|
|
1
|
+
const require_test_CTcmp4Su = require("./test.CTcmp4Su-DlzTarwH.cjs");
|
|
3
2
|
const require_composer = require("./composer.cjs");
|
|
3
|
+
const require_resolver = require("./resolver.cjs");
|
|
4
4
|
//#region src/composer.snapshot.test.ts
|
|
5
5
|
function generateForPreset(services, skillPacks = []) {
|
|
6
6
|
return require_composer.composeMultiFile(require_resolver.resolve({
|
|
@@ -15,29 +15,29 @@ function generateForPreset(services, skillPacks = []) {
|
|
|
15
15
|
openclawVersion: "latest"
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
require_test_CTcmp4Su.describe("compose snapshot tests", () => {
|
|
19
|
+
require_test_CTcmp4Su.it("minimal preset (redis only)", () => {
|
|
20
|
+
require_test_CTcmp4Su.globalExpect(generateForPreset(["redis"]).files["docker-compose.yml"]).toMatchSnapshot();
|
|
21
21
|
});
|
|
22
|
-
|
|
22
|
+
require_test_CTcmp4Su.it("creator preset (ffmpeg + remotion + minio + redis)", () => {
|
|
23
23
|
const result = generateForPreset([
|
|
24
24
|
"ffmpeg",
|
|
25
25
|
"remotion",
|
|
26
26
|
"minio",
|
|
27
27
|
"redis"
|
|
28
28
|
], ["video-creator"]);
|
|
29
|
-
|
|
30
|
-
if (result.files["docker-compose.media.yml"])
|
|
29
|
+
require_test_CTcmp4Su.globalExpect(result.files["docker-compose.yml"]).toMatchSnapshot();
|
|
30
|
+
if (result.files["docker-compose.media.yml"]) require_test_CTcmp4Su.globalExpect(result.files["docker-compose.media.yml"]).toMatchSnapshot();
|
|
31
31
|
});
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
require_test_CTcmp4Su.it("researcher preset (qdrant + searxng + browserless + redis)", () => {
|
|
33
|
+
require_test_CTcmp4Su.globalExpect(generateForPreset([
|
|
34
34
|
"qdrant",
|
|
35
35
|
"searxng",
|
|
36
36
|
"browserless",
|
|
37
37
|
"redis"
|
|
38
38
|
], ["research-agent"]).files["docker-compose.yml"]).toMatchSnapshot();
|
|
39
39
|
});
|
|
40
|
-
|
|
40
|
+
require_test_CTcmp4Su.it("devops preset (n8n + postgresql + redis + monitoring)", () => {
|
|
41
41
|
const result = generateForPreset([
|
|
42
42
|
"n8n",
|
|
43
43
|
"postgresql",
|
|
@@ -46,10 +46,10 @@ require_vi_2VT5v0um.describe("compose snapshot tests", () => {
|
|
|
46
46
|
"grafana",
|
|
47
47
|
"prometheus"
|
|
48
48
|
], ["dev-ops"]);
|
|
49
|
-
|
|
50
|
-
if (result.files["docker-compose.monitoring.yml"])
|
|
49
|
+
require_test_CTcmp4Su.globalExpect(result.files["docker-compose.yml"]).toMatchSnapshot();
|
|
50
|
+
if (result.files["docker-compose.monitoring.yml"]) require_test_CTcmp4Su.globalExpect(result.files["docker-compose.monitoring.yml"]).toMatchSnapshot();
|
|
51
51
|
});
|
|
52
|
-
|
|
52
|
+
require_test_CTcmp4Su.it("full preset (many services)", () => {
|
|
53
53
|
const result = generateForPreset([
|
|
54
54
|
"redis",
|
|
55
55
|
"postgresql",
|
|
@@ -69,11 +69,11 @@ require_vi_2VT5v0um.describe("compose snapshot tests", () => {
|
|
|
69
69
|
"whisper",
|
|
70
70
|
"gotify"
|
|
71
71
|
]);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
require_test_CTcmp4Su.globalExpect(result.files["docker-compose.yml"]).toMatchSnapshot();
|
|
73
|
+
require_test_CTcmp4Su.globalExpect(result.profiles.length).toBeGreaterThan(0);
|
|
74
|
+
require_test_CTcmp4Su.globalExpect(Object.keys(result.files).length).toBeGreaterThan(1);
|
|
75
75
|
});
|
|
76
|
-
|
|
76
|
+
require_test_CTcmp4Su.it("multi-file output has correct profile assignments", () => {
|
|
77
77
|
const result = generateForPreset([
|
|
78
78
|
"redis",
|
|
79
79
|
"ollama",
|
|
@@ -81,8 +81,8 @@ require_vi_2VT5v0um.describe("compose snapshot tests", () => {
|
|
|
81
81
|
"grafana",
|
|
82
82
|
"prometheus"
|
|
83
83
|
]);
|
|
84
|
-
if (result.files["docker-compose.ai.yml"])
|
|
85
|
-
if (result.files["docker-compose.monitoring.yml"])
|
|
84
|
+
if (result.files["docker-compose.ai.yml"]) require_test_CTcmp4Su.globalExpect(result.files["docker-compose.ai.yml"]).toContain("profiles:");
|
|
85
|
+
if (result.files["docker-compose.monitoring.yml"]) require_test_CTcmp4Su.globalExpect(result.files["docker-compose.monitoring.yml"]).toContain("profiles:");
|
|
86
86
|
});
|
|
87
87
|
});
|
|
88
88
|
//#endregion
|