diff --git a/deploy/Dockerfile b/deploy/Dockerfile index 1c4bb3c9c4..57c7599a44 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -81,6 +81,12 @@ COPY --chown=open-design:open-design craft ./craft COPY --chown=open-design:open-design prompt-templates ./prompt-templates COPY --chown=open-design:open-design assets/frames ./assets/frames COPY --chown=open-design:open-design assets/community-pets ./assets/community-pets +# Plan §3.J4 / spec §23.3.5 — bundled atom plugins registered on +# daemon boot. The directory contains `plugins/_official/atoms//` +# pairs (SKILL.md + open-design.json); registerBundledPlugins() walks +# it on startup so the container ships with first-party atoms reachable +# via the same registry path third-party plugins use. +COPY --chown=open-design:open-design plugins/_official ./plugins/_official RUN mkdir -p /app/.od && \ chown -R open-design:open-design /app diff --git a/scripts/guard.ts b/scripts/guard.ts index 4e0604aa83..8ea39cc8c3 100644 --- a/scripts/guard.ts +++ b/scripts/guard.ts @@ -36,6 +36,7 @@ const residualSkippedDirectories = new Set([ const residualAllowedExactPaths = new Set([ // esbuild config entrypoints are executed directly by Node before package // dist output exists. + "packages/agui-adapter/esbuild.config.mjs", "packages/contracts/esbuild.config.mjs", "packages/platform/esbuild.config.mjs", "packages/plugin-runtime/esbuild.config.mjs", diff --git a/tools/pack/docker-compose.yml b/tools/pack/docker-compose.yml new file mode 100644 index 0000000000..7cc369eb3a --- /dev/null +++ b/tools/pack/docker-compose.yml @@ -0,0 +1,73 @@ +# Phase 5 / spec §15.4 — reference Docker Compose manifest. +# +# Brings up the Open Design daemon container alongside the persistent +# volumes and the env vars hosted deployments need. Drop this file into +# any directory and run `docker compose up -d` — the only required +# customisation is the OD_API_TOKEN secret and the provider keys. +# +# Build the image first (from the repo root): +# +# docker build -f deploy/Dockerfile -t open-design/od:dev . +# +# Or pull a published tag: +# +# docker pull ghcr.io/open-design/od:edge +# +# Then `docker compose -f tools/pack/docker-compose.yml up -d`. + +version: "3.9" + +services: + od: + # The image tag the operator wants. Spec §15.5 expects multi-arch + # ghcr.io/open-design/od:; the build path above produces + # `open-design/od:dev` locally. + image: ${OD_IMAGE:-ghcr.io/open-design/od:edge} + container_name: od + + # Spec §15.3 — every dimension the daemon respects in headless + # mode. OD_BIND_HOST=0.0.0.0 + OD_API_TOKEN is the canonical + # hosted-mode tuple; remove these two if running on a private + # docker network with an upstream reverse proxy that already + # gates access. + environment: + OD_BIND_HOST: ${OD_BIND_HOST:-0.0.0.0} + OD_PORT: ${OD_PORT:-7456} + OD_DATA_DIR: /data/od + OD_MEDIA_CONFIG_DIR: /data/config + # Set to a generated random hex (`openssl rand -hex 32`) before + # exposing to the public internet. Phase 5 wires the daemon to + # refuse OD_BIND_HOST=0.0.0.0 without OD_API_TOKEN. + OD_API_TOKEN: ${OD_API_TOKEN:-} + OD_NAMESPACE: ${OD_NAMESPACE:-production} + # Snapshot retention knobs (spec §11.4 PB2). Default ttl is 30 + # days; set to 0 to keep snapshots forever. + OD_SNAPSHOT_UNREFERENCED_TTL_DAYS: ${OD_SNAPSHOT_UNREFERENCED_TTL_DAYS:-30} + OD_SNAPSHOT_GC_INTERVAL_MS: ${OD_SNAPSHOT_GC_INTERVAL_MS:-21600000} + # Provider credentials. Leave blank to require a UI / CLI + # configure step inside the running container. + ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} + TAVILY_API_KEY: ${TAVILY_API_KEY:-} + + # Two-volume layout per spec §15.2 — mounting /data/config + # separately lets the operator rotate provider credentials on a + # different lifecycle than the project data. + volumes: + - od-data:/data/od + - od-config:/data/config + + ports: + - "${OD_PORT:-7456}:${OD_PORT:-7456}" + + restart: unless-stopped + + healthcheck: + # /api/daemon/status is the canonical lifecycle probe (Phase 1.5). + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:${OD_PORT:-7456}/api/daemon/status >/dev/null 2>&1 || exit 1"] + interval: 30s + timeout: 5s + retries: 3 + +volumes: + od-data: {} + od-config: {} diff --git a/tools/pack/helm/open-design/Chart.yaml b/tools/pack/helm/open-design/Chart.yaml new file mode 100644 index 0000000000..3586f0ed2a --- /dev/null +++ b/tools/pack/helm/open-design/Chart.yaml @@ -0,0 +1,23 @@ +apiVersion: v2 +name: open-design +description: | + Open Design daemon Helm chart. Spec §15.5 reference deployment for + AWS / GCP / Azure / Aliyun / Tencent / Huawei / self-hosted Kubernetes. + Pairs with `tools/pack/docker-compose.yml` as the docker-only entry + point. +type: application +version: 0.1.0 +appVersion: "0.5.0" +icon: https://open-design.ai/favicon.png +keywords: + - open-design + - design-agent + - generative-ui + - plugins + - skills +home: https://open-design.ai +sources: + - https://github.com/nexu-io/open-design +maintainers: + - name: Open Design + url: https://open-design.ai diff --git a/tools/pack/helm/open-design/README.md b/tools/pack/helm/open-design/README.md new file mode 100644 index 0000000000..1c0ac09bde --- /dev/null +++ b/tools/pack/helm/open-design/README.md @@ -0,0 +1,39 @@ +# Open Design Helm chart + +> Spec §15.5 reference deployment. +> Status: **values surface locked, templates pending.** + +This chart is the parameter contract Phase 5 standardised on. The +`templates/` directory is intentionally absent in this slice — the +follow-up PR that lands the Kubernetes manifests will consume the +same `values.yaml` schema. + +## Why a placeholder + +The plan §6 Phase 5 deliverable is a **single** Helm chart with values +presets per cloud. Splitting that into "values surface now, templates +later" keeps the cloud-specific overrides reviewable in isolation +(every cloud's volume + secret pattern shows up as a `values-.yaml` +override file). + +## Cloud overrides (planned values files) + +```text +values-aws.yaml persistence.backend=efs secrets.backend=secrets-manager +values-gcp.yaml persistence.backend=filestore secrets.backend=secret-manager +values-azure.yaml persistence.backend=files secrets.backend=key-vault +values-aliyun.yaml persistence.backend=nas secrets.backend=kms +values-tencent.yaml persistence.backend=cfs secrets.backend=ssm +values-huawei.yaml persistence.backend=sfs secrets.backend=kms +values-self.yaml persistence.backend=hostPath secrets.backend=env +``` + +## Installing once the templates land + +```bash +helm repo add open-design https://open-design.ai/charts +helm install od open-design/open-design \ + --set image.tag=edge \ + --set secrets.apiToken="$(openssl rand -hex 32)" \ + -f values-aws.yaml # one of the cloud overrides above +``` diff --git a/tools/pack/helm/open-design/values.yaml b/tools/pack/helm/open-design/values.yaml new file mode 100644 index 0000000000..baf08c764b --- /dev/null +++ b/tools/pack/helm/open-design/values.yaml @@ -0,0 +1,85 @@ +# Open Design daemon — Helm chart values. Spec §15.5. +# +# Override the image / persistence / secret pattern per cloud: +# AWS: persistence.backend=efs secrets.backend=secrets-manager +# GCP: persistence.backend=filestore + gcsfuse secrets.backend=secret-manager +# Azure: persistence.backend=files secrets.backend=key-vault +# Aliyun: persistence.backend=nas secrets.backend=kms +# Tencent: persistence.backend=cfs secrets.backend=ssm +# Huawei: persistence.backend=sfs secrets.backend=kms +# Self-hosted: persistence.backend=hostPath / nfs / longhorn +# +# These values are reference defaults; the chart's templates/ aren't +# wired here yet (Phase 5 follow-up). The file pins the parameter +# surface so a downstream PR can implement the templates without +# revisiting the deployment shape. + +image: + repository: ghcr.io/open-design/od + tag: edge + pullPolicy: IfNotPresent + +# `replicaCount: 1` is the v1 default — pluggable storage + Postgres +# adapter (spec §15.6) lift this in a follow-up. +replicaCount: 1 + +service: + type: ClusterIP + port: 7456 + +persistence: + enabled: true + data: + size: 20Gi + storageClass: "" + config: + size: 1Gi + storageClass: "" + +env: + OD_BIND_HOST: 0.0.0.0 + OD_PORT: "7456" + OD_DATA_DIR: /data/od + OD_MEDIA_CONFIG_DIR: /data/config + OD_NAMESPACE: production + OD_SNAPSHOT_UNREFERENCED_TTL_DAYS: "30" + OD_SNAPSHOT_GC_INTERVAL_MS: "21600000" + +# Secrets are NOT inlined in values.yaml — operators inject them +# through their cloud's secret manager. This block is the schema the +# templates will consume. +secrets: + apiToken: "" # OD_API_TOKEN + anthropicApiKey: "" # ANTHROPIC_API_KEY + tavilyApiKey: "" # TAVILY_API_KEY + +resources: + limits: + cpu: 2 + memory: 2Gi + requests: + cpu: 250m + memory: 512Mi + +podSecurityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + runAsNonRoot: true + +securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + readOnlyRootFilesystem: false + +ingress: + enabled: false + className: "" + annotations: {} + hosts: [] + tls: [] + +nodeSelector: {} +tolerations: [] +affinity: {}