源码快照 @ 2026-09-02 (source-only, 剔除非源码素材)
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
apiVersion: v2
|
||||
name: openwork-ee
|
||||
description: OpenWork EE Den control plane, web app, and optional inference service
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "0.1.0"
|
||||
@@ -0,0 +1,986 @@
|
||||
# OpenWork EE Helm Chart
|
||||
|
||||
Initial Helm chart for the OpenWork EE Den stack:
|
||||
|
||||
- `den-api` control plane on port `8788`
|
||||
- `den-web` web app on port `3005`
|
||||
- optional `inference` service on port `8791`
|
||||
- shared ConfigMap and Secret templating
|
||||
- optional Ingress for web and API hosts
|
||||
- pre-install/pre-upgrade migration Job scaffold
|
||||
|
||||
## Install
|
||||
|
||||
Published releases are available as an OCI Helm chart:
|
||||
|
||||
```bash
|
||||
helm upgrade --install openwork-ee oci://ghcr.io/different-ai/charts/openwork-ee \
|
||||
--version REPLACE_OPENWORK_VERSION \
|
||||
-f values.prod.yaml
|
||||
```
|
||||
|
||||
Use the matching image tag in `values.prod.yaml`:
|
||||
|
||||
Create a values file for the target environment:
|
||||
|
||||
```yaml
|
||||
image:
|
||||
tag: "REPLACE_OPENWORK_VERSION"
|
||||
|
||||
config:
|
||||
tenancy:
|
||||
# Default chart behavior is single-org for private/self-hosted installs.
|
||||
# Hosted OpenWork Cloud should set this to "multi_org" explicitly.
|
||||
mode: "single_org"
|
||||
singleOrgName: "OpenWork"
|
||||
singleOrgSlug: "default"
|
||||
ownerEmails: "admin@example.com"
|
||||
allowPublicSignup: "false"
|
||||
requireEmailVerification: "false"
|
||||
public:
|
||||
# Single public Den web origin. The chart renders this as DEN_BASE_URL and
|
||||
# den-api derives Better Auth, CORS/trusted origins, web-app hosts, API
|
||||
# defaults, and MCP resource defaults from it.
|
||||
webOrigin: "https://openwork.example.com"
|
||||
# Leave these blank unless you intentionally expose a split Den API origin
|
||||
# or need migration compatibility with an older topology.
|
||||
apiOrigin: ""
|
||||
mcpResourceUrl: ""
|
||||
mcpClaimNamespace: "https://openwork.example.com"
|
||||
desktopDenBaseUrl: ""
|
||||
corsOrigins: ""
|
||||
betterAuthTrustedOrigins: ""
|
||||
webAppHosts: ""
|
||||
bootstrapAdminEmails: "admin@example.com"
|
||||
# Self-hosted default: every organization gets install downloads.
|
||||
installLinksGatingEnabled: "false"
|
||||
authCallbackUrl: "https://openwork.example.com"
|
||||
githubConnector:
|
||||
appId: ""
|
||||
clientId: ""
|
||||
|
||||
secret:
|
||||
values:
|
||||
# Transitional/smoke TLS only: sslaccept=accept encrypts without certificate verification.
|
||||
# For production verification, use customCa plus sslmode=verify-full or verify-ca.
|
||||
databaseUrl: "mysql://openwork:REPLACE_ME@mysql.example.internal:3306/openwork_den?sslaccept=accept"
|
||||
betterAuthSecret: "REPLACE_WITH_AT_LEAST_32_CHARACTERS"
|
||||
denDbEncryptionKey: "REPLACE_WITH_AT_LEAST_32_CHARACTERS"
|
||||
emailFrom: "OpenWork <no-reply@example.com>"
|
||||
smtpHost: "smtp.example.com"
|
||||
smtpPort: "587"
|
||||
smtpUser: "openwork@example.com"
|
||||
smtpPass: "REPLACE_ME"
|
||||
smtpSecure: "false"
|
||||
githubConnectorAppClientSecret: ""
|
||||
githubConnectorAppPrivateKey: ""
|
||||
githubConnectorAppWebhookSecret: ""
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
web:
|
||||
host: openwork.example.com
|
||||
api:
|
||||
host: api.openwork.example.com
|
||||
```
|
||||
|
||||
### Upgrade note: public URL values
|
||||
|
||||
Current chart versions make `config.public.webOrigin` the primary public URL.
|
||||
It renders as `DEN_BASE_URL`, and Den derives these values from it unless you
|
||||
set explicit compatibility overrides:
|
||||
|
||||
- `BETTER_AUTH_URL`
|
||||
- `CORS_ORIGINS`
|
||||
- `DEN_BETTER_AUTH_TRUSTED_ORIGINS`
|
||||
- `DEN_WEB_APP_HOSTS`
|
||||
- `DEN_API_PUBLIC_URL`
|
||||
- `DEN_MCP_RESOURCE_URL`
|
||||
|
||||
When upgrading an existing release, inspect your values file for the older
|
||||
split-origin keys under `config.public`. If your deployment is single-origin,
|
||||
remove `apiOrigin`, `mcpResourceUrl`, `desktopDenBaseUrl`, `corsOrigins`,
|
||||
`betterAuthTrustedOrigins`, and `webAppHosts` or set them to `""` so Den can
|
||||
derive them from `webOrigin`. Keep `apiOrigin`/`mcpResourceUrl` only when you
|
||||
intentionally expose a separate API origin for install-link exchange or external
|
||||
MCP clients.
|
||||
|
||||
For private GHCR packages, authenticate before installing:
|
||||
|
||||
```bash
|
||||
helm registry login ghcr.io
|
||||
kubectl create secret docker-registry ghcr-pull-secret \
|
||||
--docker-server=ghcr.io \
|
||||
--docker-username=<github-user> \
|
||||
--docker-password=<github-token>
|
||||
```
|
||||
|
||||
Then add:
|
||||
|
||||
```yaml
|
||||
imagePullSecrets:
|
||||
- name: ghcr-pull-secret
|
||||
```
|
||||
|
||||
For local development from a repository checkout, render or install directly:
|
||||
|
||||
```bash
|
||||
helm template openwork-ee ./packaging/helm/openwork-ee -f values.prod.yaml
|
||||
helm upgrade --install openwork-ee ./packaging/helm/openwork-ee -f values.prod.yaml
|
||||
```
|
||||
|
||||
### Automations rollout
|
||||
|
||||
The Helm chart advertises Automations as unavailable by default for self-hosted
|
||||
and customer-managed deployments. Availability and server shutdown are
|
||||
separate so a Den upgrade cannot remove routes beneath an older published
|
||||
Desktop:
|
||||
|
||||
| `automationsEnabled` | `automationsRuntimeEnabled` | Behavior |
|
||||
| --- | --- | --- |
|
||||
| `"false"` | `"true"` | New Desktops hide Automations; legacy routes and scheduling remain available during the upgrade window. |
|
||||
| `"false"` | `"false"` | Automations are hard-disabled: routes, MCP resources, and scheduler startup are omitted. |
|
||||
| `"true"` | `"true"` | Automations are available and execute normally. |
|
||||
| `"true"` | `"false"` | The runtime shutdown wins and Desktop receives `automationsEnabled: false`. |
|
||||
|
||||
Set both values explicitly when the deployment is ready to run Automations:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
public:
|
||||
automationsEnabled: "true"
|
||||
automationsRuntimeEnabled: "true"
|
||||
```
|
||||
|
||||
These render `DEN_AUTOMATIONS_ENABLED=true` and
|
||||
`DEN_AUTOMATIONS_RUNTIME_ENABLED=true` for Den. An entirely unconfigured Den
|
||||
keeps availability fail-closed while preserving the legacy runtime. When using
|
||||
raw environment variables, an explicit `DEN_AUTOMATIONS_ENABLED` value also
|
||||
becomes the runtime default: `false` is therefore a complete shutdown unless
|
||||
`DEN_AUTOMATIONS_RUNTIME_ENABLED=true` explicitly selects mixed-version
|
||||
compatibility. The chart always renders both values to make that choice
|
||||
unambiguous. Hosted OpenWork Cloud explicitly enables availability.
|
||||
|
||||
Desktop v0.18.35 and newer consume the value from `/v1/me/desktop-config`, hide
|
||||
the Automation surface, and do not register a runner unless the value is
|
||||
explicitly true. Older clients predate that contract, so the runtime flag must
|
||||
remain true while they are in use even when availability is false.
|
||||
|
||||
For an existing deployment, stage the upgrade so independently released Den
|
||||
and Desktop versions never observe an unintended flag state:
|
||||
|
||||
1. Keep `config.public.automationsRuntimeEnabled: "true"` while any connected
|
||||
Desktop is older than v0.18.35.
|
||||
2. Upgrade Den. Legacy Desktops retain their existing routes and scheduling;
|
||||
compatible Desktops honor `automationsEnabled` from desktop config.
|
||||
3. Roll out Desktop v0.18.35 or newer to the whole deployment.
|
||||
4. To keep Automations, set both values to true. To disable them, set both
|
||||
values to false only after the Desktop rollout is complete.
|
||||
|
||||
New installations with no legacy Desktop clients can hard-disable Automations
|
||||
immediately by setting both values to false.
|
||||
|
||||
### Dashboards rollout
|
||||
|
||||
The organization-managed Dashboard is unavailable by default. Enable it for a
|
||||
self-hosted or customer-managed deployment with:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
public:
|
||||
dashboardsEnabled: "true"
|
||||
```
|
||||
|
||||
The chart renders this value as `DEN_DASHBOARDS_ENABLED`. Raw environment-based
|
||||
deployments can set the same variable directly. Hosted environments use that
|
||||
same flag, so Dashboard availability does not depend on a per-device preference.
|
||||
Desktop reads `dashboardEnabled` from `/v1/me/desktop-config` and hides both the
|
||||
sidebar entry and route unless the server explicitly returns `true`.
|
||||
|
||||
### OpenWork Web rollout
|
||||
|
||||
OpenWork Web is unavailable by default for self-hosted and customer-managed
|
||||
deployments. OpenWork Cloud enables it in its deployment values with:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
public:
|
||||
openworkWebEnabled: "true"
|
||||
```
|
||||
|
||||
The chart renders this value as `DEN_OPENWORK_WEB_ENABLED`. A raw environment
|
||||
deployment can set the same variable directly. Missing, blank, false, or an
|
||||
unrecognized value fails closed: Den omits Web from its advertised
|
||||
capabilities, the sidebar and Billing offer stay hidden, and Web billing routes
|
||||
return the deployment-unavailable response. Availability is deployment-wide;
|
||||
it does not depend on organization mode, Stripe-variable presence, or mutable
|
||||
organization metadata.
|
||||
|
||||
Enabling the flag advertises the hosted product. The deployment must also
|
||||
configure `STRIPE_SECRET_KEY` and `STRIPE_OPENWORK_WEB_PRICE_ID` before the
|
||||
purchase action becomes available. This separate billing-readiness check keeps
|
||||
a partially configured hosted rollout visible but non-purchasable instead of
|
||||
mistaking secrets for an availability signal.
|
||||
|
||||
Provider-specific starter guides:
|
||||
|
||||
- AWS EKS:
|
||||
[guide](../../../docs/aws-eks-helm.md),
|
||||
[`examples/values.aws-load-balancer.yaml`](examples/values.aws-load-balancer.yaml),
|
||||
[`examples/values.aws-load-balancer-http-smoke.yaml`](examples/values.aws-load-balancer-http-smoke.yaml).
|
||||
The recommended first AWS path is EKS Auto Mode plus `LoadBalancer` Services,
|
||||
which provisions AWS Network Load Balancers without installing an ingress
|
||||
controller.
|
||||
- Azure AKS:
|
||||
[guide](../../../docs/azure-aks-helm.md),
|
||||
[`examples/values.azure-ingress.yaml`](examples/values.azure-ingress.yaml).
|
||||
The recommended first Azure path is VNet-first AKS application routing plus
|
||||
Azure Database for MySQL Flexible Server private access, with
|
||||
`ingress.enabled=true`.
|
||||
- Google Cloud GKE:
|
||||
[guide](../../../docs/gcp-gke-helm.md),
|
||||
[`examples/values.gcp-ingress.yaml`](examples/values.gcp-ingress.yaml).
|
||||
The recommended first GCP path is GKE Ingress with a reserved global IP,
|
||||
Google-managed certificate, and BackendConfig health checks.
|
||||
|
||||
`ingress.enabled=true` only emits Kubernetes `Ingress` resources; it does not
|
||||
install an ingress controller. Use it only when the cluster already has a
|
||||
compatible provider ingress controller.
|
||||
|
||||
Published self-host planning pages:
|
||||
|
||||
- [Private network deployment](../../../packages/docs/start-here/private-network-deployment.mdx)
|
||||
- [Air-gapped deployment](../../../packages/docs/start-here/air-gapped-deployment.mdx)
|
||||
- [Installer delivery](../../../packages/docs/start-here/installer-delivery.mdx)
|
||||
- [Certificate trust and proxies](../../../packages/docs/start-here/certificate-trust-and-proxies.mdx)
|
||||
|
||||
## Secrets
|
||||
|
||||
The chart can create an Opaque Secret from `secret.values`, or consume an existing Secret:
|
||||
|
||||
```yaml
|
||||
secret:
|
||||
create: false
|
||||
existingSecret: openwork-ee-secrets
|
||||
```
|
||||
|
||||
The existing Secret must contain the keys listed under `secret.keys`, especially:
|
||||
|
||||
- `DATABASE_URL`
|
||||
- `BETTER_AUTH_SECRET`
|
||||
- `DEN_DB_ENCRYPTION_KEY`
|
||||
|
||||
Set optional `DATABASE_REDIS_URL` to enable Den API Redis-backed session and query caching. Set `DAYTONA_API_KEY` when `config.provisioner.mode` is `daytona`. Set `POLAR_ACCESS_TOKEN` when Polar feature gating is enabled. Set `OPENROUTER_MANAGEMENT_API_KEY` when enabling OpenWork Models management.
|
||||
|
||||
Redis cache examples:
|
||||
|
||||
```yaml
|
||||
secret:
|
||||
values:
|
||||
databaseRedisUrl: "rediss://redis-master.openwork.svc.cluster.local:6379"
|
||||
```
|
||||
|
||||
Prefer `rediss://`. For hosting platforms that only provide a private internal
|
||||
`redis://` URL, such as Render internal Redis, explicitly acknowledge the trust
|
||||
boundary with `redis.allowInsecureInternal=true`. Use this only when the Redis
|
||||
endpoint is non-public and reachable only from trusted services in the private
|
||||
network.
|
||||
|
||||
```yaml
|
||||
redis:
|
||||
allowInsecureInternal: true
|
||||
secret:
|
||||
values:
|
||||
databaseRedisUrl: "redis://red-...:6379"
|
||||
```
|
||||
|
||||
## Custom CA certificates
|
||||
|
||||
For higher-level planning across desktop, sidecar, Den, and MySQL trust
|
||||
surfaces, see the published
|
||||
[Certificate trust and proxies](../../../packages/docs/start-here/certificate-trust-and-proxies.mdx)
|
||||
page. This section remains the authoritative chart values reference.
|
||||
|
||||
Use `customCa` when OpenWork must trust a private certificate authority for
|
||||
strict TLS verification, such as a MySQL endpoint signed by an internal or cloud
|
||||
private CA. The chart does not accept PEM material in values and does not create
|
||||
the CA resource for you; create the Kubernetes Secret or ConfigMap in the
|
||||
release namespace before running `helm install` or `helm upgrade`.
|
||||
|
||||
Secret example:
|
||||
|
||||
```bash
|
||||
kubectl create secret generic openwork-custom-ca \
|
||||
--namespace openwork \
|
||||
--from-file=ca.crt=./corp-root-ca.pem
|
||||
```
|
||||
|
||||
```yaml
|
||||
customCa:
|
||||
enabled: true
|
||||
existingSecret: openwork-custom-ca
|
||||
existingConfigMap: ""
|
||||
key: ca.crt
|
||||
```
|
||||
|
||||
ConfigMap example:
|
||||
|
||||
```bash
|
||||
kubectl create configmap openwork-custom-ca \
|
||||
--namespace openwork \
|
||||
--from-file=ca.crt=./corp-root-ca.pem
|
||||
```
|
||||
|
||||
```yaml
|
||||
customCa:
|
||||
enabled: true
|
||||
existingSecret: ""
|
||||
existingConfigMap: openwork-custom-ca
|
||||
key: ca.crt
|
||||
```
|
||||
|
||||
When enabled, set exactly one of `existingSecret` or `existingConfigMap`, and set
|
||||
`key` to the data key containing the CA bundle. The chart mounts only that key as
|
||||
`/etc/openwork/custom-ca/ca-bundle.pem` and sets `NODE_EXTRA_CA_CERTS` to that
|
||||
file for `den-api`, `den-web`, enabled `inference`, and the migration Job. Do
|
||||
not also set `denApi.env.NODE_EXTRA_CA_CERTS`, `denWeb.env.NODE_EXTRA_CA_CERTS`,
|
||||
or `inference.env.NODE_EXTRA_CA_CERTS`; Helm rejects those conflicts while
|
||||
`customCa.enabled=true`.
|
||||
|
||||
For strict MySQL TLS verification, pair the mounted CA with a verifying
|
||||
`DATABASE_URL`, for example:
|
||||
|
||||
```yaml
|
||||
secret:
|
||||
values:
|
||||
databaseUrl: "mysql://openwork:REPLACE_DB_PASSWORD@mysql.example.internal:3306/openwork_den?sslmode=verify-full"
|
||||
```
|
||||
|
||||
`sslmode=verify-ca`, `sslmode=verify-full`, and `sslaccept=strict` enable strict
|
||||
certificate verification. `sslaccept=accept` keeps TLS enabled but does not
|
||||
verify the certificate chain, so use it only for smoke tests or while preparing
|
||||
the CA bundle.
|
||||
|
||||
The custom CA is release-wide for Node.js processes in this chart. Treat it as a
|
||||
global trust decision for outbound TLS from those workloads, and include only CA
|
||||
roots your OpenWork deployment should trust. On CA rotation, update the existing
|
||||
Secret or ConfigMap and restart the running workloads so Node reloads the CA
|
||||
file, for example:
|
||||
|
||||
```bash
|
||||
kubectl rollout restart deployment/openwork-ee-den-api --namespace openwork
|
||||
kubectl rollout restart deployment/openwork-ee-den-web --namespace openwork
|
||||
kubectl rollout restart deployment/openwork-ee-inference --namespace openwork
|
||||
```
|
||||
|
||||
The next migration hook Job will mount the current CA data; rerun a failed
|
||||
upgrade after the CA resource is corrected.
|
||||
|
||||
## Observability
|
||||
|
||||
The chart exposes first-class runtime observability settings for `den-api` and
|
||||
`den-web` only. `observability.backend` defaults to `none`; set it to `otel` or
|
||||
`sentry` to enable the matching runtime environment. The chart injects distinct
|
||||
`OTEL_SERVICE_NAME` values directly into each Deployment, so the shared
|
||||
ConfigMap is not used for service identity or auth-like observability values.
|
||||
|
||||
OpenTelemetry uses OTLP over `http/protobuf`, with a shared endpoint and
|
||||
optional per-signal endpoint overrides. Per-signal exporters default to `otlp`,
|
||||
and trace sampling defaults to the standard parent-based always-on sampler.
|
||||
|
||||
### OpenTelemetry quick start
|
||||
|
||||
Before starting, you need:
|
||||
|
||||
- An OpenTelemetry Collector or vendor endpoint reachable from the Kubernetes
|
||||
cluster over OTLP HTTP. Port `4318` is the usual Collector port.
|
||||
- The endpoint's authentication token or headers, if it requires
|
||||
authentication.
|
||||
- `kubectl` and Helm configured for the target cluster.
|
||||
|
||||
The chart configures telemetry export from OpenWork; it does not install an
|
||||
OpenTelemetry Collector. For an in-cluster Collector, use its Kubernetes DNS
|
||||
name, for example
|
||||
`http://otel-collector.observability.svc.cluster.local:4318`. Do not use
|
||||
`localhost`, because that would refer to the OpenWork container itself.
|
||||
|
||||
First create the namespace used by this example:
|
||||
|
||||
```bash
|
||||
kubectl create namespace openwork
|
||||
```
|
||||
|
||||
If the Collector does not require authentication, skip the Secret and leave
|
||||
`observability.otel.headers.existingSecret` empty.
|
||||
|
||||
If it requires a bearer token, create the header Secret in the **same
|
||||
namespace as OpenWork**:
|
||||
|
||||
```bash
|
||||
kubectl create secret generic openwork-otel-headers \
|
||||
--namespace openwork \
|
||||
--from-literal=OTEL_EXPORTER_OTLP_HEADERS='Authorization=Bearer <token>'
|
||||
```
|
||||
|
||||
Replace `<token>` with the real token. Keep the single quotes so your shell
|
||||
passes the complete header as one value. To update an existing Secret without
|
||||
deleting it first, use:
|
||||
|
||||
```bash
|
||||
kubectl create secret generic openwork-otel-headers \
|
||||
--namespace openwork \
|
||||
--from-literal=OTEL_EXPORTER_OTLP_HEADERS='Authorization=Bearer <token>' \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
```
|
||||
|
||||
Multiple OTLP headers use the standard comma-separated `key=value` format:
|
||||
|
||||
```bash
|
||||
kubectl create secret generic openwork-otel-headers \
|
||||
--namespace openwork \
|
||||
--from-literal=OTEL_EXPORTER_OTLP_HEADERS='Authorization=Bearer <token>,x-scope-orgid=<tenant>'
|
||||
```
|
||||
|
||||
Do not put tokens directly in a values file. Kubernetes Secrets are not
|
||||
encrypted by default unless your cluster enables encryption at rest, so use
|
||||
your organization's external-secret or secret-management system in production
|
||||
when available.
|
||||
|
||||
Create `values-observability.yaml`:
|
||||
|
||||
```yaml
|
||||
observability:
|
||||
backend: otel
|
||||
serviceNames:
|
||||
denApi: openwork-den-api
|
||||
denWeb: openwork-den-web
|
||||
otel:
|
||||
endpoint: "http://otel-collector.observability.svc.cluster.local:4318"
|
||||
tracesEndpoint: ""
|
||||
metricsEndpoint: ""
|
||||
logsEndpoint: ""
|
||||
exporters:
|
||||
traces: otlp
|
||||
metrics: otlp
|
||||
logs: otlp
|
||||
tracesSampler: parentbased_always_on
|
||||
tracesSamplerArg: ""
|
||||
headers:
|
||||
existingSecret: openwork-otel-headers
|
||||
key: OTEL_EXPORTER_OTLP_HEADERS
|
||||
```
|
||||
|
||||
For a Collector without authentication, use:
|
||||
|
||||
```yaml
|
||||
headers:
|
||||
existingSecret: ""
|
||||
key: OTEL_EXPORTER_OTLP_HEADERS
|
||||
```
|
||||
|
||||
Install or upgrade OpenWork with the values file:
|
||||
|
||||
```bash
|
||||
helm upgrade --install openwork-ee ./packaging/helm/openwork-ee \
|
||||
--namespace openwork \
|
||||
--create-namespace \
|
||||
--values values-observability.yaml
|
||||
```
|
||||
|
||||
`observability.otel.headers.existingSecret` must name an existing Kubernetes
|
||||
Secret. Its key is exposed as `OTEL_EXPORTER_OTLP_HEADERS` only on `den-api` and
|
||||
`den-web`; it is not added to inference pods or migration Jobs.
|
||||
|
||||
### Verify the OpenTelemetry setup
|
||||
|
||||
The commands below assume the Helm release is named `openwork-ee`. If you use a
|
||||
different release name, run `kubectl get deployments,services --namespace
|
||||
openwork` to find the generated resource names.
|
||||
|
||||
Confirm that the workloads are ready:
|
||||
|
||||
```bash
|
||||
kubectl get pods --namespace openwork
|
||||
kubectl rollout status deployment/openwork-ee-den-api --namespace openwork
|
||||
kubectl rollout status deployment/openwork-ee-den-web --namespace openwork
|
||||
```
|
||||
|
||||
Inspect the rendered environment references without printing the Secret's
|
||||
value:
|
||||
|
||||
```bash
|
||||
kubectl describe deployment/openwork-ee-den-api --namespace openwork
|
||||
kubectl describe deployment/openwork-ee-den-web --namespace openwork
|
||||
```
|
||||
|
||||
Look for `DEN_OBSERVABILITY_BACKEND=otel`, distinct `OTEL_SERVICE_NAME` values,
|
||||
the OTLP endpoint, and an `OTEL_EXPORTER_OTLP_HEADERS` reference to
|
||||
`openwork-otel-headers`.
|
||||
|
||||
Generate a request that crosses both services. Keep this port-forward running:
|
||||
|
||||
```bash
|
||||
kubectl port-forward service/openwork-ee-den-web 3005:3005 --namespace openwork
|
||||
```
|
||||
|
||||
In another terminal:
|
||||
|
||||
```bash
|
||||
curl --fail --silent --show-error \
|
||||
http://127.0.0.1:3005/api/den/openapi.json >/dev/null
|
||||
```
|
||||
|
||||
Your observability backend should show `openwork-den-web` and
|
||||
`openwork-den-api`, with one connected trace for the request. Logs from both
|
||||
services carry trace and span IDs. Den API also exports Hono request-duration
|
||||
and active-request metrics.
|
||||
|
||||
### Endpoint and troubleshooting notes
|
||||
|
||||
- `observability.otel.endpoint` is a base endpoint. OpenWork appends
|
||||
`/v1/traces`, `/v1/metrics`, and `/v1/logs`.
|
||||
- Signal-specific endpoints are used exactly as written. Include the full
|
||||
signal path, such as `https://collector.example.com/v1/traces`.
|
||||
- Only OTLP HTTP/protobuf is supported. Port `4317` is normally OTLP gRPC and
|
||||
will not work; use the HTTP receiver, usually port `4318`.
|
||||
- The Secret must be in the OpenWork release namespace, and its key must match
|
||||
`observability.otel.headers.key` exactly.
|
||||
- A `401` or `403` exporter error usually means the token or header syntax is
|
||||
wrong. A connection error usually means the endpoint is not reachable from
|
||||
the pod or a NetworkPolicy blocks it.
|
||||
- After changing an externally managed Secret, restart the deployments if your
|
||||
secret controller does not trigger a rollout:
|
||||
|
||||
```bash
|
||||
kubectl rollout restart deployment/openwork-ee-den-api --namespace openwork
|
||||
kubectl rollout restart deployment/openwork-ee-den-web --namespace openwork
|
||||
```
|
||||
- For lower production trace volume, use
|
||||
`tracesSampler: parentbased_traceidratio` with `tracesSamplerArg: "0.1"` to
|
||||
sample approximately ten percent of root traces.
|
||||
|
||||
For Sentry runtime capture, configure the DSN directly or through an existing
|
||||
Secret. Helm runtime pods intentionally do not receive `SENTRY_AUTH_TOKEN`,
|
||||
`SENTRY_ORG`, `SENTRY_PROJECT`, or `SENTRY_URL`; those are build-time source-map
|
||||
upload settings, not runtime settings.
|
||||
|
||||
```yaml
|
||||
observability:
|
||||
backend: sentry
|
||||
sentry:
|
||||
dsnSecret:
|
||||
existingSecret: openwork-sentry-runtime
|
||||
key: SENTRY_DSN
|
||||
tracesSampleRate: "0.01"
|
||||
environment: production
|
||||
release: "2026.07.11"
|
||||
```
|
||||
|
||||
Sentry Logs default to warning-and-error only through `SENTRY_LOG_LEVEL=warn`.
|
||||
Set `SENTRY_LOG_LEVEL=info` only during short debugging windows if you need
|
||||
successful request logs in Sentry; stdout JSON logs remain available either way.
|
||||
|
||||
Sentry source-map upload is build-time behavior. Helm configures runtime pods
|
||||
after images already exist, so it cannot retroactively upload source maps for
|
||||
Vercel or CI builds. Set `SENTRY_AUTH_TOKEN`, `SENTRY_ORG`, `SENTRY_PROJECT`,
|
||||
and `SENTRY_URL` in the build environment that creates the image (for example,
|
||||
Vercel project build environment variables), not in Helm values or the chart
|
||||
ConfigMap. The generic published images cannot upload source maps after they
|
||||
are built; build your own image with CI/BuildKit source-map secrets when you
|
||||
need uploaded artifacts. `packaging/docker/Dockerfile.den-web` accepts optional
|
||||
BuildKit secret IDs `sentry_auth_token`, `sentry_org`, `sentry_project`,
|
||||
`sentry_url`, `sentry_release`, and `sentry_dist`; the EE image publish workflow
|
||||
wires these IDs from GitHub Secrets when present.
|
||||
|
||||
## GitHub Connector
|
||||
|
||||
The GitHub repository connector uses a GitHub App. It is separate from GitHub
|
||||
OAuth social sign-in. Follow the full setup guide in
|
||||
[`packages/docs/start-here/github-connector-helm.mdx`](../../../packages/docs/start-here/github-connector-helm.mdx).
|
||||
|
||||
Use these public URLs when creating the GitHub App:
|
||||
|
||||
- Setup URL: `https://openwork.example.com/dashboard/integrations/github`
|
||||
- Webhook URL: `https://api.openwork.example.com/v1/webhooks/connectors/github`
|
||||
|
||||
Then set the chart values:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
githubConnector:
|
||||
appId: "123456"
|
||||
clientId: "Iv1.example"
|
||||
|
||||
secret:
|
||||
values:
|
||||
githubConnectorAppClientSecret: "github-app-client-secret-if-used"
|
||||
githubConnectorAppPrivateKey: |-
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
...
|
||||
-----END PRIVATE KEY-----
|
||||
githubConnectorAppWebhookSecret: "replace-with-the-github-webhook-secret"
|
||||
```
|
||||
|
||||
The chart exposes these to Den API as:
|
||||
|
||||
- `GITHUB_CONNECTOR_APP_ID`
|
||||
- `GITHUB_CONNECTOR_APP_CLIENT_ID`
|
||||
- `GITHUB_CONNECTOR_APP_CLIENT_SECRET`
|
||||
- `GITHUB_CONNECTOR_APP_PRIVATE_KEY`
|
||||
- `GITHUB_CONNECTOR_APP_WEBHOOK_SECRET`
|
||||
|
||||
If `secret.create=false`, add the three secret-backed keys to the existing
|
||||
Secret referenced by `secret.existingSecret`. The app ID and client ID come from
|
||||
the chart ConfigMap.
|
||||
|
||||
## Transactional Email
|
||||
|
||||
Den API can send transactional email through SMTP. Configure the SMTP values in
|
||||
the chart Secret:
|
||||
|
||||
```yaml
|
||||
secret:
|
||||
values:
|
||||
emailFrom: "OpenWork <no-reply@example.com>"
|
||||
smtpHost: "smtp.example.com"
|
||||
smtpPort: "587"
|
||||
smtpUser: "openwork@example.com"
|
||||
smtpPass: "REPLACE_ME"
|
||||
smtpSecure: "false"
|
||||
```
|
||||
|
||||
These values are exposed to Den API as:
|
||||
|
||||
- `EMAIL_FROM`
|
||||
- `SMTP_HOST`
|
||||
- `SMTP_PORT`
|
||||
- `SMTP_USER`
|
||||
- `SMTP_PASS`
|
||||
- `SMTP_SECURE`
|
||||
|
||||
If `secret.create=false`, add those keys to the existing Secret referenced by
|
||||
`secret.existingSecret`. SMTP delivery requires both `EMAIL_FROM` and
|
||||
`SMTP_HOST`; leave `smtpHost` blank only when SMTP-backed transactional email
|
||||
should be disabled.
|
||||
|
||||
## Tenancy Mode
|
||||
|
||||
The chart defaults to a private single-org deployment:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
tenancy:
|
||||
mode: "single_org"
|
||||
singleOrgName: "OpenWork"
|
||||
singleOrgSlug: "default"
|
||||
ownerEmails: "admin@example.com"
|
||||
allowPublicSignup: "false"
|
||||
requireEmailVerification: "false"
|
||||
```
|
||||
|
||||
These values are exposed to both `den-api` and `den-web` as:
|
||||
|
||||
- `DEN_ORG_MODE`
|
||||
- `DEN_SINGLE_ORG_NAME`
|
||||
- `DEN_SINGLE_ORG_SLUG`
|
||||
- `DEN_SINGLE_ORG_OWNER_EMAILS`
|
||||
- `DEN_SINGLE_ORG_ALLOW_PUBLIC_SIGNUP`
|
||||
- `DEN_REQUIRE_EMAIL_VERIFICATION`
|
||||
|
||||
In the implemented target state, blank or unset `DEN_ORG_MODE` is treated as `single_org`. The Helm chart sets it explicitly to make rendered manifests clear. Hosted or cloud-style multi-organization deployments should set:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
tenancy:
|
||||
mode: "multi_org"
|
||||
requireEmailVerification: "true"
|
||||
```
|
||||
|
||||
`config.tenancy.ownerEmails` controls who can claim ownership of the singleton deployment organization. `config.public.bootstrapAdminEmails` is separate: it seeds platform/admin allowlist access and does not by itself make a user the singleton organization owner.
|
||||
|
||||
## Initial Organization Setup
|
||||
|
||||
For self-hosted installs, configure the singleton organization before the first
|
||||
user signs in:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
tenancy:
|
||||
mode: "single_org"
|
||||
singleOrgName: "Acme"
|
||||
singleOrgSlug: "acme"
|
||||
ownerEmails: "admin@acme.com"
|
||||
requireEmailVerification: "false"
|
||||
public:
|
||||
bootstrapAdminEmails: "admin@acme.com"
|
||||
```
|
||||
|
||||
For releases that include initial-administrator bootstrap, inject the
|
||||
release-documented one-time setup secret through the chart's Secret integration.
|
||||
Do not put the setup code in Helm values, a ConfigMap, source control, logs, or a
|
||||
PR. After the release is installed and the web host is reachable, open `/setup`,
|
||||
enter an email configured in `config.tenancy.ownerEmails`, and verify it with the
|
||||
one-time operator code. Den then creates the Better Auth account, creates or
|
||||
claims the singleton organization identified by `singleOrgName` and
|
||||
`singleOrgSlug`, grants owner and configured platform-admin access, and consumes
|
||||
the setup claim. The code cannot be reused and public signup remains disabled.
|
||||
|
||||
Configuring `ownerEmails` or `bootstrapAdminEmails` does not create an account or
|
||||
password. `ownerEmails` controls singleton-organization ownership;
|
||||
`bootstrapAdminEmails` controls platform-admin allowlist access. Configure the
|
||||
initial administrator in both lists when that person needs both roles.
|
||||
|
||||
Chart versions without the `/setup` route do not support this private bootstrap
|
||||
flow. Upgrade to a release that includes initial-administrator bootstrap before
|
||||
attempting first-user setup; entering the configured email on the normal sign-in
|
||||
page cannot create the account and no default administrator password exists.
|
||||
|
||||
Later users are attached to the same singleton organization. They do not see an
|
||||
organization creation step, and attempts to create another organization return a
|
||||
single-org-mode error. If no eligible initial-administrator email is configured,
|
||||
the private setup flow remains unavailable; it never falls back to allowing an
|
||||
arbitrary first visitor to claim ownership.
|
||||
|
||||
For most production installs, use this first owner account as the break-glass
|
||||
setup path, then configure SAML/OIDC SSO and SCIM from the organization
|
||||
settings. Keep `bootstrapAdminEmails` aligned only if that same person should
|
||||
also have platform/admin allowlist access; it is not a replacement for
|
||||
`ownerEmails`.
|
||||
|
||||
After SAML/OIDC SSO is configured on the singleton organization, the auth
|
||||
experience becomes SSO-only: root sign-in and sign-up show one "Continue with
|
||||
SSO" action, other sign-in/sign-up entry points redirect there, and raw
|
||||
email/password sign-in or sign-up requests are rejected by Den API.
|
||||
|
||||
## Internal Service URLs
|
||||
|
||||
By default, the chart wires internal services through Kubernetes DNS:
|
||||
|
||||
- `DEN_API_BASE=http://<release>-openwork-ee-den-api:8788`
|
||||
- `DEN_AUTH_FALLBACK_BASE=http://<release>-openwork-ee-den-api:8788`
|
||||
- `INFERENCE_PROXY_BASE_URL=http://<release>-openwork-ee-inference:8791` when `inference.enabled=true`
|
||||
|
||||
Override `config.internal.*` only when routing through a mesh, gateway, or external service.
|
||||
|
||||
## Den API Node Options
|
||||
|
||||
Set `config.denApiNodeOptions` to pass Node.js runtime flags to `den-api` through
|
||||
`NODE_OPTIONS` when the container starts. The configured value is stored in the
|
||||
chart ConfigMap as `DEN_API_NODE_OPTIONS` and defaults to an empty string.
|
||||
Existing values files that set `denApi.env.NODE_OPTIONS` remain supported and
|
||||
take precedence, so upgrading does not require changing that configuration.
|
||||
|
||||
```yaml
|
||||
config:
|
||||
denApiNodeOptions: "--max-old-space-size=4096"
|
||||
```
|
||||
|
||||
`--use-openssl-ca` only changes how Node reads operating-system trust. It does
|
||||
not create or mount a private CA bundle into the container; use `customCa` for
|
||||
that.
|
||||
|
||||
## Service Exposure
|
||||
|
||||
Each service supports Kubernetes Service metadata and load balancer settings:
|
||||
|
||||
```yaml
|
||||
denWeb:
|
||||
service:
|
||||
type: LoadBalancer
|
||||
port: 443
|
||||
loadBalancerClass: eks.amazonaws.com/nlb
|
||||
loadBalancerSourceRanges:
|
||||
- 203.0.113.0/24
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
|
||||
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: arn:aws:acm:...
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
|
||||
```
|
||||
|
||||
The same shape is available under `denApi.service` and `inference.service`.
|
||||
`ingress.enabled=true` only emits Kubernetes `Ingress` resources; it does not
|
||||
install an ingress controller.
|
||||
|
||||
## Config Rollouts
|
||||
|
||||
Den API, Den Web, and inference pods include checksums for the chart-managed
|
||||
ConfigMap and Secret. Helm upgrades that change runtime config or secrets roll
|
||||
the pods automatically so environment variables such as public origins, CORS
|
||||
origins, and database URLs are refreshed.
|
||||
|
||||
## Isolated Networks
|
||||
|
||||
Use the published [Air-gapped deployment](../../../packages/docs/start-here/air-gapped-deployment.mdx)
|
||||
and [Outbound network access](../../../packages/docs/start-here/outbound-network-access.mdx)
|
||||
pages for customer-facing isolation and allowlist planning. The values below are
|
||||
the chart-level controls.
|
||||
|
||||
The chart disables external password breach screening by default so isolated self-hosted installs do not depend on the Have I Been Pwned Pwned Passwords range API. If your deployment has approved outbound access and you want password creation and reset to reject known-compromised passwords through that service, enable it:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
auth:
|
||||
passwordBreachScreeningEnabled: "true"
|
||||
```
|
||||
|
||||
Local sign-in lockout protections stay enabled either way.
|
||||
|
||||
Den API also protects External MCP connection URLs with an SSRF guard. On a
|
||||
hosted multi-tenant deployment, someone who can add an MCP connection could
|
||||
otherwise make Den fetch localhost, private-network services, or cloud metadata
|
||||
addresses from Den's network position.
|
||||
|
||||
If a legitimate internal MCP server is blocked in an isolated private-network
|
||||
deployment, the diagnostic code is `MCP_URL_BLOCKED` and the operator-facing
|
||||
message is: "Use a public HTTPS MCP URL or change the deployment's
|
||||
private-network policy through security review."
|
||||
|
||||
To allow private-address MCP servers for Den API, enable:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
public:
|
||||
allowPrivateMcpUrls: "1"
|
||||
```
|
||||
|
||||
This disables SSRF protection for external MCP connection URLs. Enable it only
|
||||
on a deployment where Den's network position and the set of people who can add
|
||||
MCP connections are both trusted.
|
||||
|
||||
## Migrations
|
||||
|
||||
The migration Job runs as a Helm `pre-install,pre-upgrade` hook by default:
|
||||
|
||||
```yaml
|
||||
migrations:
|
||||
enabled: true
|
||||
hook: true
|
||||
hookDeletePolicy: before-hook-creation,hook-succeeded
|
||||
command:
|
||||
- node
|
||||
args:
|
||||
- /app/ee/packages/den-db/dist/scripts/bootstrap.js
|
||||
```
|
||||
|
||||
The default hook executes the precompiled Den DB bootstrap runner already built into the Den API image. On a completely empty database it applies the build-time current-schema SQL snapshot, records the committed migrations as the baseline, then runs pending migrations with Drizzle ORM. On an existing schema without a Drizzle ledger, it records the baseline before migrating.
|
||||
|
||||
For retained-log troubleshooting, temporarily disable hook behavior and reduce
|
||||
retries:
|
||||
|
||||
```yaml
|
||||
migrations:
|
||||
enabled: true
|
||||
hook: false
|
||||
backoffLimit: 0
|
||||
```
|
||||
|
||||
The hook Job currently renders `DATABASE_URL` and `DEN_DB_ENCRYPTION_KEY` into
|
||||
the Job environment when `secret.create=true`, because pre-install hooks run
|
||||
before normal chart resources. Avoid sharing `kubectl describe job` output
|
||||
without redacting secrets.
|
||||
|
||||
## Install links
|
||||
|
||||
The migration Job creates the `install_link` and `desktop_connect_grant` tables
|
||||
automatically when `migrations.enabled=true`. Hosted deployments that enable
|
||||
install-link gating must opt organizations in through `/admin`; self-hosted
|
||||
deployments default to enabled. See the
|
||||
[operator guide](../../../docs/org-install-links.md) and the published
|
||||
[Installer delivery](../../../packages/docs/start-here/installer-delivery.mdx)
|
||||
page.
|
||||
|
||||
Optional installer artifact values:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
public:
|
||||
installerReleaseTag: "v0.17.9"
|
||||
installerReleaseRepo: "different-ai/openwork"
|
||||
|
||||
installerArtifacts:
|
||||
enabled: true
|
||||
existingClaim: openwork-desktop-artifacts
|
||||
mountPath: /var/lib/openwork/installer-artifacts
|
||||
```
|
||||
|
||||
Use either `installerArtifacts.existingClaim` or `installerArtifacts.hostPath`,
|
||||
not both.
|
||||
|
||||
### Guided desktop setup
|
||||
|
||||
The organization download page hands the normal OpenWork app its Den
|
||||
configuration in an explicit second step. The default is a short-lived,
|
||||
single-use HTTPS exchange and needs no key configuration:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
public:
|
||||
connectLinkMode: exchange
|
||||
```
|
||||
|
||||
Den validates the install token and then either:
|
||||
|
||||
- streams the standard installer already mounted at
|
||||
`installerArtifacts.mountPath`; or
|
||||
- redirects the browser directly to the exact configured GitHub release asset.
|
||||
|
||||
Den does not download, cache, wrap, or ZIP GitHub artifacts. The organization
|
||||
setup stays in the **Open OpenWork** deep-link step after installation.
|
||||
|
||||
For an optional signed handoff, explicitly select signed mode and configure a
|
||||
dedicated Ed25519 key whose public key is already trusted by the desktop build:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
public:
|
||||
connectLinkMode: signed
|
||||
connectLinkKeyId: "owc-2026-07"
|
||||
|
||||
secret:
|
||||
values:
|
||||
connectLinkPrivateKey: |-
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
...
|
||||
-----END PRIVATE KEY-----
|
||||
```
|
||||
|
||||
For an existing Secret, put the private key under the key named by
|
||||
`secret.keys.connectLinkPrivateKey` (default `DEN_CONNECT_LINK_PRIVATE_KEY`).
|
||||
`scripts/generate-connect-link-keypair.mjs` can generate a pair, but a standard
|
||||
desktop build will reject it until the matching public key ships in that build.
|
||||
|
||||
For a semi-air-gapped deployment, mount these normal release filenames (where
|
||||
`<version>` is `installerReleaseTag` without its leading `v`):
|
||||
|
||||
- `openwork-mac-arm64-<version>.dmg`
|
||||
- `openwork-mac-x64-<version>.dmg`
|
||||
- `openwork-win-x64-<version>.exe`
|
||||
- `openwork-linux-x86_64-<version>.AppImage`
|
||||
- `openwork-linux-arm64-<version>.AppImage`
|
||||
|
||||
Without mounted artifacts, client networks must permit the configured GitHub
|
||||
release URL and GitHub's redirected release-asset host. With mounted artifacts,
|
||||
the browser only talks to Den. Use a shared read-only PVC when
|
||||
`denApi.replicaCount` is greater than one. Connection grants are stored and
|
||||
consumed in MySQL, so the guided flow is safe when preview and acceptance land
|
||||
on different API replicas.
|
||||
|
||||
## Health Probes
|
||||
|
||||
The chart uses the existing service health endpoints:
|
||||
|
||||
- `den-api`: `GET /health`
|
||||
- `den-web`: `GET /api/health`
|
||||
- `inference`: `GET /health`
|
||||
|
||||
Readiness probes use dependency-aware endpoints:
|
||||
|
||||
- `den-api`: `GET /ready`
|
||||
- `den-web`: `GET /api/ready`
|
||||
- `inference`: `GET /ready`
|
||||
|
||||
## Worker Provisioning Recovery
|
||||
|
||||
`den-api` periodically reconciles cloud workers that remain in `provisioning` beyond `config.provisioner.reconcileStaleMs`. This lets a replacement pod resume provisioning after a crash. Keep `denApi.replicaCount: 1` unless your worker provider operations are idempotent or you add external leader election.
|
||||
@@ -0,0 +1,88 @@
|
||||
# AWS EKS Auto Mode HTTP smoke-test values for a first OpenWork EE install when
|
||||
# DNS and ACM TLS are not ready yet.
|
||||
#
|
||||
# Copy this file, replace every REPLACE_* value, and install with:
|
||||
#
|
||||
# helm upgrade --install openwork-ee oci://ghcr.io/different-ai/charts/openwork-ee \
|
||||
# --version REPLACE_OPENWORK_VERSION \
|
||||
# --namespace openwork-ee \
|
||||
# --create-namespace \
|
||||
# -f values.aws-http-smoke.yaml
|
||||
#
|
||||
# This creates two public AWS Network Load Balancers on clear HTTP ports. After
|
||||
# the first successful install, replace the pending origins with the real NLB
|
||||
# hostnames, run helm upgrade again, and then validate browser auth. Prefer the
|
||||
# HTTPS/ACM example for production.
|
||||
|
||||
image:
|
||||
tag: "REPLACE_OPENWORK_VERSION"
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
config:
|
||||
tenancy:
|
||||
mode: single_org
|
||||
singleOrgName: "REPLACE_COMPANY_NAME"
|
||||
singleOrgSlug: "REPLACE_COMPANY_SLUG"
|
||||
ownerEmails: "REPLACE_ADMIN_EMAIL"
|
||||
allowPublicSignup: "false"
|
||||
requireEmailVerification: "false"
|
||||
auth:
|
||||
passwordBreachScreeningEnabled: "false"
|
||||
public:
|
||||
webOrigin: "http://pending-web-nlb:3005"
|
||||
apiOrigin: "http://pending-api-nlb:8788"
|
||||
mcpResourceUrl: "http://pending-api-nlb:8788/mcp"
|
||||
mcpClaimNamespace: "http://pending-web-nlb:3005"
|
||||
desktopDenBaseUrl: "http://pending-web-nlb:3005"
|
||||
corsOrigins: "http://pending-web-nlb:3005,http://pending-api-nlb:8788"
|
||||
betterAuthTrustedOrigins: "http://pending-web-nlb:3005"
|
||||
webAppHosts: "pending-web-nlb"
|
||||
bootstrapAdminEmails: "REPLACE_ADMIN_EMAIL"
|
||||
authCallbackUrl: "http://pending-web-nlb:3005"
|
||||
provisioner:
|
||||
mode: stub
|
||||
|
||||
secret:
|
||||
create: true
|
||||
values:
|
||||
# RDS MySQL private-network smoke path. sslaccept=accept preserves TLS but
|
||||
# does not require the RDS CA chain to be mounted into the image.
|
||||
databaseUrl: "mysql://openwork:REPLACE_DB_PASSWORD@REPLACE_RDS_ENDPOINT:3306/openwork_den?sslaccept=accept"
|
||||
# Generate with: openssl rand -base64 48
|
||||
betterAuthSecret: "REPLACE_BETTER_AUTH_SECRET"
|
||||
# Generate separately with: openssl rand -base64 48
|
||||
denDbEncryptionKey: "REPLACE_DEN_DB_ENCRYPTION_KEY"
|
||||
|
||||
denApi:
|
||||
replicaCount: 1
|
||||
service:
|
||||
type: LoadBalancer
|
||||
port: 8788
|
||||
loadBalancerClass: eks.amazonaws.com/nlb
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
|
||||
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
|
||||
service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol: HTTP
|
||||
service.beta.kubernetes.io/aws-load-balancer-healthcheck-path: /ready
|
||||
|
||||
denWeb:
|
||||
replicaCount: 1
|
||||
service:
|
||||
type: LoadBalancer
|
||||
port: 3005
|
||||
loadBalancerClass: eks.amazonaws.com/nlb
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
|
||||
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
|
||||
service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol: HTTP
|
||||
service.beta.kubernetes.io/aws-load-balancer-healthcheck-path: /api/ready
|
||||
|
||||
inference:
|
||||
enabled: false
|
||||
|
||||
migrations:
|
||||
enabled: true
|
||||
hook: true
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
@@ -0,0 +1,94 @@
|
||||
# AWS EKS Auto Mode starter values for a first OpenWork EE self-host install.
|
||||
#
|
||||
# Copy this file, replace every REPLACE_* value, and install with:
|
||||
#
|
||||
# helm upgrade --install openwork-ee oci://ghcr.io/different-ai/charts/openwork-ee \
|
||||
# --version REPLACE_OPENWORK_VERSION \
|
||||
# --namespace openwork-ee \
|
||||
# --create-namespace \
|
||||
# -f values.aws.yaml
|
||||
#
|
||||
# This example exposes Den Web and Den API through separate AWS Network Load
|
||||
# Balancers created from Kubernetes Services. Use it for the first AWS smoke
|
||||
# test and for installs where separate web/API hostnames are acceptable.
|
||||
|
||||
image:
|
||||
tag: "REPLACE_OPENWORK_VERSION"
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
config:
|
||||
tenancy:
|
||||
mode: single_org
|
||||
singleOrgName: "REPLACE_COMPANY_NAME"
|
||||
singleOrgSlug: "REPLACE_COMPANY_SLUG"
|
||||
ownerEmails: "REPLACE_ADMIN_EMAIL"
|
||||
allowPublicSignup: "false"
|
||||
requireEmailVerification: "false"
|
||||
auth:
|
||||
passwordBreachScreeningEnabled: "false"
|
||||
public:
|
||||
webOrigin: "https://REPLACE_WEB_HOST"
|
||||
# Split-origin compatibility override for this two-load-balancer example.
|
||||
# Leave blank in single-origin deployments so Den derives it from webOrigin.
|
||||
apiOrigin: "https://REPLACE_API_HOST"
|
||||
mcpResourceUrl: "https://REPLACE_API_HOST/mcp"
|
||||
mcpClaimNamespace: "https://REPLACE_WEB_HOST"
|
||||
desktopDenBaseUrl: ""
|
||||
corsOrigins: "https://REPLACE_WEB_HOST,https://REPLACE_API_HOST"
|
||||
betterAuthTrustedOrigins: "https://REPLACE_WEB_HOST"
|
||||
webAppHosts: "REPLACE_WEB_HOST"
|
||||
bootstrapAdminEmails: "REPLACE_ADMIN_EMAIL"
|
||||
authCallbackUrl: "https://REPLACE_WEB_HOST"
|
||||
provisioner:
|
||||
mode: stub
|
||||
|
||||
secret:
|
||||
create: true
|
||||
values:
|
||||
# RDS MySQL private-network smoke path. sslaccept=accept preserves TLS but
|
||||
# does not require the RDS CA chain to be mounted into the image.
|
||||
databaseUrl: "mysql://openwork:REPLACE_DB_PASSWORD@REPLACE_RDS_ENDPOINT:3306/openwork_den?sslaccept=accept"
|
||||
# Generate with: openssl rand -base64 48
|
||||
betterAuthSecret: "REPLACE_BETTER_AUTH_SECRET"
|
||||
# Generate separately with: openssl rand -base64 48
|
||||
denDbEncryptionKey: "REPLACE_DEN_DB_ENCRYPTION_KEY"
|
||||
# One-time /setup code. Generate a unique value and keep it out of source control.
|
||||
initialAdminBootstrapCode: "REPLACE_BOOTSTRAP_CODE"
|
||||
|
||||
denApi:
|
||||
replicaCount: 1
|
||||
service:
|
||||
type: LoadBalancer
|
||||
port: 443
|
||||
loadBalancerClass: eks.amazonaws.com/nlb
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
|
||||
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
|
||||
service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol: HTTP
|
||||
service.beta.kubernetes.io/aws-load-balancer-healthcheck-path: /ready
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: REPLACE_ACM_CERT_ARN
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
|
||||
|
||||
denWeb:
|
||||
replicaCount: 1
|
||||
service:
|
||||
type: LoadBalancer
|
||||
port: 443
|
||||
loadBalancerClass: eks.amazonaws.com/nlb
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
|
||||
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
|
||||
service.beta.kubernetes.io/aws-load-balancer-healthcheck-protocol: HTTP
|
||||
service.beta.kubernetes.io/aws-load-balancer-healthcheck-path: /api/ready
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: REPLACE_ACM_CERT_ARN
|
||||
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
|
||||
|
||||
inference:
|
||||
enabled: false
|
||||
|
||||
migrations:
|
||||
enabled: true
|
||||
hook: true
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
@@ -0,0 +1,99 @@
|
||||
# Azure AKS starter values for a first OpenWork EE self-host install.
|
||||
#
|
||||
# Follow docs/azure-aks-helm.md to create the VNet, AKS subnet, delegated
|
||||
# MySQL subnet, AKS cluster, and Azure Database for MySQL Flexible Server.
|
||||
# Then copy this file, replace every REPLACE_* value, and install with:
|
||||
#
|
||||
# helm upgrade --install openwork-ee oci://ghcr.io/different-ai/charts/openwork-ee \
|
||||
# --version REPLACE_OPENWORK_VERSION \
|
||||
# --namespace openwork-ee \
|
||||
# --create-namespace \
|
||||
# -f values.azure.yaml
|
||||
#
|
||||
# This example exposes Den Web and Den API through the AKS application routing
|
||||
# add-on's managed NGINX Ingress controller. Use it for production-like Azure
|
||||
# tests where browser auth and SSO need stable HTTPS hostnames.
|
||||
|
||||
image:
|
||||
tag: "REPLACE_OPENWORK_VERSION"
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
config:
|
||||
tenancy:
|
||||
mode: single_org
|
||||
singleOrgName: "REPLACE_COMPANY_NAME"
|
||||
singleOrgSlug: "REPLACE_COMPANY_SLUG"
|
||||
ownerEmails: "REPLACE_ADMIN_EMAIL"
|
||||
allowPublicSignup: "false"
|
||||
requireEmailVerification: "false"
|
||||
auth:
|
||||
passwordBreachScreeningEnabled: "false"
|
||||
public:
|
||||
webOrigin: "https://REPLACE_WEB_HOST"
|
||||
# Split-origin compatibility override for this two-host ingress example.
|
||||
# Leave blank in single-origin deployments so Den derives it from webOrigin.
|
||||
apiOrigin: "https://REPLACE_API_HOST"
|
||||
mcpResourceUrl: "https://REPLACE_API_HOST/mcp"
|
||||
mcpClaimNamespace: "https://REPLACE_WEB_HOST"
|
||||
desktopDenBaseUrl: ""
|
||||
corsOrigins: "https://REPLACE_WEB_HOST,https://REPLACE_API_HOST"
|
||||
betterAuthTrustedOrigins: "https://REPLACE_WEB_HOST"
|
||||
webAppHosts: "REPLACE_WEB_HOST"
|
||||
bootstrapAdminEmails: "REPLACE_ADMIN_EMAIL"
|
||||
authCallbackUrl: "https://REPLACE_WEB_HOST"
|
||||
provisioner:
|
||||
mode: stub
|
||||
|
||||
secret:
|
||||
create: true
|
||||
values:
|
||||
# Azure Database for MySQL Flexible Server private-network smoke path.
|
||||
# sslaccept=accept preserves TLS without requiring a mounted CA bundle.
|
||||
databaseUrl: "mysql://openwork:REPLACE_DB_PASSWORD@REPLACE_MYSQL_SERVER_NAME.mysql.database.azure.com:3306/openwork_den?sslaccept=accept"
|
||||
# Generate with: openssl rand -base64 48
|
||||
betterAuthSecret: "REPLACE_BETTER_AUTH_SECRET"
|
||||
# Generate separately with: openssl rand -base64 48
|
||||
denDbEncryptionKey: "REPLACE_DEN_DB_ENCRYPTION_KEY"
|
||||
# One-time /setup code. Generate a unique value and keep it out of source control.
|
||||
initialAdminBootstrapCode: "REPLACE_BOOTSTRAP_CODE"
|
||||
# Optional SMTP settings for invitations and other transactional email.
|
||||
# SMTP delivery requires both emailFrom and smtpHost.
|
||||
emailFrom: "OpenWork <no-reply@example.com>"
|
||||
smtpHost: ""
|
||||
smtpPort: "587"
|
||||
smtpUser: ""
|
||||
smtpPass: ""
|
||||
smtpSecure: "false"
|
||||
|
||||
denApi:
|
||||
replicaCount: 1
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8788
|
||||
|
||||
denWeb:
|
||||
replicaCount: 1
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 3005
|
||||
|
||||
inference:
|
||||
enabled: false
|
||||
|
||||
migrations:
|
||||
enabled: true
|
||||
hook: true
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: webapprouting.kubernetes.azure.com
|
||||
web:
|
||||
host: REPLACE_WEB_HOST
|
||||
api:
|
||||
enabled: true
|
||||
host: REPLACE_API_HOST
|
||||
tls:
|
||||
- secretName: openwork-ee-tls
|
||||
hosts:
|
||||
- REPLACE_WEB_HOST
|
||||
- REPLACE_API_HOST
|
||||
@@ -0,0 +1,94 @@
|
||||
# Google Cloud GKE starter values for a first OpenWork EE self-host install.
|
||||
#
|
||||
# Copy this file, replace every REPLACE_* value, and install with:
|
||||
#
|
||||
# helm upgrade --install openwork-ee oci://ghcr.io/different-ai/charts/openwork-ee \
|
||||
# --version REPLACE_OPENWORK_VERSION \
|
||||
# --namespace openwork-ee \
|
||||
# --create-namespace \
|
||||
# -f values.gcp.yaml
|
||||
#
|
||||
# This example exposes Den Web and Den API through GKE Ingress with a reserved
|
||||
# global IP and Google-managed certificate. Apply the ManagedCertificate and
|
||||
# BackendConfig manifests from docs/gcp-gke-helm.md before installing.
|
||||
|
||||
image:
|
||||
tag: "REPLACE_OPENWORK_VERSION"
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
config:
|
||||
tenancy:
|
||||
mode: single_org
|
||||
singleOrgName: "REPLACE_COMPANY_NAME"
|
||||
singleOrgSlug: "REPLACE_COMPANY_SLUG"
|
||||
ownerEmails: "REPLACE_ADMIN_EMAIL"
|
||||
allowPublicSignup: "false"
|
||||
requireEmailVerification: "false"
|
||||
auth:
|
||||
passwordBreachScreeningEnabled: "false"
|
||||
public:
|
||||
webOrigin: "https://REPLACE_WEB_HOST"
|
||||
# Split-origin compatibility override for this two-host ingress example.
|
||||
# Leave blank in single-origin deployments so Den derives it from webOrigin.
|
||||
apiOrigin: "https://REPLACE_API_HOST"
|
||||
mcpResourceUrl: "https://REPLACE_API_HOST/mcp"
|
||||
mcpClaimNamespace: "https://REPLACE_WEB_HOST"
|
||||
desktopDenBaseUrl: ""
|
||||
corsOrigins: "https://REPLACE_WEB_HOST,https://REPLACE_API_HOST"
|
||||
betterAuthTrustedOrigins: "https://REPLACE_WEB_HOST"
|
||||
webAppHosts: "REPLACE_WEB_HOST"
|
||||
bootstrapAdminEmails: "REPLACE_ADMIN_EMAIL"
|
||||
authCallbackUrl: "https://REPLACE_WEB_HOST"
|
||||
provisioner:
|
||||
mode: stub
|
||||
|
||||
secret:
|
||||
create: true
|
||||
values:
|
||||
# Direct private IP Cloud SQL example. The current chart does not inject
|
||||
# Cloud SQL Auth Proxy sidecars, so use a private IP reachable from GKE.
|
||||
databaseUrl: "mysql://openwork:REPLACE_DB_PASSWORD@REPLACE_CLOUD_SQL_PRIVATE_IP:3306/openwork_den"
|
||||
# Generate with: openssl rand -base64 48
|
||||
betterAuthSecret: "REPLACE_BETTER_AUTH_SECRET"
|
||||
# Generate separately with: openssl rand -base64 48
|
||||
denDbEncryptionKey: "REPLACE_DEN_DB_ENCRYPTION_KEY"
|
||||
# One-time /setup code. Generate a unique value and keep it out of source control.
|
||||
initialAdminBootstrapCode: "REPLACE_BOOTSTRAP_CODE"
|
||||
|
||||
denApi:
|
||||
replicaCount: 1
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8788
|
||||
annotations:
|
||||
cloud.google.com/backend-config: '{"default": "openwork-ee-den-api-backend"}'
|
||||
cloud.google.com/neg: '{"ingress": true}'
|
||||
|
||||
denWeb:
|
||||
replicaCount: 1
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 3005
|
||||
annotations:
|
||||
cloud.google.com/backend-config: '{"default": "openwork-ee-den-web-backend"}'
|
||||
cloud.google.com/neg: '{"ingress": true}'
|
||||
|
||||
inference:
|
||||
enabled: false
|
||||
|
||||
migrations:
|
||||
enabled: true
|
||||
hook: true
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: ""
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: gce
|
||||
kubernetes.io/ingress.global-static-ip-name: REPLACE_GLOBAL_STATIC_IP_NAME
|
||||
networking.gke.io/managed-certificates: REPLACE_MANAGED_CERTIFICATE_NAME
|
||||
web:
|
||||
host: REPLACE_WEB_HOST
|
||||
api:
|
||||
enabled: true
|
||||
host: REPLACE_API_HOST
|
||||
@@ -0,0 +1,26 @@
|
||||
OpenWork EE has been rendered with:
|
||||
|
||||
DEN_BASE_URL={{ .Values.config.public.webOrigin }}
|
||||
|
||||
DEN_BASE_URL is now the primary public Den URL. den-api derives Better Auth,
|
||||
CORS/trusted origins, web-app hosts, API defaults, and MCP resource defaults
|
||||
from this value unless compatibility overrides are set.
|
||||
|
||||
{{- $overrides := list -}}
|
||||
{{- if .Values.config.public.apiOrigin }}{{- $overrides = append $overrides (printf "config.public.apiOrigin=%s" .Values.config.public.apiOrigin) -}}{{- end -}}
|
||||
{{- if .Values.config.public.mcpResourceUrl }}{{- $overrides = append $overrides (printf "config.public.mcpResourceUrl=%s" .Values.config.public.mcpResourceUrl) -}}{{- end -}}
|
||||
{{- if .Values.config.public.desktopDenBaseUrl }}{{- $overrides = append $overrides (printf "config.public.desktopDenBaseUrl=%s" .Values.config.public.desktopDenBaseUrl) -}}{{- end -}}
|
||||
{{- if .Values.config.public.corsOrigins }}{{- $overrides = append $overrides (printf "config.public.corsOrigins=%s" .Values.config.public.corsOrigins) -}}{{- end -}}
|
||||
{{- if .Values.config.public.betterAuthTrustedOrigins }}{{- $overrides = append $overrides (printf "config.public.betterAuthTrustedOrigins=%s" .Values.config.public.betterAuthTrustedOrigins) -}}{{- end -}}
|
||||
{{- if .Values.config.public.webAppHosts }}{{- $overrides = append $overrides (printf "config.public.webAppHosts=%s" .Values.config.public.webAppHosts) -}}{{- end -}}
|
||||
{{- if gt (len $overrides) 0 }}
|
||||
|
||||
Compatibility URL overrides are still set:
|
||||
{{- range $overrides }}
|
||||
- {{ . }}
|
||||
{{- end }}
|
||||
|
||||
Keep these only for a deliberate split-origin/migration topology. For the
|
||||
single-origin recommended deployment, clear them so Den derives the values from
|
||||
config.public.webOrigin.
|
||||
{{- end }}
|
||||
@@ -0,0 +1,259 @@
|
||||
{{- define "openwork-ee.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.fullname" -}}
|
||||
{{- if .Values.fullnameOverride -}}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- if contains $name .Release.Name -}}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "openwork-ee.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.labels" -}}
|
||||
helm.sh/chart: {{ include "openwork-ee.chart" . }}
|
||||
{{ include "openwork-ee.selectorLabels" . }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.componentSelectorLabels" -}}
|
||||
{{ include "openwork-ee.selectorLabels" .root }}
|
||||
app.kubernetes.io/component: {{ .component }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.componentLabels" -}}
|
||||
{{ include "openwork-ee.labels" .root }}
|
||||
app.kubernetes.io/component: {{ .component }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.configName" -}}
|
||||
{{ include "openwork-ee.fullname" . }}-config
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.allowPrivateMcpUrls" -}}
|
||||
{{- $value := .Values.config.public.allowPrivateMcpUrls | default "" | toString | trim | lower -}}
|
||||
{{- if eq $value "1" -}}
|
||||
1
|
||||
{{- else if or (eq $value "") (eq $value "0") (eq $value "false") -}}
|
||||
{{- else -}}
|
||||
{{- fail "config.public.allowPrivateMcpUrls must be blank, 0, false, or \"1\"" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.secretName" -}}
|
||||
{{- if .Values.secret.existingSecret -}}
|
||||
{{- .Values.secret.existingSecret -}}
|
||||
{{- else -}}
|
||||
{{- include "openwork-ee.fullname" . }}-secret
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.denApiServiceName" -}}
|
||||
{{ include "openwork-ee.fullname" . }}-den-api
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.denWebServiceName" -}}
|
||||
{{ include "openwork-ee.fullname" . }}-den-web
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.inferenceServiceName" -}}
|
||||
{{ include "openwork-ee.fullname" . }}-inference
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.denApiInternalUrl" -}}
|
||||
{{- default (printf "http://%s:%v" (include "openwork-ee.denApiServiceName" .) .Values.denApi.service.port) .Values.config.internal.apiBaseUrl -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.authFallbackInternalUrl" -}}
|
||||
{{- default (include "openwork-ee.denApiInternalUrl" .) .Values.config.internal.authFallbackBaseUrl -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.inferenceInternalUrl" -}}
|
||||
{{- default (printf "http://%s:%v" (include "openwork-ee.inferenceServiceName" .) .Values.inference.service.port) .Values.config.internal.inferenceProxyBaseUrl -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.customCa.mountPath" -}}
|
||||
/etc/openwork/custom-ca
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.customCa.filePath" -}}
|
||||
{{ include "openwork-ee.customCa.mountPath" . }}/ca-bundle.pem
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.customCa.validate" -}}
|
||||
{{- if .Values.customCa.enabled -}}
|
||||
{{- if and .Values.customCa.existingSecret .Values.customCa.existingConfigMap -}}
|
||||
{{- fail "customCa.existingSecret and customCa.existingConfigMap are mutually exclusive when customCa.enabled=true" -}}
|
||||
{{- end -}}
|
||||
{{- if not (or .Values.customCa.existingSecret .Values.customCa.existingConfigMap) -}}
|
||||
{{- fail "customCa.existingSecret or customCa.existingConfigMap is required when customCa.enabled=true" -}}
|
||||
{{- end -}}
|
||||
{{- if not .Values.customCa.key -}}
|
||||
{{- fail "customCa.key is required when customCa.enabled=true" -}}
|
||||
{{- end -}}
|
||||
{{- if hasKey .Values.denApi.env "NODE_EXTRA_CA_CERTS" -}}
|
||||
{{- fail "denApi.env.NODE_EXTRA_CA_CERTS conflicts with customCa.enabled=true; remove it and use customCa instead" -}}
|
||||
{{- end -}}
|
||||
{{- if hasKey .Values.denWeb.env "NODE_EXTRA_CA_CERTS" -}}
|
||||
{{- fail "denWeb.env.NODE_EXTRA_CA_CERTS conflicts with customCa.enabled=true; remove it and use customCa instead" -}}
|
||||
{{- end -}}
|
||||
{{- if hasKey .Values.inference.env "NODE_EXTRA_CA_CERTS" -}}
|
||||
{{- fail "inference.env.NODE_EXTRA_CA_CERTS conflicts with customCa.enabled=true; remove it and use customCa instead" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.customCa.volume" -}}
|
||||
- name: custom-ca
|
||||
{{- if .Values.customCa.existingSecret }}
|
||||
secret:
|
||||
secretName: {{ .Values.customCa.existingSecret | quote }}
|
||||
items:
|
||||
- key: {{ .Values.customCa.key | quote }}
|
||||
path: ca-bundle.pem
|
||||
{{- else }}
|
||||
configMap:
|
||||
name: {{ .Values.customCa.existingConfigMap | quote }}
|
||||
items:
|
||||
- key: {{ .Values.customCa.key | quote }}
|
||||
path: ca-bundle.pem
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.customCa.volumeMount" -}}
|
||||
- name: custom-ca
|
||||
mountPath: {{ include "openwork-ee.customCa.mountPath" . | quote }}
|
||||
readOnly: true
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.customCa.env" -}}
|
||||
- name: NODE_EXTRA_CA_CERTS
|
||||
value: {{ include "openwork-ee.customCa.filePath" . | quote }}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.observabilityBackend" -}}
|
||||
{{- $backend := default "none" .Values.observability.backend -}}
|
||||
{{- if not (has $backend (list "none" "otel" "sentry")) -}}
|
||||
{{- fail "observability.backend must be one of none, otel, sentry" -}}
|
||||
{{- end -}}
|
||||
{{- $backend -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.observabilityOtelExporter" -}}
|
||||
{{- $exporter := default "otlp" .value -}}
|
||||
{{- if not (has $exporter (list "otlp" "none")) -}}
|
||||
{{- fail (printf "observability.otel.exporters.%s must be otlp or none" .signal) -}}
|
||||
{{- end -}}
|
||||
{{- $exporter -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.observabilityOtelSampler" -}}
|
||||
{{- $sampler := default "parentbased_always_on" . -}}
|
||||
{{- if not (has $sampler (list "always_on" "always_off" "traceidratio" "parentbased_always_on" "parentbased_always_off" "parentbased_traceidratio")) -}}
|
||||
{{- fail "observability.otel.tracesSampler must be a standard OpenTelemetry sampler" -}}
|
||||
{{- end -}}
|
||||
{{- $sampler -}}
|
||||
{{- end -}}
|
||||
|
||||
{{- define "openwork-ee.observabilityEnv" -}}
|
||||
{{- $root := .root -}}
|
||||
{{- $serviceName := .serviceName -}}
|
||||
{{- $backend := include "openwork-ee.observabilityBackend" $root -}}
|
||||
{{- $otel := $root.Values.observability.otel -}}
|
||||
{{- $sentry := $root.Values.observability.sentry -}}
|
||||
- name: DEN_OBSERVABILITY_BACKEND
|
||||
value: {{ $backend | quote }}
|
||||
- name: OTEL_SERVICE_NAME
|
||||
value: {{ $serviceName | quote }}
|
||||
{{- if eq $backend "otel" }}
|
||||
{{- $otelSampler := include "openwork-ee.observabilityOtelSampler" $otel.tracesSampler -}}
|
||||
{{- $otelProtocol := default "http/protobuf" $otel.protocol -}}
|
||||
{{- if ne $otelProtocol "http/protobuf" }}
|
||||
{{- fail "observability.otel.protocol must be http/protobuf" -}}
|
||||
{{- end }}
|
||||
- name: OTEL_EXPORTER_OTLP_PROTOCOL
|
||||
value: {{ $otelProtocol | quote }}
|
||||
{{- with $otel.endpoint }}
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- with $otel.tracesEndpoint }}
|
||||
- name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- with $otel.metricsEndpoint }}
|
||||
- name: OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- with $otel.logsEndpoint }}
|
||||
- name: OTEL_EXPORTER_OTLP_LOGS_ENDPOINT
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
- name: OTEL_TRACES_EXPORTER
|
||||
value: {{ include "openwork-ee.observabilityOtelExporter" (dict "signal" "traces" "value" $otel.exporters.traces) | quote }}
|
||||
- name: OTEL_METRICS_EXPORTER
|
||||
value: {{ include "openwork-ee.observabilityOtelExporter" (dict "signal" "metrics" "value" $otel.exporters.metrics) | quote }}
|
||||
- name: OTEL_LOGS_EXPORTER
|
||||
value: {{ include "openwork-ee.observabilityOtelExporter" (dict "signal" "logs" "value" $otel.exporters.logs) | quote }}
|
||||
- name: OTEL_TRACES_SAMPLER
|
||||
value: {{ $otelSampler | quote }}
|
||||
{{- if has $otelSampler (list "traceidratio" "parentbased_traceidratio") }}
|
||||
- name: OTEL_TRACES_SAMPLER_ARG
|
||||
value: {{ default "1" $otel.tracesSamplerArg | quote }}
|
||||
{{- else if and $otel.tracesSamplerArg (ne (toString $otel.tracesSamplerArg) "1") }}
|
||||
{{- fail "observability.otel.tracesSamplerArg is only supported for traceidratio samplers" -}}
|
||||
{{- end }}
|
||||
{{- with $otel.headers.existingSecret }}
|
||||
- name: OTEL_EXPORTER_OTLP_HEADERS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ . | quote }}
|
||||
key: {{ $otel.headers.key | quote }}
|
||||
{{- end }}
|
||||
{{- else if eq $backend "sentry" }}
|
||||
{{- if and $sentry.dsn $sentry.dsnSecret.existingSecret }}
|
||||
{{- fail "observability.sentry.dsn and observability.sentry.dsnSecret.existingSecret are mutually exclusive" -}}
|
||||
{{- end }}
|
||||
{{- if not (or $sentry.dsn $sentry.dsnSecret.existingSecret) }}
|
||||
{{- fail "observability.sentry.dsn or observability.sentry.dsnSecret.existingSecret is required when observability.backend=sentry" -}}
|
||||
{{- end }}
|
||||
- name: SENTRY_DSN
|
||||
{{- if $sentry.dsn }}
|
||||
value: {{ $sentry.dsn | quote }}
|
||||
{{- else }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ $sentry.dsnSecret.existingSecret | quote }}
|
||||
key: {{ $sentry.dsnSecret.key | quote }}
|
||||
{{- end }}
|
||||
- name: SENTRY_TRACES_SAMPLE_RATE
|
||||
value: {{ $sentry.tracesSampleRate | quote }}
|
||||
{{- with $sentry.environment }}
|
||||
- name: SENTRY_ENVIRONMENT
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- with $sentry.release }}
|
||||
- name: SENTRY_RELEASE
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- with $sentry.dist }}
|
||||
- name: SENTRY_DIST
|
||||
value: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
@@ -0,0 +1,200 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "openwork-ee.configName" . }}
|
||||
labels:
|
||||
{{- include "openwork-ee.labels" . | nindent 4 }}
|
||||
data:
|
||||
CI: "true"
|
||||
NODE_ENV: "production"
|
||||
OPENWORK_DEV_MODE: {{ .Values.config.openworkDevMode | quote }}
|
||||
DEN_API_NODE_OPTIONS: {{ .Values.config.denApiNodeOptions | quote }}
|
||||
# SECURITY: Enables non-local redis:// DATABASE_REDIS_URL values only for
|
||||
# trusted private networks. Keep false for public or cross-network Redis;
|
||||
# prefer rediss:// whenever the provider supports TLS.
|
||||
DATABASE_REDIS_ALLOW_INSECURE_INTERNAL: {{ ternary "1" "0" .Values.redis.allowInsecureInternal | quote }}
|
||||
DB_MODE: {{ .Values.config.databaseMode | quote }}
|
||||
DEN_ORG_MODE: {{ .Values.config.tenancy.mode | quote }}
|
||||
DEN_SINGLE_ORG_NAME: {{ .Values.config.tenancy.singleOrgName | quote }}
|
||||
DEN_SINGLE_ORG_SLUG: {{ .Values.config.tenancy.singleOrgSlug | quote }}
|
||||
DEN_SINGLE_ORG_OWNER_EMAILS: {{ .Values.config.tenancy.ownerEmails | quote }}
|
||||
DEN_SINGLE_ORG_ALLOW_PUBLIC_SIGNUP: {{ .Values.config.tenancy.allowPublicSignup | quote }}
|
||||
DEN_REQUIRE_EMAIL_VERIFICATION: {{ .Values.config.tenancy.requireEmailVerification | quote }}
|
||||
DEN_PASSWORD_BREACH_SCREENING_ENABLED: {{ .Values.config.auth.passwordBreachScreeningEnabled | quote }}
|
||||
GITHUB_CONNECTOR_APP_ID: {{ .Values.config.githubConnector.appId | quote }}
|
||||
GITHUB_CONNECTOR_APP_CLIENT_ID: {{ .Values.config.githubConnector.clientId | quote }}
|
||||
DEN_BASE_URL: {{ .Values.config.public.webOrigin | quote }}
|
||||
{{ if .Values.config.public.webOrigin }}
|
||||
# Compatibility alias for older Den builds; current den-api derives it from DEN_BASE_URL.
|
||||
BETTER_AUTH_URL: {{ .Values.config.public.webOrigin | quote }}
|
||||
{{ end }}
|
||||
{{ if .Values.config.public.mcpResourceUrl }}
|
||||
DEN_MCP_RESOURCE_URL: {{ .Values.config.public.mcpResourceUrl | quote }}
|
||||
{{ end }}
|
||||
# Extra MCP resource URLs beyond the DEN_API_PUBLIC_URL API origin and web-app defaults.
|
||||
{{ if .Values.config.public.mcpAdditionalResources }}
|
||||
DEN_MCP_ADDITIONAL_RESOURCES: {{ .Values.config.public.mcpAdditionalResources | quote }}
|
||||
{{ end }}
|
||||
DEN_MCP_CLAIM_NAMESPACE: {{ .Values.config.public.mcpClaimNamespace | quote }}
|
||||
{{ if .Values.config.public.betterAuthTrustedOrigins }}
|
||||
DEN_BETTER_AUTH_TRUSTED_ORIGINS: {{ .Values.config.public.betterAuthTrustedOrigins | quote }}
|
||||
{{ end }}
|
||||
{{ if .Values.config.public.betterAuthCookieDomain }}
|
||||
DEN_BETTER_AUTH_COOKIE_DOMAIN: {{ .Values.config.public.betterAuthCookieDomain | quote }}
|
||||
{{ end }}
|
||||
{{ if .Values.config.public.webAppHosts }}
|
||||
DEN_WEB_APP_HOSTS: {{ .Values.config.public.webAppHosts | quote }}
|
||||
{{ end }}
|
||||
DEN_BOOTSTRAP_ADMIN_EMAILS: {{ .Values.config.public.bootstrapAdminEmails | quote }}
|
||||
DEN_INSTALL_LINKS_GATING_ENABLED: {{ .Values.config.public.installLinksGatingEnabled | quote }}
|
||||
DEN_AUTOMATIONS_ENABLED: {{ .Values.config.public.automationsEnabled | quote }}
|
||||
DEN_DASHBOARDS_ENABLED: {{ .Values.config.public.dashboardsEnabled | quote }}
|
||||
DEN_OPENWORK_WEB_ENABLED: {{ .Values.config.public.openworkWebEnabled | quote }}
|
||||
DEN_AUTOMATIONS_RUNTIME_ENABLED: {{ .Values.config.public.automationsRuntimeEnabled | quote }}
|
||||
DEN_GENERATED_ARTIFACT_VIEWS_ENABLED: {{ .Values.config.public.generatedArtifactViewsEnabled | quote }}
|
||||
DEN_CONNECT_LINK_MODE: {{ .Values.config.public.connectLinkMode | quote }}
|
||||
{{ if .Values.config.public.connectLinkKeyId }}
|
||||
DEN_CONNECT_LINK_KEY_ID: {{ .Values.config.public.connectLinkKeyId | quote }}
|
||||
{{ end }}
|
||||
{{ if .Values.config.public.corsOrigins }}
|
||||
CORS_ORIGINS: {{ .Values.config.public.corsOrigins | quote }}
|
||||
DEN_CORS_ORIGINS: {{ .Values.config.public.corsOrigins | quote }}
|
||||
{{ end }}
|
||||
{{ if .Values.config.public.apiOrigin }}
|
||||
DEN_API_PUBLIC_URL: {{ .Values.config.public.apiOrigin | quote }}
|
||||
{{ end }}
|
||||
{{ if .Values.config.public.desktopDenBaseUrl }}
|
||||
DEN_DESKTOP_DEN_BASE_URL: {{ .Values.config.public.desktopDenBaseUrl | quote }}
|
||||
{{ end }}
|
||||
{{ if .Values.config.public.installerReleaseTag }}
|
||||
OPENWORK_INSTALLER_RELEASE_TAG: {{ .Values.config.public.installerReleaseTag | quote }}
|
||||
{{ end }}
|
||||
{{ if .Values.config.public.installerReleaseRepo }}
|
||||
OPENWORK_INSTALLER_RELEASE_REPO: {{ .Values.config.public.installerReleaseRepo | quote }}
|
||||
{{ end }}
|
||||
PROVISIONER_MODE: {{ .Values.config.provisioner.mode | quote }}
|
||||
WORKER_PROVISIONING_RECONCILE_INTERVAL_MS: {{ .Values.config.provisioner.reconcileIntervalMs | quote }}
|
||||
WORKER_PROVISIONING_RECONCILE_STALE_MS: {{ .Values.config.provisioner.reconcileStaleMs | quote }}
|
||||
WORKER_PROVISIONING_RECONCILE_BATCH_SIZE: {{ .Values.config.provisioner.reconcileBatchSize | quote }}
|
||||
WORKER_URL_TEMPLATE: {{ .Values.config.provisioner.workerUrlTemplate | quote }}
|
||||
{{ if or .Values.inference.enabled .Values.config.internal.inferenceProxyBaseUrl }}
|
||||
INFERENCE_PROXY_BASE_URL: {{ include "openwork-ee.inferenceInternalUrl" . | quote }}
|
||||
{{ end }}
|
||||
POLAR_FEATURE_GATE_ENABLED: {{ .Values.config.polar.featureGateEnabled | quote }}
|
||||
DAYTONA_SIGNED_PREVIEW_EXPIRES_SECONDS: {{ .Values.config.daytona.signedPreviewExpiresSeconds | quote }}
|
||||
DEN_API_BASE: {{ include "openwork-ee.denApiInternalUrl" . | quote }}
|
||||
DEN_AUTH_FALLBACK_BASE: {{ include "openwork-ee.authFallbackInternalUrl" . | quote }}
|
||||
DEN_AUTH_ORIGIN: {{ .Values.config.public.webOrigin | quote }}
|
||||
DEN_WEB_PUBLIC_ORIGIN: {{ .Values.config.public.webOrigin | quote }}
|
||||
DEN_WEB_OPENWORK_AUTH_CALLBACK_URL: {{ .Values.config.public.authCallbackUrl | quote }}
|
||||
{{ with .Values.config.polar.apiBase }}
|
||||
POLAR_API_BASE: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.polar.productId }}
|
||||
POLAR_PRODUCT_ID: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.polar.benefitId }}
|
||||
POLAR_BENEFIT_ID: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.polar.successUrl }}
|
||||
POLAR_SUCCESS_URL: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.polar.returnUrl }}
|
||||
POLAR_RETURN_URL: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.apiUrl }}
|
||||
DAYTONA_API_URL: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.target }}
|
||||
DAYTONA_TARGET: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.snapshot }}
|
||||
DAYTONA_SNAPSHOT: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.sandboxImage }}
|
||||
DAYTONA_SANDBOX_IMAGE: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.sandboxCpu }}
|
||||
DAYTONA_SANDBOX_CPU: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.sandboxMemory }}
|
||||
DAYTONA_SANDBOX_MEMORY: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.sandboxDisk }}
|
||||
DAYTONA_SANDBOX_DISK: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.sandboxPublic }}
|
||||
DAYTONA_SANDBOX_PUBLIC: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.sandboxAutoStopInterval }}
|
||||
DAYTONA_SANDBOX_AUTO_STOP_INTERVAL: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.sandboxAutoArchiveInterval }}
|
||||
DAYTONA_SANDBOX_AUTO_ARCHIVE_INTERVAL: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.sandboxAutoDeleteInterval }}
|
||||
DAYTONA_SANDBOX_AUTO_DELETE_INTERVAL: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.sandboxNamePrefix }}
|
||||
DAYTONA_SANDBOX_NAME_PREFIX: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.sharedVolumeName }}
|
||||
DAYTONA_SHARED_VOLUME_NAME: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.volumeNamePrefix }}
|
||||
DAYTONA_VOLUME_NAME_PREFIX: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.workspaceMountPath }}
|
||||
DAYTONA_WORKSPACE_MOUNT_PATH: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.dataMountPath }}
|
||||
DAYTONA_DATA_MOUNT_PATH: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.runtimeWorkspacePath }}
|
||||
DAYTONA_RUNTIME_WORKSPACE_PATH: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.runtimeDataPath }}
|
||||
DAYTONA_RUNTIME_DATA_PATH: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.sidecarDir }}
|
||||
DAYTONA_SIDECAR_DIR: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.openworkPort }}
|
||||
DAYTONA_OPENWORK_PORT: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.opencodePort }}
|
||||
DAYTONA_OPENCODE_PORT: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.createTimeoutSeconds }}
|
||||
DAYTONA_CREATE_TIMEOUT_SECONDS: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.deleteTimeoutSeconds }}
|
||||
DAYTONA_DELETE_TIMEOUT_SECONDS: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.daytona.healthcheckTimeoutMs }}
|
||||
DAYTONA_HEALTHCHECK_TIMEOUT_MS: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.inference.openRouterWorkspaceId }}
|
||||
OPENROUTER_WORKSPACE_ID: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.inference.stripeInferencePriceId }}
|
||||
STRIPE_INFERENCE_PRICE_ID: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.inference.openRouterUpstreamUrl }}
|
||||
OPENROUTER_UPSTREAM_URL: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.inference.creditsPerDollar }}
|
||||
INFERENCE_CREDITS_PER_DOLLAR: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.public.openworkAppConnectUrl }}
|
||||
DEN_WEB_OPENWORK_APP_CONNECT_URL: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.public.openworkWebUrl }}
|
||||
DEN_WEB_OPENWORK_WEB_URL: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.web.posthogKey }}
|
||||
DEN_WEB_POSTHOG_KEY: {{ . | quote }}
|
||||
{{ end }}
|
||||
{{ with .Values.config.web.posthogHost }}
|
||||
DEN_WEB_POSTHOG_HOST: {{ . | quote }}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,145 @@
|
||||
{{- include "openwork-ee.customCa.validate" . }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "openwork-ee.denApiServiceName" . }}
|
||||
labels:
|
||||
{{- include "openwork-ee.componentLabels" (dict "root" . "component" "den-api") | nindent 4 }}
|
||||
{{- with .Values.denApi.service.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ .Values.denApi.service.type }}
|
||||
{{- with .Values.denApi.service.loadBalancerClass }}
|
||||
loadBalancerClass: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- with .Values.denApi.service.loadBalancerSourceRanges }}
|
||||
loadBalancerSourceRanges:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .Values.denApi.service.port }}
|
||||
targetPort: http
|
||||
selector:
|
||||
{{- include "openwork-ee.componentSelectorLabels" (dict "root" . "component" "den-api") | nindent 4 }}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "openwork-ee.fullname" . }}-den-api
|
||||
labels:
|
||||
{{- include "openwork-ee.componentLabels" (dict "root" . "component" "den-api") | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.denApi.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "openwork-ee.componentSelectorLabels" (dict "root" . "component" "den-api") | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "openwork-ee.componentSelectorLabels" (dict "root" . "component" "den-api") | nindent 8 }}
|
||||
{{- with .Values.denApi.podLabels }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
|
||||
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
|
||||
{{- with .Values.denApi.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.installerArtifacts.enabled }}
|
||||
{{- if and .Values.installerArtifacts.existingClaim .Values.installerArtifacts.hostPath }}
|
||||
{{- fail "installerArtifacts.existingClaim and installerArtifacts.hostPath are mutually exclusive" }}
|
||||
{{- end }}
|
||||
{{- if not (or .Values.installerArtifacts.existingClaim .Values.installerArtifacts.hostPath) }}
|
||||
{{- fail "installerArtifacts.existingClaim or installerArtifacts.hostPath is required when installerArtifacts.enabled=true" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if or .Values.installerArtifacts.enabled .Values.customCa.enabled }}
|
||||
volumes:
|
||||
{{- if .Values.installerArtifacts.enabled }}
|
||||
- name: installer-artifacts
|
||||
{{- if .Values.installerArtifacts.existingClaim }}
|
||||
persistentVolumeClaim:
|
||||
claimName: {{ .Values.installerArtifacts.existingClaim | quote }}
|
||||
{{- else }}
|
||||
hostPath:
|
||||
path: {{ .Values.installerArtifacts.hostPath | quote }}
|
||||
type: Directory
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.customCa.enabled }}
|
||||
{{- include "openwork-ee.customCa.volume" . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: den-api
|
||||
image: "{{ .Values.denApi.image.repository }}:{{ default .Values.image.tag .Values.denApi.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.denApi.containerPort }}
|
||||
{{- if or .Values.installerArtifacts.enabled .Values.customCa.enabled }}
|
||||
volumeMounts:
|
||||
{{- if .Values.installerArtifacts.enabled }}
|
||||
- name: installer-artifacts
|
||||
mountPath: {{ .Values.installerArtifacts.mountPath | quote }}
|
||||
readOnly: true
|
||||
{{- end }}
|
||||
{{- if .Values.customCa.enabled }}
|
||||
{{- include "openwork-ee.customCa.volumeMount" . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "openwork-ee.configName" . }}
|
||||
- secretRef:
|
||||
name: {{ include "openwork-ee.secretName" . }}
|
||||
env:
|
||||
- name: PORT
|
||||
value: {{ .Values.denApi.containerPort | quote }}
|
||||
{{- if not (hasKey .Values.denApi.env "NODE_OPTIONS") }}
|
||||
# Keep this as a direct value so upgrades from legacy denApi.env.NODE_OPTIONS
|
||||
# do not leave both value and valueFrom in Kubernetes' strategic merge patch.
|
||||
- name: NODE_OPTIONS
|
||||
value: {{ .Values.config.denApiNodeOptions | quote }}
|
||||
# Explicitly clear the valueFrom emitted by chart 0.17.24 during upgrades.
|
||||
valueFrom: null
|
||||
{{- end }}
|
||||
{{- if .Values.installerArtifacts.enabled }}
|
||||
- name: OPENWORK_INSTALLER_ARTIFACTS_DIR
|
||||
value: {{ .Values.installerArtifacts.mountPath | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.customCa.enabled }}
|
||||
{{- include "openwork-ee.customCa.env" . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if eq (include "openwork-ee.allowPrivateMcpUrls" .) "1" }}
|
||||
- name: DEN_ALLOW_PRIVATE_MCP_URLS
|
||||
value: "1"
|
||||
{{- end }}
|
||||
{{- include "openwork-ee.observabilityEnv" (dict "root" . "serviceName" .Values.observability.serviceNames.denApi) | nindent 12 }}
|
||||
{{- range $key, $value := .Values.denApi.env }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $value | quote }}
|
||||
{{- end }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
{{- toYaml .Values.denApi.probes.liveness | nindent 12 }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: http
|
||||
{{- toYaml .Values.denApi.probes.readiness | nindent 12 }}
|
||||
{{- with .Values.denApi.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,101 @@
|
||||
{{- include "openwork-ee.customCa.validate" . }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "openwork-ee.denWebServiceName" . }}
|
||||
labels:
|
||||
{{- include "openwork-ee.componentLabels" (dict "root" . "component" "den-web") | nindent 4 }}
|
||||
{{- with .Values.denWeb.service.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ .Values.denWeb.service.type }}
|
||||
{{- with .Values.denWeb.service.loadBalancerClass }}
|
||||
loadBalancerClass: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- with .Values.denWeb.service.loadBalancerSourceRanges }}
|
||||
loadBalancerSourceRanges:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .Values.denWeb.service.port }}
|
||||
targetPort: http
|
||||
selector:
|
||||
{{- include "openwork-ee.componentSelectorLabels" (dict "root" . "component" "den-web") | nindent 4 }}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "openwork-ee.fullname" . }}-den-web
|
||||
labels:
|
||||
{{- include "openwork-ee.componentLabels" (dict "root" . "component" "den-web") | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.denWeb.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "openwork-ee.componentSelectorLabels" (dict "root" . "component" "den-web") | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "openwork-ee.componentSelectorLabels" (dict "root" . "component" "den-web") | nindent 8 }}
|
||||
{{- with .Values.denWeb.podLabels }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
|
||||
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
|
||||
{{- with .Values.denWeb.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.customCa.enabled }}
|
||||
volumes:
|
||||
{{- include "openwork-ee.customCa.volume" . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: den-web
|
||||
image: "{{ .Values.denWeb.image.repository }}:{{ default .Values.image.tag .Values.denWeb.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.denWeb.containerPort }}
|
||||
{{- if .Values.customCa.enabled }}
|
||||
volumeMounts:
|
||||
{{- include "openwork-ee.customCa.volumeMount" . | nindent 12 }}
|
||||
{{- end }}
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "openwork-ee.configName" . }}
|
||||
- secretRef:
|
||||
name: {{ include "openwork-ee.secretName" . }}
|
||||
env:
|
||||
- name: PORT
|
||||
value: {{ .Values.denWeb.containerPort | quote }}
|
||||
{{- if .Values.customCa.enabled }}
|
||||
{{- include "openwork-ee.customCa.env" . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- include "openwork-ee.observabilityEnv" (dict "root" . "serviceName" .Values.observability.serviceNames.denWeb) | nindent 12 }}
|
||||
{{- range $key, $value := .Values.denWeb.env }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $value | quote }}
|
||||
{{- end }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /api/health
|
||||
port: http
|
||||
{{- toYaml .Values.denWeb.probes.liveness | nindent 12 }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /api/ready
|
||||
port: http
|
||||
{{- toYaml .Values.denWeb.probes.readiness | nindent 12 }}
|
||||
{{- with .Values.denWeb.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,102 @@
|
||||
{{- include "openwork-ee.customCa.validate" . }}
|
||||
{{- if .Values.inference.enabled }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "openwork-ee.inferenceServiceName" . }}
|
||||
labels:
|
||||
{{- include "openwork-ee.componentLabels" (dict "root" . "component" "inference") | nindent 4 }}
|
||||
{{- with .Values.inference.service.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ .Values.inference.service.type }}
|
||||
{{- with .Values.inference.service.loadBalancerClass }}
|
||||
loadBalancerClass: {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- with .Values.inference.service.loadBalancerSourceRanges }}
|
||||
loadBalancerSourceRanges:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
port: {{ .Values.inference.service.port }}
|
||||
targetPort: http
|
||||
selector:
|
||||
{{- include "openwork-ee.componentSelectorLabels" (dict "root" . "component" "inference") | nindent 4 }}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "openwork-ee.fullname" . }}-inference
|
||||
labels:
|
||||
{{- include "openwork-ee.componentLabels" (dict "root" . "component" "inference") | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.inference.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "openwork-ee.componentSelectorLabels" (dict "root" . "component" "inference") | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "openwork-ee.componentSelectorLabels" (dict "root" . "component" "inference") | nindent 8 }}
|
||||
{{- with .Values.inference.podLabels }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
|
||||
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
|
||||
{{- with .Values.inference.podAnnotations }}
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.customCa.enabled }}
|
||||
volumes:
|
||||
{{- include "openwork-ee.customCa.volume" . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: inference
|
||||
image: "{{ .Values.inference.image.repository }}:{{ default .Values.image.tag .Values.inference.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.inference.containerPort }}
|
||||
{{- if .Values.customCa.enabled }}
|
||||
volumeMounts:
|
||||
{{- include "openwork-ee.customCa.volumeMount" . | nindent 12 }}
|
||||
{{- end }}
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "openwork-ee.configName" . }}
|
||||
- secretRef:
|
||||
name: {{ include "openwork-ee.secretName" . }}
|
||||
env:
|
||||
- name: PORT
|
||||
value: {{ .Values.inference.containerPort | quote }}
|
||||
{{- if .Values.customCa.enabled }}
|
||||
{{- include "openwork-ee.customCa.env" . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- range $key, $value := .Values.inference.env }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $value | quote }}
|
||||
{{- end }}
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
{{- toYaml .Values.inference.probes.liveness | nindent 12 }}
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: http
|
||||
{{- toYaml .Values.inference.probes.readiness | nindent 12 }}
|
||||
{{- with .Values.inference.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,43 @@
|
||||
{{- if .Values.ingress.enabled }}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "openwork-ee.fullname" . }}
|
||||
labels:
|
||||
{{- include "openwork-ee.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.ingress.className }}
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- end }}
|
||||
{{- with .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
rules:
|
||||
- host: {{ .Values.ingress.web.host | quote }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ .Values.ingress.web.path }}
|
||||
pathType: {{ .Values.ingress.web.pathType }}
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "openwork-ee.denWebServiceName" . }}
|
||||
port:
|
||||
name: http
|
||||
{{- if .Values.ingress.api.enabled }}
|
||||
- host: {{ .Values.ingress.api.host | quote }}
|
||||
http:
|
||||
paths:
|
||||
- path: {{ .Values.ingress.api.path }}
|
||||
pathType: {{ .Values.ingress.api.pathType }}
|
||||
backend:
|
||||
service:
|
||||
name: {{ include "openwork-ee.denApiServiceName" . }}
|
||||
port:
|
||||
name: http
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,78 @@
|
||||
{{- include "openwork-ee.customCa.validate" . }}
|
||||
{{- if .Values.migrations.enabled }}
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: {{ include "openwork-ee.fullname" . }}-migrate
|
||||
labels:
|
||||
{{- include "openwork-ee.componentLabels" (dict "root" . "component" "migration") | nindent 4 }}
|
||||
{{- if .Values.migrations.hook }}
|
||||
annotations:
|
||||
"helm.sh/hook": pre-install,pre-upgrade
|
||||
"helm.sh/hook-weight": "-5"
|
||||
{{- with .Values.migrations.hookDeletePolicy }}
|
||||
"helm.sh/hook-delete-policy": {{ . | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
spec:
|
||||
backoffLimit: {{ .Values.migrations.backoffLimit }}
|
||||
activeDeadlineSeconds: {{ .Values.migrations.activeDeadlineSeconds }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "openwork-ee.componentSelectorLabels" (dict "root" . "component" "migration") | nindent 8 }}
|
||||
{{- with .Values.migrations.podAnnotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.customCa.enabled }}
|
||||
volumes:
|
||||
{{- include "openwork-ee.customCa.volume" . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: migrate
|
||||
image: "{{ .Values.denApi.image.repository }}:{{ default .Values.image.tag .Values.denApi.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
{{- if .Values.customCa.enabled }}
|
||||
volumeMounts:
|
||||
{{- include "openwork-ee.customCa.volumeMount" . | nindent 12 }}
|
||||
{{- end }}
|
||||
command:
|
||||
{{- toYaml .Values.migrations.command | nindent 12 }}
|
||||
args:
|
||||
{{- toYaml .Values.migrations.args | nindent 12 }}
|
||||
env:
|
||||
- name: DB_MODE
|
||||
value: {{ .Values.config.databaseMode | quote }}
|
||||
- name: DATABASE_URL
|
||||
{{- if .Values.secret.create }}
|
||||
value: {{ .Values.secret.values.databaseUrl | quote }}
|
||||
{{- else }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "openwork-ee.secretName" . }}
|
||||
key: {{ .Values.secret.keys.databaseUrl }}
|
||||
{{- end }}
|
||||
- name: DEN_DB_ENCRYPTION_KEY
|
||||
{{- if .Values.secret.create }}
|
||||
value: {{ .Values.secret.values.denDbEncryptionKey | quote }}
|
||||
{{- else }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ include "openwork-ee.secretName" . }}
|
||||
key: {{ .Values.secret.keys.denDbEncryptionKey }}
|
||||
{{- end }}
|
||||
{{- if .Values.customCa.enabled }}
|
||||
{{- include "openwork-ee.customCa.env" . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- with .Values.migrations.resources }}
|
||||
resources:
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,30 @@
|
||||
{{- if .Values.secret.create }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "openwork-ee.secretName" . }}
|
||||
labels:
|
||||
{{- include "openwork-ee.labels" . | nindent 4 }}
|
||||
type: Opaque
|
||||
stringData:
|
||||
{{ .Values.secret.keys.databaseUrl }}: {{ .Values.secret.values.databaseUrl | quote }}
|
||||
{{ .Values.secret.keys.databaseRedisUrl }}: {{ .Values.secret.values.databaseRedisUrl | quote }}
|
||||
{{ .Values.secret.keys.betterAuthSecret }}: {{ .Values.secret.values.betterAuthSecret | quote }}
|
||||
{{ .Values.secret.keys.denDbEncryptionKey }}: {{ .Values.secret.values.denDbEncryptionKey | quote }}
|
||||
{{ .Values.secret.keys.daytonaApiKey }}: {{ .Values.secret.values.daytonaApiKey | quote }}
|
||||
{{ .Values.secret.keys.polarAccessToken }}: {{ .Values.secret.values.polarAccessToken | quote }}
|
||||
{{ .Values.secret.keys.openRouterManagementApiKey }}: {{ .Values.secret.values.openRouterManagementApiKey | quote }}
|
||||
{{ .Values.secret.keys.inferenceAdminToken }}: {{ .Values.secret.values.inferenceAdminToken | quote }}
|
||||
{{ .Values.secret.keys.inferenceWebhookSecret }}: {{ .Values.secret.values.inferenceWebhookSecret | quote }}
|
||||
{{ .Values.secret.keys.emailFrom }}: {{ .Values.secret.values.emailFrom | quote }}
|
||||
{{ .Values.secret.keys.smtpHost }}: {{ .Values.secret.values.smtpHost | quote }}
|
||||
{{ .Values.secret.keys.smtpPort }}: {{ .Values.secret.values.smtpPort | quote }}
|
||||
{{ .Values.secret.keys.smtpUser }}: {{ .Values.secret.values.smtpUser | quote }}
|
||||
{{ .Values.secret.keys.smtpPass }}: {{ .Values.secret.values.smtpPass | quote }}
|
||||
{{ .Values.secret.keys.smtpSecure }}: {{ .Values.secret.values.smtpSecure | quote }}
|
||||
{{ .Values.secret.keys.githubConnectorAppClientSecret }}: {{ .Values.secret.values.githubConnectorAppClientSecret | quote }}
|
||||
{{ .Values.secret.keys.githubConnectorAppPrivateKey }}: {{ .Values.secret.values.githubConnectorAppPrivateKey | quote }}
|
||||
{{ .Values.secret.keys.githubConnectorAppWebhookSecret }}: {{ .Values.secret.values.githubConnectorAppWebhookSecret | quote }}
|
||||
{{ .Values.secret.keys.connectLinkPrivateKey }}: {{ .Values.secret.values.connectLinkPrivateKey | quote }}
|
||||
{{ .Values.secret.keys.initialAdminBootstrapCode }}: {{ .Values.secret.values.initialAdminBootstrapCode | quote }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,182 @@
|
||||
{{- include "openwork-ee.customCa.validate" . }}
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: {{ include "openwork-ee.fullname" . }}-env-probe
|
||||
labels:
|
||||
{{- include "openwork-ee.componentLabels" (dict "root" . "component" "env-probe") | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": test
|
||||
"helm.sh/hook-delete-policy": before-hook-creation
|
||||
spec:
|
||||
backoffLimit: 0
|
||||
activeDeadlineSeconds: 120
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "openwork-ee.componentSelectorLabels" (dict "root" . "component" "env-probe") | nindent 8 }}
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- if .Values.customCa.enabled }}
|
||||
volumes:
|
||||
{{- include "openwork-ee.customCa.volume" . | nindent 8 }}
|
||||
{{- end }}
|
||||
containers:
|
||||
- name: env-probe
|
||||
image: "{{ .Values.denApi.image.repository }}:{{ default .Values.image.tag .Values.denApi.image.tag }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
{{- if .Values.customCa.enabled }}
|
||||
volumeMounts:
|
||||
{{- include "openwork-ee.customCa.volumeMount" . | nindent 12 }}
|
||||
{{- end }}
|
||||
command:
|
||||
- node
|
||||
args:
|
||||
- -e
|
||||
- |
|
||||
const fs = require("node:fs");
|
||||
const tls = require("node:tls");
|
||||
|
||||
const expected = new Map([
|
||||
["DB_MODE", process.env.OPENWORK_ENV_PROBE_EXPECTED_DB_MODE],
|
||||
["DEN_ORG_MODE", process.env.OPENWORK_ENV_PROBE_EXPECTED_DEN_ORG_MODE],
|
||||
["PROVISIONER_MODE", process.env.OPENWORK_ENV_PROBE_EXPECTED_PROVISIONER_MODE],
|
||||
["OPENWORK_DEV_MODE", process.env.OPENWORK_ENV_PROBE_EXPECTED_OPENWORK_DEV_MODE],
|
||||
["DEN_BASE_URL", process.env.OPENWORK_ENV_PROBE_EXPECTED_DEN_BASE_URL],
|
||||
["DEN_AUTH_ORIGIN", process.env.OPENWORK_ENV_PROBE_EXPECTED_DEN_AUTH_ORIGIN],
|
||||
["PORT", process.env.OPENWORK_ENV_PROBE_EXPECTED_PORT],
|
||||
["NODE_OPTIONS", process.env.OPENWORK_ENV_PROBE_EXPECTED_NODE_OPTIONS],
|
||||
["DEN_API_NODE_OPTIONS", process.env.OPENWORK_ENV_PROBE_EXPECTED_NODE_OPTIONS],
|
||||
]);
|
||||
const required = [
|
||||
"DATABASE_URL",
|
||||
"DEN_DB_ENCRYPTION_KEY",
|
||||
"BETTER_AUTH_SECRET",
|
||||
"DEN_BASE_URL",
|
||||
"DB_MODE",
|
||||
"DEN_ORG_MODE",
|
||||
"PROVISIONER_MODE",
|
||||
"DEN_API_BASE",
|
||||
"DEN_AUTH_FALLBACK_BASE",
|
||||
"DEN_AUTH_ORIGIN",
|
||||
"OPENWORK_DEV_MODE",
|
||||
"PORT",
|
||||
"NODE_OPTIONS",
|
||||
];
|
||||
const secretPattern = /(SECRET|PASSWORD|PRIVATE|TOKEN|KEY|DATABASE_URL|BOOTSTRAP_CODE|SHA256)/;
|
||||
const failures = [];
|
||||
|
||||
function compactPem(value) {
|
||||
return value.replace(/\s+/g, "");
|
||||
}
|
||||
|
||||
function display(name, value) {
|
||||
if (value === undefined) return "<missing>";
|
||||
if (secretPattern.test(name)) return value ? "<set>" : "<empty>";
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
for (const name of required) {
|
||||
if (process.env[name] === undefined) {
|
||||
failures.push(`${name}: expected present, actual <missing>`);
|
||||
}
|
||||
}
|
||||
for (const [name, expectedValue] of expected) {
|
||||
if (expectedValue === undefined) continue;
|
||||
const actual = process.env[name];
|
||||
if (actual !== expectedValue) {
|
||||
failures.push(`${name}: expected ${display(name, expectedValue)}, actual ${display(name, actual)}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.OPENWORK_ENV_PROBE_EXPECTED_CUSTOM_CA === "1") {
|
||||
const expectedCaPath = process.env.OPENWORK_ENV_PROBE_EXPECTED_NODE_EXTRA_CA_CERTS;
|
||||
if (process.env.NODE_EXTRA_CA_CERTS !== expectedCaPath) {
|
||||
failures.push(`NODE_EXTRA_CA_CERTS: expected ${display("NODE_EXTRA_CA_CERTS", expectedCaPath)}, actual ${display("NODE_EXTRA_CA_CERTS", process.env.NODE_EXTRA_CA_CERTS)}`);
|
||||
} else {
|
||||
try {
|
||||
const bundle = fs.readFileSync(expectedCaPath, "utf8");
|
||||
const certs = bundle.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g) || [];
|
||||
if (certs.length === 0) {
|
||||
failures.push(`NODE_EXTRA_CA_CERTS: ${expectedCaPath} did not contain a PEM certificate`);
|
||||
} else if (typeof tls.getCACertificates !== "function") {
|
||||
failures.push("NODE_EXTRA_CA_CERTS: node does not expose tls.getCACertificates; cannot prove runtime trust");
|
||||
} else {
|
||||
const extra = tls.getCACertificates("extra").map(compactPem);
|
||||
const trusted = certs.some((cert) => extra.includes(compactPem(cert)));
|
||||
if (!trusted) {
|
||||
failures.push(`NODE_EXTRA_CA_CERTS: ${expectedCaPath} was mounted but did not appear in node's extra CA trust store`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
failures.push(`NODE_EXTRA_CA_CERTS: could not read ${expectedCaPath}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.length > 0) {
|
||||
console.error("OpenWork Helm env probe failed:");
|
||||
for (const failure of failures) console.error(`- ${failure}`);
|
||||
console.error("Observed runtime env:");
|
||||
for (const name of [...required, "DEN_API_NODE_OPTIONS", "NODE_EXTRA_CA_CERTS"].sort()) {
|
||||
console.error(` ${name}: ${display(name, process.env[name])}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("OpenWork Helm env probe passed.");
|
||||
console.log(`DEN_ORG_MODE=${process.env.DEN_ORG_MODE}; PROVISIONER_MODE=${process.env.PROVISIONER_MODE}; NODE_OPTIONS=${JSON.stringify(process.env.NODE_OPTIONS || "")}`);
|
||||
if (process.env.NODE_EXTRA_CA_CERTS) console.log(`NODE_EXTRA_CA_CERTS=${process.env.NODE_EXTRA_CA_CERTS} is loaded by node.`);
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "openwork-ee.configName" . }}
|
||||
- secretRef:
|
||||
name: {{ include "openwork-ee.secretName" . }}
|
||||
env:
|
||||
- name: PORT
|
||||
value: {{ .Values.denApi.containerPort | quote }}
|
||||
{{- if not (hasKey .Values.denApi.env "NODE_OPTIONS") }}
|
||||
- name: NODE_OPTIONS
|
||||
value: {{ .Values.config.denApiNodeOptions | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.installerArtifacts.enabled }}
|
||||
- name: OPENWORK_INSTALLER_ARTIFACTS_DIR
|
||||
value: {{ .Values.installerArtifacts.mountPath | quote }}
|
||||
{{- end }}
|
||||
{{- if .Values.customCa.enabled }}
|
||||
{{- include "openwork-ee.customCa.env" . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if eq (include "openwork-ee.allowPrivateMcpUrls" .) "1" }}
|
||||
- name: DEN_ALLOW_PRIVATE_MCP_URLS
|
||||
value: "1"
|
||||
{{- end }}
|
||||
{{- range $key, $value := .Values.denApi.env }}
|
||||
- name: {{ $key }}
|
||||
value: {{ $value | quote }}
|
||||
{{- end }}
|
||||
- name: OPENWORK_ENV_PROBE_EXPECTED_DB_MODE
|
||||
value: {{ .Values.config.databaseMode | quote }}
|
||||
- name: OPENWORK_ENV_PROBE_EXPECTED_DEN_ORG_MODE
|
||||
value: {{ .Values.config.tenancy.mode | quote }}
|
||||
- name: OPENWORK_ENV_PROBE_EXPECTED_PROVISIONER_MODE
|
||||
value: {{ .Values.config.provisioner.mode | quote }}
|
||||
- name: OPENWORK_ENV_PROBE_EXPECTED_OPENWORK_DEV_MODE
|
||||
value: {{ .Values.config.openworkDevMode | quote }}
|
||||
- name: OPENWORK_ENV_PROBE_EXPECTED_DEN_BASE_URL
|
||||
value: {{ .Values.config.public.webOrigin | quote }}
|
||||
- name: OPENWORK_ENV_PROBE_EXPECTED_DEN_AUTH_ORIGIN
|
||||
value: {{ .Values.config.public.webOrigin | quote }}
|
||||
- name: OPENWORK_ENV_PROBE_EXPECTED_PORT
|
||||
value: {{ .Values.denApi.containerPort | quote }}
|
||||
- name: OPENWORK_ENV_PROBE_EXPECTED_NODE_OPTIONS
|
||||
value: {{ if hasKey .Values.denApi.env "NODE_OPTIONS" }}{{ index .Values.denApi.env "NODE_OPTIONS" | quote }}{{ else }}{{ .Values.config.denApiNodeOptions | quote }}{{ end }}
|
||||
- name: OPENWORK_ENV_PROBE_EXPECTED_CUSTOM_CA
|
||||
value: {{ ternary "1" "0" .Values.customCa.enabled | quote }}
|
||||
{{- if .Values.customCa.enabled }}
|
||||
- name: OPENWORK_ENV_PROBE_EXPECTED_NODE_EXTRA_CA_CERTS
|
||||
value: {{ include "openwork-ee.customCa.filePath" . | quote }}
|
||||
{{- end }}
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
chart_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp_dir"' EXIT
|
||||
|
||||
assert_contains() {
|
||||
local file="$1"
|
||||
local needle="$2"
|
||||
if ! grep -F -q -- "$needle" "$file"; then
|
||||
printf 'Expected rendered chart to contain %s\n' "$needle" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
default_rendered="$tmp_dir/default.yaml"
|
||||
helm template openwork-ee "$chart_dir" > "$default_rendered"
|
||||
assert_contains "$default_rendered" 'DEN_AUTOMATIONS_ENABLED: "false"'
|
||||
assert_contains "$default_rendered" 'DEN_AUTOMATIONS_RUNTIME_ENABLED: "true"'
|
||||
|
||||
enabled_values="$tmp_dir/enabled-values.yaml"
|
||||
enabled_rendered="$tmp_dir/enabled.yaml"
|
||||
printf '%s\n' 'config:' ' public:' ' automationsEnabled: "true"' > "$enabled_values"
|
||||
helm template openwork-ee "$chart_dir" -f "$enabled_values" > "$enabled_rendered"
|
||||
assert_contains "$enabled_rendered" 'DEN_AUTOMATIONS_ENABLED: "true"'
|
||||
|
||||
disabled_values="$tmp_dir/disabled-values.yaml"
|
||||
disabled_rendered="$tmp_dir/disabled.yaml"
|
||||
printf '%s\n' 'config:' ' public:' ' automationsRuntimeEnabled: "false"' > "$disabled_values"
|
||||
helm template openwork-ee "$chart_dir" -f "$disabled_values" > "$disabled_rendered"
|
||||
assert_contains "$disabled_rendered" 'DEN_AUTOMATIONS_RUNTIME_ENABLED: "false"'
|
||||
|
||||
printf 'automations-enabled chart checks passed\n'
|
||||
@@ -0,0 +1,271 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { chmodSync, existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
const chartDir = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoRoot = join(chartDir, "..", "..", "..");
|
||||
const denDbDir = join(repoRoot, "ee", "packages", "den-db");
|
||||
const denDbPackage = join(denDbDir, "package.json");
|
||||
const denDbRequire = createRequire(denDbPackage);
|
||||
const mysql = denDbRequire("mysql2/promise");
|
||||
const bootstrapPath = join(denDbDir, "dist", "scripts", "bootstrap.js");
|
||||
const currentSchemaPath = join(denDbDir, "dist", "current-schema.sql");
|
||||
const denDbIndexPath = join(denDbDir, "dist", "index.js");
|
||||
const image = process.env.OPENWORK_EVAL_MYSQL_IMAGE?.trim() || "mysql:8.4";
|
||||
const containerName = `openwork-custom-ca-mysql-${process.pid}-${Date.now()}`;
|
||||
const tmp = mkdtempSync(join(tmpdir(), "openwork-custom-ca-mysql-"));
|
||||
const certsDir = join(tmp, "certs");
|
||||
const caKey = join(certsDir, "ca.key");
|
||||
const caCert = join(certsDir, "ca.crt");
|
||||
const serverKey = join(certsDir, "server.key");
|
||||
const serverCsr = join(certsDir, "server.csr");
|
||||
const serverCert = join(certsDir, "server.crt");
|
||||
const opensslConfig = join(tmp, "openssl.cnf");
|
||||
const mysqlConfig = join(tmp, "tls.cnf");
|
||||
const databaseName = "openwork_den";
|
||||
const databaseUser = "openwork";
|
||||
const databasePassword = "openwork_tls_test_password";
|
||||
let containerStarted = false;
|
||||
|
||||
function run(command, args, options = {}) {
|
||||
return spawnSync(command, args, {
|
||||
cwd: repoRoot,
|
||||
encoding: "utf8",
|
||||
maxBuffer: 20 * 1024 * 1024,
|
||||
timeout: 180_000,
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
function ensureSuccess(result, description) {
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`${description} failed\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`);
|
||||
}
|
||||
}
|
||||
|
||||
function runOpenSsl(args) {
|
||||
ensureSuccess(run("openssl", args), `openssl ${args.join(" ")}`);
|
||||
}
|
||||
|
||||
function ensureBuildArtifacts() {
|
||||
if (existsSync(bootstrapPath) && existsSync(currentSchemaPath) && existsSync(denDbIndexPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = run("pnpm", ["--dir", denDbDir, "run", "build"], { timeout: 240_000 });
|
||||
ensureSuccess(result, "pnpm --dir ee/packages/den-db run build");
|
||||
console.log("Built @openwork-ee/den-db migration artifacts");
|
||||
}
|
||||
|
||||
function generateCertificates() {
|
||||
mkdirSync(certsDir, { recursive: true });
|
||||
writeFileSync(
|
||||
opensslConfig,
|
||||
`[req]
|
||||
distinguished_name = dn
|
||||
prompt = no
|
||||
req_extensions = v3_req
|
||||
[dn]
|
||||
CN = localhost
|
||||
[v3_req]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = digitalSignature,keyEncipherment
|
||||
extendedKeyUsage = serverAuth
|
||||
subjectAltName = @alt_names
|
||||
[alt_names]
|
||||
DNS.1 = localhost
|
||||
DNS.2 = mysql.private.test
|
||||
IP.1 = 127.0.0.1
|
||||
`,
|
||||
);
|
||||
|
||||
runOpenSsl(["req", "-x509", "-newkey", "rsa:2048", "-nodes", "-days", "1", "-subj", "/CN=OpenWork Test MySQL CA", "-keyout", caKey, "-out", caCert]);
|
||||
runOpenSsl(["req", "-newkey", "rsa:2048", "-nodes", "-keyout", serverKey, "-out", serverCsr, "-config", opensslConfig]);
|
||||
runOpenSsl(["x509", "-req", "-in", serverCsr, "-CA", caCert, "-CAkey", caKey, "-CAcreateserial", "-out", serverCert, "-days", "1", "-extensions", "v3_req", "-extfile", opensslConfig]);
|
||||
chmodSync(serverKey, 0o644);
|
||||
chmodSync(serverCert, 0o644);
|
||||
chmodSync(caCert, 0o644);
|
||||
|
||||
writeFileSync(
|
||||
mysqlConfig,
|
||||
`[mysqld]
|
||||
ssl-ca=/etc/mysql/certs/ca.crt
|
||||
ssl-cert=/etc/mysql/certs/server.crt
|
||||
ssl-key=/etc/mysql/certs/server.key
|
||||
require_secure_transport=ON
|
||||
tls_version=TLSv1.2,TLSv1.3
|
||||
`,
|
||||
);
|
||||
chmodSync(mysqlConfig, 0o644);
|
||||
console.log("Generated private CA and MySQL server certificate with SAN DNS:localhost, DNS:mysql.private.test, IP:127.0.0.1");
|
||||
}
|
||||
|
||||
function docker(args, options = {}) {
|
||||
return run("docker", args, { timeout: 240_000, ...options });
|
||||
}
|
||||
|
||||
function startMysqlContainer() {
|
||||
const result = docker([
|
||||
"run",
|
||||
"--rm",
|
||||
"--detach",
|
||||
"--name",
|
||||
containerName,
|
||||
"--publish",
|
||||
"127.0.0.1::3306",
|
||||
"--env",
|
||||
"MYSQL_ROOT_PASSWORD=openwork_root_password",
|
||||
"--env",
|
||||
"MYSQL_ROOT_HOST=%",
|
||||
"--env",
|
||||
`MYSQL_DATABASE=${databaseName}`,
|
||||
"--env",
|
||||
`MYSQL_USER=${databaseUser}`,
|
||||
"--env",
|
||||
`MYSQL_PASSWORD=${databasePassword}`,
|
||||
"--volume",
|
||||
`${certsDir}:/etc/mysql/certs:ro`,
|
||||
"--volume",
|
||||
`${mysqlConfig}:/etc/mysql/conf.d/tls.cnf:ro`,
|
||||
image,
|
||||
]);
|
||||
ensureSuccess(result, `docker run ${image}`);
|
||||
containerStarted = true;
|
||||
console.log(`Started TLS-enabled MySQL container ${containerName} from ${image}`);
|
||||
}
|
||||
|
||||
function mappedPort() {
|
||||
const result = docker(["port", containerName, "3306/tcp"]);
|
||||
ensureSuccess(result, "docker port");
|
||||
const endpoint = result.stdout.trim().split("\n").find(Boolean);
|
||||
const port = endpoint?.split(":").pop();
|
||||
if (!port) {
|
||||
throw new Error(`Unable to resolve mapped MySQL port from: ${result.stdout}`);
|
||||
}
|
||||
return Number(port);
|
||||
}
|
||||
|
||||
async function waitForMysql(port) {
|
||||
const started = Date.now();
|
||||
let lastError = "";
|
||||
while (Date.now() - started < 90_000) {
|
||||
try {
|
||||
const connection = await mysql.createConnection({
|
||||
host: "127.0.0.1",
|
||||
port,
|
||||
user: databaseUser,
|
||||
password: databasePassword,
|
||||
database: databaseName,
|
||||
ssl: { rejectUnauthorized: false },
|
||||
});
|
||||
await connection.query("select 1");
|
||||
await connection.end();
|
||||
return;
|
||||
} catch (error) {
|
||||
lastError = error instanceof Error ? error.message : String(error);
|
||||
await new Promise((resolve) => setTimeout(resolve, 750));
|
||||
}
|
||||
}
|
||||
|
||||
const logs = docker(["logs", containerName], { timeout: 30_000 });
|
||||
throw new Error(`MySQL container did not become ready: ${lastError}\n${logs.stdout}\n${logs.stderr}`);
|
||||
}
|
||||
|
||||
function strictDatabaseUrl(port) {
|
||||
return `mysql://${databaseUser}:${databasePassword}@127.0.0.1:${port}/${databaseName}?sslmode=verify-full`;
|
||||
}
|
||||
|
||||
function redactedDatabaseUrl(port) {
|
||||
return `mysql://${databaseUser}:REDACTED@127.0.0.1:${port}/${databaseName}?sslmode=verify-full`;
|
||||
}
|
||||
|
||||
function childEnv(extra = {}) {
|
||||
const env = { ...process.env, ...extra };
|
||||
delete env.NODE_TLS_REJECT_UNAUTHORIZED;
|
||||
return env;
|
||||
}
|
||||
|
||||
function runOpenWorkMysqlQuery(databaseUrl, query, extraEnv = {}) {
|
||||
const source = `
|
||||
import { createRequire } from "node:module";
|
||||
const mysql = createRequire(process.argv[1])("mysql2/promise");
|
||||
const { parseMySqlConnectionConfig } = await import(process.argv[2]);
|
||||
const connection = await mysql.createConnection(parseMySqlConnectionConfig(process.argv[3]));
|
||||
try {
|
||||
const [rows] = await connection.query(process.argv[4]);
|
||||
console.log(JSON.stringify(rows));
|
||||
} finally {
|
||||
await connection.end();
|
||||
}
|
||||
`;
|
||||
|
||||
return run(process.execPath, ["--input-type=module", "-e", source, denDbPackage, pathToFileURL(denDbIndexPath).href, databaseUrl, query], {
|
||||
env: childEnv(extraEnv),
|
||||
timeout: 90_000,
|
||||
});
|
||||
}
|
||||
|
||||
function runBootstrap(databaseUrl) {
|
||||
return run(process.execPath, [bootstrapPath], {
|
||||
cwd: denDbDir,
|
||||
env: childEnv({
|
||||
DATABASE_URL: databaseUrl,
|
||||
DEN_DB_ENCRYPTION_KEY: "openwork-custom-ca-mysql-tls-test-key-1234567890",
|
||||
NODE_EXTRA_CA_CERTS: caCert,
|
||||
OPENWORK_DEN_DB_ENV_PATH: join(tmp, "does-not-exist.env"),
|
||||
}),
|
||||
timeout: 180_000,
|
||||
});
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
if (containerStarted) {
|
||||
docker(["rm", "-f", containerName], { timeout: 30_000 });
|
||||
}
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
try {
|
||||
ensureBuildArtifacts();
|
||||
generateCertificates();
|
||||
startMysqlContainer();
|
||||
const port = mappedPort();
|
||||
await waitForMysql(port);
|
||||
const databaseUrl = strictDatabaseUrl(port);
|
||||
console.log(`TLS MySQL endpoint ready at ${redactedDatabaseUrl(port)}`);
|
||||
|
||||
const withoutCa = runOpenWorkMysqlQuery(databaseUrl, "select 1 as ok");
|
||||
if (withoutCa.status === 0) {
|
||||
throw new Error("Strict OpenWork/mysql2 unexpectedly connected without NODE_EXTRA_CA_CERTS");
|
||||
}
|
||||
const withoutCaError = `${withoutCa.stdout}\n${withoutCa.stderr}`.trim();
|
||||
if (!/certificate|verify|self-signed|unable/i.test(withoutCaError)) {
|
||||
throw new Error(`Strict OpenWork/mysql2 failed for a non-certificate reason:\n${withoutCaError}`);
|
||||
}
|
||||
const certificateError = withoutCaError.split("\n").find((line) => /certificate|verify|self-signed|unable/i.test(line)) ?? withoutCaError.split("\n").find(Boolean);
|
||||
console.log(`Strict OpenWork/mysql2 without custom CA: rejected (${certificateError})`);
|
||||
|
||||
const bootstrap = runBootstrap(databaseUrl);
|
||||
ensureSuccess(bootstrap, "OpenWork migration bootstrap with NODE_EXTRA_CA_CERTS");
|
||||
console.log("OpenWork migration bootstrap completed with NODE_EXTRA_CA_CERTS and strict DATABASE_URL");
|
||||
console.log(bootstrap.stdout.trim());
|
||||
|
||||
const query = runOpenWorkMysqlQuery(
|
||||
databaseUrl,
|
||||
"select (select count(*) from information_schema.tables where table_schema = database()) as table_count, (select count(*) from `__drizzle_migrations`) as migration_count",
|
||||
{ NODE_EXTRA_CA_CERTS: caCert },
|
||||
);
|
||||
ensureSuccess(query, "OpenWork/mysql2 strict post-migration query with NODE_EXTRA_CA_CERTS");
|
||||
const rows = JSON.parse(query.stdout.trim());
|
||||
const tableCount = Number(rows[0]?.table_count ?? 0);
|
||||
const migrationCount = Number(rows[0]?.migration_count ?? 0);
|
||||
if (tableCount <= 0 || migrationCount <= 0) {
|
||||
throw new Error(`Expected migrated OpenWork tables and migration ledger rows, got ${query.stdout}`);
|
||||
}
|
||||
console.log(`OpenWork/mysql2 strict query succeeded after migration: tables=${tableCount}, migrations=${migrationCount}`);
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import { createServer } from "node:https";
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
const tmp = mkdtempSync(join(tmpdir(), "openwork-custom-ca-"));
|
||||
const caKey = join(tmp, "ca.key");
|
||||
const caCert = join(tmp, "ca.crt");
|
||||
const serverKey = join(tmp, "server.key");
|
||||
const serverCsr = join(tmp, "server.csr");
|
||||
const serverCert = join(tmp, "server.crt");
|
||||
const opensslConfig = join(tmp, "openssl.cnf");
|
||||
|
||||
function runOpenSsl(args) {
|
||||
const result = spawnSync("openssl", args, { encoding: "utf8" });
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`openssl ${args.join(" ")} failed\n${result.stderr}`);
|
||||
}
|
||||
}
|
||||
|
||||
function clientEnvironment(caPath) {
|
||||
const env = { ...process.env };
|
||||
delete env.NODE_EXTRA_CA_CERTS;
|
||||
delete env.NODE_TLS_REJECT_UNAUTHORIZED;
|
||||
if (caPath) {
|
||||
env.NODE_EXTRA_CA_CERTS = caPath;
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
function runClient(url, caPath) {
|
||||
const clientSource = `
|
||||
const https = require("node:https");
|
||||
https.get(process.argv[1], (response) => {
|
||||
let body = "";
|
||||
response.setEncoding("utf8");
|
||||
response.on("data", (chunk) => { body += chunk; });
|
||||
response.on("end", () => {
|
||||
if (response.statusCode === 200 && body === "openwork-custom-ca-ok") {
|
||||
console.log(body);
|
||||
process.exit(0);
|
||||
}
|
||||
console.error(\`unexpected response: \${response.statusCode} \${body}\`);
|
||||
process.exit(1);
|
||||
});
|
||||
}).on("error", (error) => {
|
||||
console.error(error.code || error.message);
|
||||
process.exit(2);
|
||||
});
|
||||
`;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const child = spawn(process.execPath, ["-e", clientSource, url], {
|
||||
env: clientEnvironment(caPath),
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
child.stdout.on("data", (chunk) => {
|
||||
stdout += chunk;
|
||||
});
|
||||
child.stderr.on("data", (chunk) => {
|
||||
stderr += chunk;
|
||||
});
|
||||
child.on("close", (status) => {
|
||||
resolve({ status, stdout, stderr });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function listen(server) {
|
||||
return new Promise((resolve, reject) => {
|
||||
server.once("error", reject);
|
||||
server.listen(0, "127.0.0.1", () => {
|
||||
server.off("error", reject);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function close(server) {
|
||||
return new Promise((resolve, reject) => {
|
||||
server.close((error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
writeFileSync(
|
||||
opensslConfig,
|
||||
`[req]
|
||||
distinguished_name = dn
|
||||
prompt = no
|
||||
[dn]
|
||||
CN = localhost
|
||||
[v3_req]
|
||||
subjectAltName = @alt_names
|
||||
[alt_names]
|
||||
DNS.1 = localhost
|
||||
IP.1 = 127.0.0.1
|
||||
`,
|
||||
);
|
||||
|
||||
runOpenSsl(["req", "-x509", "-newkey", "rsa:2048", "-nodes", "-days", "1", "-subj", "/CN=OpenWork Test CA", "-keyout", caKey, "-out", caCert]);
|
||||
runOpenSsl(["req", "-newkey", "rsa:2048", "-nodes", "-keyout", serverKey, "-out", serverCsr, "-config", opensslConfig]);
|
||||
runOpenSsl(["x509", "-req", "-in", serverCsr, "-CA", caCert, "-CAkey", caKey, "-CAcreateserial", "-out", serverCert, "-days", "1", "-extensions", "v3_req", "-extfile", opensslConfig]);
|
||||
|
||||
const server = createServer(
|
||||
{
|
||||
key: readFileSync(serverKey),
|
||||
cert: readFileSync(serverCert),
|
||||
},
|
||||
(_request, response) => {
|
||||
response.end("openwork-custom-ca-ok");
|
||||
},
|
||||
);
|
||||
|
||||
await listen(server);
|
||||
const address = server.address();
|
||||
const url = `https://127.0.0.1:${address.port}/`;
|
||||
|
||||
try {
|
||||
const withoutCustomCa = await runClient(url);
|
||||
if (withoutCustomCa.status === 0) {
|
||||
throw new Error("Strict TLS unexpectedly trusted the private CA without NODE_EXTRA_CA_CERTS");
|
||||
}
|
||||
|
||||
const withCustomCa = await runClient(url, caCert);
|
||||
if (withCustomCa.status !== 0) {
|
||||
throw new Error(`Strict TLS did not trust NODE_EXTRA_CA_CERTS=${caCert}\n${withCustomCa.stderr}`);
|
||||
}
|
||||
|
||||
console.log(`Strict TLS without NODE_EXTRA_CA_CERTS: rejected (${withoutCustomCa.stderr.trim()})`);
|
||||
console.log(`Strict TLS with NODE_EXTRA_CA_CERTS=${caCert}: accepted (${withCustomCa.stdout.trim()})`);
|
||||
console.log("Focused runtime trust check passed; this verifies Node's strict TLS trust path without starting MySQL.");
|
||||
} finally {
|
||||
await close(server);
|
||||
}
|
||||
} finally {
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
chart_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp_dir"' EXIT
|
||||
|
||||
assert_count() {
|
||||
local file="$1"
|
||||
local needle="$2"
|
||||
local expected="$3"
|
||||
local count
|
||||
count="$(grep -F -c -- "$needle" "$file" || true)"
|
||||
if [[ "$count" != "$expected" ]]; then
|
||||
printf 'Expected %s occurrences of %s, found %s\n' "$expected" "$needle" "$count" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_contains() {
|
||||
local file="$1"
|
||||
local needle="$2"
|
||||
if ! grep -F -q -- "$needle" "$file"; then
|
||||
printf 'Expected rendered chart to contain %s\n' "$needle" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_contains() {
|
||||
local file="$1"
|
||||
local needle="$2"
|
||||
if grep -F -q -- "$needle" "$file"; then
|
||||
printf 'Expected rendered chart not to contain %s\n' "$needle" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_failure() {
|
||||
local values_file="$1"
|
||||
local expected="$2"
|
||||
local output_file="$tmp_dir/failure-output.yaml"
|
||||
local error_file="$tmp_dir/failure-error.txt"
|
||||
|
||||
if helm template openwork-ee "$chart_dir" -f "$values_file" > "$output_file" 2> "$error_file"; then
|
||||
printf 'Expected helm template to fail for %s\n' "$values_file" >&2
|
||||
return 1
|
||||
fi
|
||||
assert_contains "$error_file" "$expected"
|
||||
}
|
||||
|
||||
default_rendered="$tmp_dir/default.yaml"
|
||||
disabled_values="$tmp_dir/disabled-values.yaml"
|
||||
disabled_rendered="$tmp_dir/disabled.yaml"
|
||||
helm template openwork-ee "$chart_dir" --set inference.enabled=true > "$default_rendered"
|
||||
cat > "$disabled_values" <<'YAML'
|
||||
inference:
|
||||
enabled: true
|
||||
customCa:
|
||||
enabled: false
|
||||
existingSecret: ignored-ca-secret
|
||||
key: ignored.pem
|
||||
YAML
|
||||
helm template openwork-ee "$chart_dir" -f "$disabled_values" > "$disabled_rendered"
|
||||
cmp -s "$default_rendered" "$disabled_rendered"
|
||||
assert_not_contains "$default_rendered" 'name: NODE_EXTRA_CA_CERTS'
|
||||
assert_not_contains "$default_rendered" '/etc/openwork/custom-ca'
|
||||
assert_not_contains "$default_rendered" 'name: custom-ca'
|
||||
|
||||
secret_values="$tmp_dir/secret-values.yaml"
|
||||
secret_rendered="$tmp_dir/secret.yaml"
|
||||
cat > "$secret_values" <<'YAML'
|
||||
customCa:
|
||||
enabled: true
|
||||
existingSecret: openwork-ca-secret
|
||||
key: corp-root.pem
|
||||
YAML
|
||||
helm template openwork-ee "$chart_dir" -f "$secret_values" > "$secret_rendered"
|
||||
assert_count "$secret_rendered" 'name: NODE_EXTRA_CA_CERTS' 4
|
||||
assert_count "$secret_rendered" 'value: "/etc/openwork/custom-ca/ca-bundle.pem"' 5
|
||||
assert_count "$secret_rendered" 'name: custom-ca' 8
|
||||
assert_count "$secret_rendered" 'secretName: "openwork-ca-secret"' 4
|
||||
assert_count "$secret_rendered" 'key: "corp-root.pem"' 4
|
||||
assert_count "$secret_rendered" 'path: ca-bundle.pem' 4
|
||||
assert_count "$secret_rendered" 'mountPath: "/etc/openwork/custom-ca"' 4
|
||||
assert_not_contains "$secret_rendered" 'subPath:'
|
||||
assert_not_contains "$secret_rendered" 'name: "openwork-ca-config"'
|
||||
assert_not_contains "$secret_rendered" 'name: openwork-ee-inference'
|
||||
|
||||
configmap_values="$tmp_dir/configmap-values.yaml"
|
||||
configmap_rendered="$tmp_dir/configmap.yaml"
|
||||
cat > "$configmap_values" <<'YAML'
|
||||
inference:
|
||||
enabled: true
|
||||
customCa:
|
||||
enabled: true
|
||||
existingConfigMap: openwork-ca-config
|
||||
key: ca.crt
|
||||
YAML
|
||||
helm template openwork-ee "$chart_dir" -f "$configmap_values" > "$configmap_rendered"
|
||||
assert_contains "$configmap_rendered" 'name: openwork-ee-den-api'
|
||||
assert_contains "$configmap_rendered" 'name: openwork-ee-den-web'
|
||||
assert_contains "$configmap_rendered" 'name: openwork-ee-inference'
|
||||
assert_contains "$configmap_rendered" 'name: openwork-ee-migrate'
|
||||
assert_count "$configmap_rendered" 'name: NODE_EXTRA_CA_CERTS' 5
|
||||
assert_count "$configmap_rendered" 'value: "/etc/openwork/custom-ca/ca-bundle.pem"' 6
|
||||
assert_count "$configmap_rendered" 'name: custom-ca' 10
|
||||
assert_count "$configmap_rendered" 'name: "openwork-ca-config"' 5
|
||||
assert_count "$configmap_rendered" 'key: "ca.crt"' 5
|
||||
assert_count "$configmap_rendered" 'path: ca-bundle.pem' 5
|
||||
assert_count "$configmap_rendered" 'mountPath: "/etc/openwork/custom-ca"' 5
|
||||
assert_not_contains "$configmap_rendered" 'subPath:'
|
||||
assert_not_contains "$configmap_rendered" 'secretName: "openwork-ca-secret"'
|
||||
|
||||
installer_values="$tmp_dir/installer-values.yaml"
|
||||
installer_rendered="$tmp_dir/installer.yaml"
|
||||
cat > "$installer_values" <<'YAML'
|
||||
customCa:
|
||||
enabled: true
|
||||
existingSecret: openwork-ca-secret
|
||||
installerArtifacts:
|
||||
enabled: true
|
||||
existingClaim: installer-artifacts-pvc
|
||||
YAML
|
||||
helm template openwork-ee "$chart_dir" -f "$installer_values" > "$installer_rendered"
|
||||
assert_contains "$installer_rendered" 'name: installer-artifacts'
|
||||
assert_contains "$installer_rendered" 'claimName: "installer-artifacts-pvc"'
|
||||
assert_contains "$installer_rendered" 'name: OPENWORK_INSTALLER_ARTIFACTS_DIR'
|
||||
assert_contains "$installer_rendered" 'name: custom-ca'
|
||||
assert_contains "$installer_rendered" 'name: NODE_EXTRA_CA_CERTS'
|
||||
|
||||
missing_source_values="$tmp_dir/missing-source-values.yaml"
|
||||
cat > "$missing_source_values" <<'YAML'
|
||||
customCa:
|
||||
enabled: true
|
||||
YAML
|
||||
assert_failure "$missing_source_values" 'customCa.existingSecret or customCa.existingConfigMap is required when customCa.enabled=true'
|
||||
|
||||
multiple_sources_values="$tmp_dir/multiple-sources-values.yaml"
|
||||
cat > "$multiple_sources_values" <<'YAML'
|
||||
customCa:
|
||||
enabled: true
|
||||
existingSecret: openwork-ca-secret
|
||||
existingConfigMap: openwork-ca-config
|
||||
YAML
|
||||
assert_failure "$multiple_sources_values" 'customCa.existingSecret and customCa.existingConfigMap are mutually exclusive when customCa.enabled=true'
|
||||
|
||||
empty_key_values="$tmp_dir/empty-key-values.yaml"
|
||||
cat > "$empty_key_values" <<'YAML'
|
||||
customCa:
|
||||
enabled: true
|
||||
existingSecret: openwork-ca-secret
|
||||
key: ""
|
||||
YAML
|
||||
assert_failure "$empty_key_values" 'customCa.key is required when customCa.enabled=true'
|
||||
|
||||
den_api_conflict_values="$tmp_dir/den-api-conflict-values.yaml"
|
||||
cat > "$den_api_conflict_values" <<'YAML'
|
||||
customCa:
|
||||
enabled: true
|
||||
existingSecret: openwork-ca-secret
|
||||
denApi:
|
||||
env:
|
||||
NODE_EXTRA_CA_CERTS: /already/set.pem
|
||||
YAML
|
||||
assert_failure "$den_api_conflict_values" 'denApi.env.NODE_EXTRA_CA_CERTS conflicts with customCa.enabled=true; remove it and use customCa instead'
|
||||
|
||||
den_web_conflict_values="$tmp_dir/den-web-conflict-values.yaml"
|
||||
cat > "$den_web_conflict_values" <<'YAML'
|
||||
customCa:
|
||||
enabled: true
|
||||
existingSecret: openwork-ca-secret
|
||||
denWeb:
|
||||
env:
|
||||
NODE_EXTRA_CA_CERTS: /already/set.pem
|
||||
YAML
|
||||
assert_failure "$den_web_conflict_values" 'denWeb.env.NODE_EXTRA_CA_CERTS conflicts with customCa.enabled=true; remove it and use customCa instead'
|
||||
|
||||
inference_conflict_values="$tmp_dir/inference-conflict-values.yaml"
|
||||
cat > "$inference_conflict_values" <<'YAML'
|
||||
customCa:
|
||||
enabled: true
|
||||
existingSecret: openwork-ca-secret
|
||||
inference:
|
||||
env:
|
||||
NODE_EXTRA_CA_CERTS: /already/set.pem
|
||||
YAML
|
||||
assert_failure "$inference_conflict_values" 'inference.env.NODE_EXTRA_CA_CERTS conflicts with customCa.enabled=true; remove it and use customCa instead'
|
||||
|
||||
node "$chart_dir/tests/custom-ca-node-trust.mjs"
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
chart_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp_dir"' EXIT
|
||||
|
||||
assert_contains() {
|
||||
local file="$1"
|
||||
local needle="$2"
|
||||
if ! grep -F -q -- "$needle" "$file"; then
|
||||
printf 'Expected rendered chart to contain %s\n' "$needle" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
default_rendered="$tmp_dir/default.yaml"
|
||||
helm template openwork-ee "$chart_dir" > "$default_rendered"
|
||||
assert_contains "$default_rendered" 'DEN_DASHBOARDS_ENABLED: "false"'
|
||||
|
||||
enabled_values="$tmp_dir/enabled-values.yaml"
|
||||
enabled_rendered="$tmp_dir/enabled.yaml"
|
||||
printf '%s\n' 'config:' ' public:' ' dashboardsEnabled: "true"' > "$enabled_values"
|
||||
helm template openwork-ee "$chart_dir" -f "$enabled_values" > "$enabled_rendered"
|
||||
assert_contains "$enabled_rendered" 'DEN_DASHBOARDS_ENABLED: "true"'
|
||||
|
||||
printf 'dashboards-enabled chart checks passed\n'
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
chart_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
repo_root="$(cd "$chart_dir/../../.." && pwd)"
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp_dir"' EXIT
|
||||
|
||||
assert_count() {
|
||||
local file="$1"
|
||||
local needle="$2"
|
||||
local expected="$3"
|
||||
local count
|
||||
count="$(grep -F -c -- "$needle" "$file" || true)"
|
||||
if [[ "$count" != "$expected" ]]; then
|
||||
printf 'Expected %s occurrences of %s, found %s\n' "$expected" "$needle" "$count" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_contains() {
|
||||
local file="$1"
|
||||
local needle="$2"
|
||||
if ! grep -F -q -- "$needle" "$file"; then
|
||||
printf 'Expected rendered chart to contain %s\n' "$needle" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_contains() {
|
||||
local file="$1"
|
||||
local needle="$2"
|
||||
if grep -F -q -- "$needle" "$file"; then
|
||||
printf 'Expected rendered chart not to contain %s\n' "$needle" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_file_contains() {
|
||||
assert_contains "$1" "$2"
|
||||
}
|
||||
|
||||
assert_file_not_contains() {
|
||||
assert_not_contains "$1" "$2"
|
||||
}
|
||||
|
||||
default_rendered="$tmp_dir/default.yaml"
|
||||
helm template openwork-ee "$chart_dir" --set inference.enabled=true > "$default_rendered"
|
||||
assert_contains "$default_rendered" 'DEN_API_NODE_OPTIONS: ""'
|
||||
assert_count "$default_rendered" 'name: NODE_OPTIONS' 2
|
||||
assert_count "$default_rendered" 'valueFrom: null' 1
|
||||
assert_not_contains "$default_rendered" 'key: DEN_API_NODE_OPTIONS'
|
||||
assert_count "$default_rendered" 'name: DEN_OBSERVABILITY_BACKEND' 2
|
||||
assert_count "$default_rendered" 'value: "none"' 2
|
||||
assert_not_contains "$default_rendered" 'name: OTEL_EXPORTER_OTLP_HEADERS'
|
||||
assert_not_contains "$default_rendered" 'name: SENTRY_DSN'
|
||||
|
||||
otel_values="$tmp_dir/otel-values.yaml"
|
||||
otel_rendered="$tmp_dir/otel.yaml"
|
||||
cat > "$otel_values" <<'YAML'
|
||||
inference:
|
||||
enabled: true
|
||||
config:
|
||||
denApiNodeOptions: --max-old-space-size=4096
|
||||
observability:
|
||||
backend: otel
|
||||
otel:
|
||||
endpoint: https://otel.example.test/otlp
|
||||
tracesEndpoint: https://otel.example.test/v1/traces
|
||||
metricsEndpoint: https://otel.example.test/v1/metrics
|
||||
logsEndpoint: https://otel.example.test/v1/logs
|
||||
exporters:
|
||||
metrics: none
|
||||
tracesSampler: parentbased_always_on
|
||||
headers:
|
||||
existingSecret: otel-runtime
|
||||
key: otlp-headers
|
||||
denApi:
|
||||
env:
|
||||
CUSTOM_DEN_API_ENV: api
|
||||
denWeb:
|
||||
env:
|
||||
CUSTOM_DEN_WEB_ENV: web
|
||||
YAML
|
||||
helm template openwork-ee "$chart_dir" -f "$otel_values" > "$otel_rendered"
|
||||
assert_contains "$otel_rendered" 'DEN_API_NODE_OPTIONS: "--max-old-space-size=4096"'
|
||||
assert_contains "$otel_rendered" 'value: "--max-old-space-size=4096"'
|
||||
assert_count "$otel_rendered" 'name: DEN_OBSERVABILITY_BACKEND' 2
|
||||
assert_count "$otel_rendered" 'value: "otel"' 2
|
||||
assert_count "$otel_rendered" 'name: OTEL_EXPORTER_OTLP_HEADERS' 2
|
||||
assert_count "$otel_rendered" 'name: OTEL_SERVICE_NAME' 2
|
||||
assert_count "$otel_rendered" 'name: OTEL_METRICS_EXPORTER' 2
|
||||
assert_count "$otel_rendered" 'value: "parentbased_always_on"' 2
|
||||
assert_not_contains "$otel_rendered" 'name: OTEL_TRACES_SAMPLER_ARG'
|
||||
assert_contains "$otel_rendered" 'value: "den-api"'
|
||||
assert_contains "$otel_rendered" 'value: "den-web"'
|
||||
assert_contains "$otel_rendered" 'name: "otel-runtime"'
|
||||
assert_contains "$otel_rendered" 'key: "otlp-headers"'
|
||||
assert_contains "$otel_rendered" 'name: CUSTOM_DEN_API_ENV'
|
||||
assert_contains "$otel_rendered" 'name: CUSTOM_DEN_WEB_ENV'
|
||||
assert_not_contains "$otel_rendered" 'name: SENTRY_AUTH_TOKEN'
|
||||
|
||||
legacy_values="$tmp_dir/legacy-node-options-values.yaml"
|
||||
legacy_rendered="$tmp_dir/legacy-node-options.yaml"
|
||||
cat > "$legacy_values" <<'YAML'
|
||||
denApi:
|
||||
env:
|
||||
NODE_OPTIONS: --tls-max-v1.2
|
||||
YAML
|
||||
helm template openwork-ee "$chart_dir" -f "$legacy_values" > "$legacy_rendered"
|
||||
assert_count "$legacy_rendered" 'name: NODE_OPTIONS' 2
|
||||
assert_contains "$legacy_rendered" 'value: "--tls-max-v1.2"'
|
||||
assert_not_contains "$legacy_rendered" 'valueFrom: null'
|
||||
assert_not_contains "$legacy_rendered" 'key: DEN_API_NODE_OPTIONS'
|
||||
|
||||
sentry_values="$tmp_dir/sentry-values.yaml"
|
||||
sentry_rendered="$tmp_dir/sentry.yaml"
|
||||
cat > "$sentry_values" <<'YAML'
|
||||
inference:
|
||||
enabled: true
|
||||
observability:
|
||||
backend: sentry
|
||||
sentry:
|
||||
dsnSecret:
|
||||
existingSecret: sentry-runtime
|
||||
key: dsn
|
||||
tracesSampleRate: "0.75"
|
||||
environment: production
|
||||
release: "2026.07.11"
|
||||
dist: web
|
||||
YAML
|
||||
helm template openwork-ee "$chart_dir" -f "$sentry_values" > "$sentry_rendered"
|
||||
assert_count "$sentry_rendered" 'name: DEN_OBSERVABILITY_BACKEND' 2
|
||||
assert_count "$sentry_rendered" 'value: "sentry"' 2
|
||||
assert_count "$sentry_rendered" 'name: SENTRY_DSN' 2
|
||||
assert_contains "$sentry_rendered" 'name: "sentry-runtime"'
|
||||
assert_not_contains "$sentry_rendered" 'SENTRY_AUTH_TOKEN'
|
||||
assert_not_contains "$sentry_rendered" 'SENTRY_ORG'
|
||||
assert_not_contains "$sentry_rendered" 'SENTRY_PROJECT'
|
||||
assert_not_contains "$sentry_rendered" 'SENTRY_URL'
|
||||
assert_not_contains "$sentry_rendered" 'name: OTEL_EXPORTER_OTLP_HEADERS'
|
||||
|
||||
assert_file_contains "$repo_root/packaging/docker/Dockerfile.den" '/app/ee/apps/den-api/dist/main.js'
|
||||
assert_file_not_contains "$repo_root/packaging/docker/Dockerfile.den" '/app/ee/apps/den-api/dist/server.js'
|
||||
assert_file_contains "$repo_root/packaging/docker/docker-compose.den-dev.yml" '/app/ee/apps/den-api/dist/main.js'
|
||||
assert_file_not_contains "$repo_root/packaging/docker/docker-compose.den-dev.yml" '/app/ee/apps/den-api/dist/server.js'
|
||||
assert_file_contains "$repo_root/.devcontainer/start-daytona-server.sh" 'pnpm --filter @openwork-ee/den-api exec tsx watch src/main.ts'
|
||||
assert_file_not_contains "$repo_root/.devcontainer/start-daytona-server.sh" 'ee/apps/den-api/src/server.ts'
|
||||
assert_file_contains "$repo_root/evals/packages/hosts/src/den-stack.ts" '"tsx", "src/main.ts"'
|
||||
assert_file_not_contains "$repo_root/evals/packages/hosts/src/den-stack.ts" '"tsx", "src/server.ts"'
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
chart_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp_dir"' EXIT
|
||||
|
||||
assert_contains() {
|
||||
local file="$1"
|
||||
local needle="$2"
|
||||
if ! grep -F -q -- "$needle" "$file"; then
|
||||
printf 'Expected rendered chart to contain %s\n' "$needle" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
default_rendered="$tmp_dir/default.yaml"
|
||||
helm template openwork-ee "$chart_dir" > "$default_rendered"
|
||||
assert_contains "$default_rendered" 'DEN_OPENWORK_WEB_ENABLED: "false"'
|
||||
|
||||
enabled_values="$tmp_dir/enabled-values.yaml"
|
||||
enabled_rendered="$tmp_dir/enabled.yaml"
|
||||
printf '%s\n' 'config:' ' public:' ' openworkWebEnabled: "true"' > "$enabled_values"
|
||||
helm template openwork-ee "$chart_dir" -f "$enabled_values" > "$enabled_rendered"
|
||||
assert_contains "$enabled_rendered" 'DEN_OPENWORK_WEB_ENABLED: "true"'
|
||||
|
||||
printf 'openwork-web-enabled chart checks passed\n'
|
||||
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
chart_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp_dir"' EXIT
|
||||
|
||||
assert_count() {
|
||||
local file="$1"
|
||||
local needle="$2"
|
||||
local expected="$3"
|
||||
local count
|
||||
count="$(grep -F -c -- "$needle" "$file" || true)"
|
||||
if [[ "$count" != "$expected" ]]; then
|
||||
printf 'Expected %s occurrences of %s, found %s\n' "$expected" "$needle" "$count" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_contains() {
|
||||
local file="$1"
|
||||
local needle="$2"
|
||||
if ! grep -F -q -- "$needle" "$file"; then
|
||||
printf 'Expected rendered chart to contain %s\n' "$needle" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_contains() {
|
||||
local file="$1"
|
||||
local needle="$2"
|
||||
if grep -F -q -- "$needle" "$file"; then
|
||||
printf 'Expected rendered chart not to contain %s\n' "$needle" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_failure() {
|
||||
local values_file="$1"
|
||||
local expected="$2"
|
||||
local output_file="$tmp_dir/failure-output.yaml"
|
||||
local error_file="$tmp_dir/failure-error.txt"
|
||||
|
||||
if helm template openwork-ee "$chart_dir" -f "$values_file" > "$output_file" 2> "$error_file"; then
|
||||
printf 'Expected helm template to fail for %s\n' "$values_file" >&2
|
||||
return 1
|
||||
fi
|
||||
assert_contains "$error_file" "$expected"
|
||||
}
|
||||
|
||||
assert_source_contains() {
|
||||
local file="$1"
|
||||
local source="$2"
|
||||
local needle="$3"
|
||||
local in_source=0
|
||||
local found=0
|
||||
local line
|
||||
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" == '# Source: openwork-ee/templates/'* ]]; then
|
||||
if [[ "$line" == "# Source: openwork-ee/templates/$source" ]]; then
|
||||
in_source=1
|
||||
else
|
||||
in_source=0
|
||||
fi
|
||||
fi
|
||||
if [[ "$in_source" == 1 && "$line" == *"$needle"* ]]; then
|
||||
found=1
|
||||
break
|
||||
fi
|
||||
done < "$file"
|
||||
|
||||
if [[ "$found" != 1 ]]; then
|
||||
printf 'Expected %s to contain %s\n' "$source" "$needle" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
assert_source_not_contains() {
|
||||
local file="$1"
|
||||
local source="$2"
|
||||
local needle="$3"
|
||||
local in_source=0
|
||||
local line
|
||||
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" == '# Source: openwork-ee/templates/'* ]]; then
|
||||
if [[ "$line" == "# Source: openwork-ee/templates/$source" ]]; then
|
||||
in_source=1
|
||||
else
|
||||
in_source=0
|
||||
fi
|
||||
fi
|
||||
if [[ "$in_source" == 1 && "$line" == *"$needle"* ]]; then
|
||||
printf 'Expected %s not to contain %s\n' "$source" "$needle" >&2
|
||||
return 1
|
||||
fi
|
||||
done < "$file"
|
||||
}
|
||||
|
||||
default_rendered="$tmp_dir/default.yaml"
|
||||
helm template openwork-ee "$chart_dir" > "$default_rendered"
|
||||
assert_count "$default_rendered" 'DEN_ALLOW_PRIVATE_MCP_URLS' 0
|
||||
assert_not_contains "$default_rendered" 'openwork-ee-den-api-private-mcp-urls'
|
||||
|
||||
enabled_values="$tmp_dir/enabled-values.yaml"
|
||||
enabled_rendered="$tmp_dir/enabled.yaml"
|
||||
cat > "$enabled_values" <<'YAML'
|
||||
inference:
|
||||
enabled: true
|
||||
config:
|
||||
public:
|
||||
allowPrivateMcpUrls: "1"
|
||||
YAML
|
||||
helm template openwork-ee "$chart_dir" -f "$enabled_values" > "$enabled_rendered"
|
||||
assert_count "$enabled_rendered" 'DEN_ALLOW_PRIVATE_MCP_URLS' 2
|
||||
assert_count "$enabled_rendered" 'name: DEN_ALLOW_PRIVATE_MCP_URLS' 2
|
||||
assert_count "$enabled_rendered" 'value: "1"' 2
|
||||
assert_not_contains "$enabled_rendered" 'key: DEN_ALLOW_PRIVATE_MCP_URLS'
|
||||
assert_not_contains "$enabled_rendered" 'configMapKeyRef:'
|
||||
assert_not_contains "$enabled_rendered" 'openwork-ee-den-api-private-mcp-urls'
|
||||
assert_source_contains "$enabled_rendered" 'den-api.yaml' 'name: DEN_ALLOW_PRIVATE_MCP_URLS'
|
||||
assert_source_contains "$enabled_rendered" 'den-api.yaml' 'value: "1"'
|
||||
assert_source_not_contains "$enabled_rendered" 'den-web.yaml' 'DEN_ALLOW_PRIVATE_MCP_URLS'
|
||||
assert_source_not_contains "$enabled_rendered" 'inference.yaml' 'DEN_ALLOW_PRIVATE_MCP_URLS'
|
||||
|
||||
disabled_values="$tmp_dir/disabled-values.yaml"
|
||||
disabled_rendered="$tmp_dir/disabled.yaml"
|
||||
cat > "$disabled_values" <<'YAML'
|
||||
config:
|
||||
public:
|
||||
allowPrivateMcpUrls: "0"
|
||||
YAML
|
||||
helm template openwork-ee "$chart_dir" -f "$disabled_values" > "$disabled_rendered"
|
||||
assert_count "$disabled_rendered" 'DEN_ALLOW_PRIVATE_MCP_URLS' 0
|
||||
|
||||
false_values="$tmp_dir/false-values.yaml"
|
||||
false_rendered="$tmp_dir/false.yaml"
|
||||
cat > "$false_values" <<'YAML'
|
||||
config:
|
||||
public:
|
||||
allowPrivateMcpUrls: "false"
|
||||
YAML
|
||||
helm template openwork-ee "$chart_dir" -f "$false_values" > "$false_rendered"
|
||||
assert_count "$false_rendered" 'DEN_ALLOW_PRIVATE_MCP_URLS' 0
|
||||
|
||||
blank_values="$tmp_dir/blank-values.yaml"
|
||||
blank_rendered="$tmp_dir/blank.yaml"
|
||||
cat > "$blank_values" <<'YAML'
|
||||
config:
|
||||
public:
|
||||
allowPrivateMcpUrls: ""
|
||||
YAML
|
||||
helm template openwork-ee "$chart_dir" -f "$blank_values" > "$blank_rendered"
|
||||
assert_count "$blank_rendered" 'DEN_ALLOW_PRIVATE_MCP_URLS' 0
|
||||
|
||||
invalid_true_values="$tmp_dir/invalid-true-values.yaml"
|
||||
cat > "$invalid_true_values" <<'YAML'
|
||||
config:
|
||||
public:
|
||||
allowPrivateMcpUrls: "true"
|
||||
YAML
|
||||
assert_failure "$invalid_true_values" 'config.public.allowPrivateMcpUrls must be blank, 0, false, or "1"'
|
||||
|
||||
invalid_yes_values="$tmp_dir/invalid-yes-values.yaml"
|
||||
cat > "$invalid_yes_values" <<'YAML'
|
||||
config:
|
||||
public:
|
||||
allowPrivateMcpUrls: "yes"
|
||||
YAML
|
||||
assert_failure "$invalid_yes_values" 'config.public.allowPrivateMcpUrls must be blank, 0, false, or "1"'
|
||||
|
||||
printf 'private-mcp-urls chart checks passed\n'
|
||||
@@ -0,0 +1,322 @@
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
image:
|
||||
tag: latest
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
imagePullSecrets: []
|
||||
|
||||
config:
|
||||
databaseMode: mysql
|
||||
openworkDevMode: "0"
|
||||
# Passed to the den-api Node.js process as NODE_OPTIONS at container startup.
|
||||
denApiNodeOptions: ""
|
||||
tenancy:
|
||||
mode: single_org
|
||||
singleOrgName: OpenWork
|
||||
singleOrgSlug: default
|
||||
ownerEmails: ""
|
||||
allowPublicSignup: "false"
|
||||
requireEmailVerification: "false"
|
||||
auth:
|
||||
passwordBreachScreeningEnabled: "false"
|
||||
githubConnector:
|
||||
appId: ""
|
||||
clientId: ""
|
||||
public:
|
||||
# Single public Den web origin. The chart passes this as DEN_BASE_URL; den-api
|
||||
# derives Better Auth, CORS/trusted origins, web-app hosts, API defaults, and
|
||||
# MCP resource defaults from it unless the compatibility overrides below are set.
|
||||
webOrigin: https://openwork.example.com
|
||||
# Compatibility overrides for split-origin or migration deployments.
|
||||
apiOrigin: ""
|
||||
mcpResourceUrl: ""
|
||||
mcpAdditionalResources: ""
|
||||
# Leave blank to keep External MCP URL SSRF protection enabled.
|
||||
# Set "1" only for trusted private-network MCP servers.
|
||||
allowPrivateMcpUrls: ""
|
||||
mcpClaimNamespace: ""
|
||||
desktopDenBaseUrl: ""
|
||||
corsOrigins: ""
|
||||
betterAuthCookieDomain: ""
|
||||
betterAuthTrustedOrigins: ""
|
||||
webAppHosts: ""
|
||||
bootstrapAdminEmails: ""
|
||||
installLinksGatingEnabled: "false"
|
||||
# Self-hosted Automations are opt-in. Hosted OpenWork Cloud enables them.
|
||||
automationsEnabled: "false"
|
||||
# Self-hosted Dashboards are opt-in. Hosted environments use the same Den flag.
|
||||
dashboardsEnabled: "false"
|
||||
# OpenWork Web is hosted-only by default. OpenWork Cloud enables it explicitly.
|
||||
openworkWebEnabled: "false"
|
||||
# Preserve the Den runtime for published legacy Desktops during upgrades.
|
||||
# Set false only after every Desktop is v0.18.35+ to hard-disable Automations.
|
||||
automationsRuntimeEnabled: "true"
|
||||
# Enable only after compatible Desktop MCP Apps hosts are deployed.
|
||||
generatedArtifactViewsEnabled: "false"
|
||||
connectLinkMode: exchange
|
||||
connectLinkKeyId: ""
|
||||
openworkAppConnectUrl: ""
|
||||
openworkWebUrl: ""
|
||||
installerReleaseTag: ""
|
||||
installerReleaseRepo: ""
|
||||
authCallbackUrl: https://openwork.example.com
|
||||
internal:
|
||||
apiBaseUrl: ""
|
||||
authFallbackBaseUrl: ""
|
||||
inferenceProxyBaseUrl: ""
|
||||
provisioner:
|
||||
mode: stub
|
||||
reconcileIntervalMs: "60000"
|
||||
reconcileStaleMs: "1200000"
|
||||
reconcileBatchSize: "10"
|
||||
workerUrlTemplate: ""
|
||||
daytona:
|
||||
apiUrl: ""
|
||||
target: ""
|
||||
snapshot: ""
|
||||
sandboxImage: ""
|
||||
sandboxCpu: ""
|
||||
sandboxMemory: ""
|
||||
sandboxDisk: ""
|
||||
sandboxPublic: ""
|
||||
sandboxAutoStopInterval: ""
|
||||
sandboxAutoArchiveInterval: ""
|
||||
sandboxAutoDeleteInterval: ""
|
||||
signedPreviewExpiresSeconds: "86400"
|
||||
sandboxNamePrefix: ""
|
||||
sharedVolumeName: ""
|
||||
volumeNamePrefix: ""
|
||||
workspaceMountPath: ""
|
||||
dataMountPath: ""
|
||||
runtimeWorkspacePath: ""
|
||||
runtimeDataPath: ""
|
||||
sidecarDir: ""
|
||||
openworkPort: ""
|
||||
opencodePort: ""
|
||||
createTimeoutSeconds: ""
|
||||
deleteTimeoutSeconds: ""
|
||||
healthcheckTimeoutMs: ""
|
||||
polar:
|
||||
featureGateEnabled: "false"
|
||||
apiBase: ""
|
||||
productId: ""
|
||||
benefitId: ""
|
||||
successUrl: ""
|
||||
returnUrl: ""
|
||||
inference:
|
||||
openRouterWorkspaceId: ""
|
||||
stripeInferencePriceId: ""
|
||||
openRouterUpstreamUrl: ""
|
||||
creditsPerDollar: ""
|
||||
web:
|
||||
posthogKey: ""
|
||||
posthogHost: ""
|
||||
|
||||
observability:
|
||||
backend: none
|
||||
serviceNames:
|
||||
denApi: den-api
|
||||
denWeb: den-web
|
||||
otel:
|
||||
endpoint: ""
|
||||
tracesEndpoint: ""
|
||||
metricsEndpoint: ""
|
||||
logsEndpoint: ""
|
||||
protocol: http/protobuf
|
||||
exporters:
|
||||
traces: otlp
|
||||
metrics: otlp
|
||||
logs: otlp
|
||||
tracesSampler: parentbased_always_on
|
||||
tracesSamplerArg: ""
|
||||
headers:
|
||||
existingSecret: ""
|
||||
key: OTEL_EXPORTER_OTLP_HEADERS
|
||||
sentry:
|
||||
dsn: ""
|
||||
dsnSecret:
|
||||
existingSecret: ""
|
||||
key: SENTRY_DSN
|
||||
tracesSampleRate: "0.01"
|
||||
environment: ""
|
||||
release: ""
|
||||
dist: ""
|
||||
|
||||
customCa:
|
||||
enabled: false
|
||||
existingSecret: ""
|
||||
existingConfigMap: ""
|
||||
key: ca.crt
|
||||
|
||||
redis:
|
||||
# Prefer rediss:// DATABASE_REDIS_URL values. Set this to true only for
|
||||
# trusted private, non-public Redis endpoints that do not support TLS, such as
|
||||
# some managed internal Redis URLs exposed by hosting platforms.
|
||||
allowInsecureInternal: false
|
||||
|
||||
secret:
|
||||
create: true
|
||||
existingSecret: ""
|
||||
keys:
|
||||
databaseUrl: DATABASE_URL
|
||||
databaseRedisUrl: DATABASE_REDIS_URL
|
||||
betterAuthSecret: BETTER_AUTH_SECRET
|
||||
denDbEncryptionKey: DEN_DB_ENCRYPTION_KEY
|
||||
daytonaApiKey: DAYTONA_API_KEY
|
||||
polarAccessToken: POLAR_ACCESS_TOKEN
|
||||
openRouterManagementApiKey: OPENROUTER_MANAGEMENT_API_KEY
|
||||
inferenceAdminToken: INFERENCE_ADMIN_TOKEN
|
||||
inferenceWebhookSecret: INFERENCE_WEBHOOK_SECRET
|
||||
emailFrom: EMAIL_FROM
|
||||
smtpHost: SMTP_HOST
|
||||
smtpPort: SMTP_PORT
|
||||
smtpUser: SMTP_USER
|
||||
smtpPass: SMTP_PASS
|
||||
smtpSecure: SMTP_SECURE
|
||||
githubConnectorAppClientSecret: GITHUB_CONNECTOR_APP_CLIENT_SECRET
|
||||
githubConnectorAppPrivateKey: GITHUB_CONNECTOR_APP_PRIVATE_KEY
|
||||
githubConnectorAppWebhookSecret: GITHUB_CONNECTOR_APP_WEBHOOK_SECRET
|
||||
connectLinkPrivateKey: DEN_CONNECT_LINK_PRIVATE_KEY
|
||||
initialAdminBootstrapCode: DEN_INITIAL_ADMIN_BOOTSTRAP_CODE
|
||||
values:
|
||||
databaseUrl: mysql://openwork:change-me@mysql:3306/openwork_den
|
||||
databaseRedisUrl: ""
|
||||
betterAuthSecret: CHANGE_ME_32_CHARS_MINIMUM_BETTER_AUTH
|
||||
denDbEncryptionKey: CHANGE_ME_32_CHARS_MINIMUM_DB_ENCRYPTION
|
||||
daytonaApiKey: ""
|
||||
polarAccessToken: ""
|
||||
openRouterManagementApiKey: ""
|
||||
inferenceAdminToken: ""
|
||||
inferenceWebhookSecret: ""
|
||||
emailFrom: ""
|
||||
smtpHost: ""
|
||||
smtpPort: "587"
|
||||
smtpUser: ""
|
||||
smtpPass: ""
|
||||
smtpSecure: "false"
|
||||
githubConnectorAppClientSecret: ""
|
||||
githubConnectorAppPrivateKey: ""
|
||||
githubConnectorAppWebhookSecret: ""
|
||||
connectLinkPrivateKey: ""
|
||||
initialAdminBootstrapCode: ""
|
||||
|
||||
installerArtifacts:
|
||||
enabled: false
|
||||
existingClaim: ""
|
||||
hostPath: ""
|
||||
mountPath: /var/lib/openwork/installer-artifacts
|
||||
|
||||
denApi:
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: ghcr.io/different-ai/openwork-den-api
|
||||
tag: ""
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8788
|
||||
annotations: {}
|
||||
loadBalancerClass: ""
|
||||
loadBalancerSourceRanges: []
|
||||
containerPort: 8788
|
||||
env: {}
|
||||
podAnnotations: {}
|
||||
podLabels: {}
|
||||
resources: {}
|
||||
probes:
|
||||
liveness:
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 6
|
||||
readiness:
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 12
|
||||
|
||||
denWeb:
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: ghcr.io/different-ai/openwork-den-web
|
||||
tag: ""
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 3005
|
||||
annotations: {}
|
||||
loadBalancerClass: ""
|
||||
loadBalancerSourceRanges: []
|
||||
containerPort: 3005
|
||||
env: {}
|
||||
podAnnotations: {}
|
||||
podLabels: {}
|
||||
resources: {}
|
||||
probes:
|
||||
liveness:
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 6
|
||||
readiness:
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 10
|
||||
failureThreshold: 18
|
||||
|
||||
inference:
|
||||
enabled: false
|
||||
replicaCount: 1
|
||||
image:
|
||||
repository: ghcr.io/different-ai/openwork-inference
|
||||
tag: ""
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8791
|
||||
annotations: {}
|
||||
loadBalancerClass: ""
|
||||
loadBalancerSourceRanges: []
|
||||
containerPort: 8791
|
||||
env: {}
|
||||
podAnnotations: {}
|
||||
podLabels: {}
|
||||
resources: {}
|
||||
probes:
|
||||
liveness:
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 6
|
||||
readiness:
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 12
|
||||
|
||||
migrations:
|
||||
enabled: true
|
||||
hook: true
|
||||
hookDeletePolicy: before-hook-creation,hook-succeeded
|
||||
backoffLimit: 2
|
||||
activeDeadlineSeconds: 600
|
||||
podAnnotations: {}
|
||||
resources: {}
|
||||
command:
|
||||
- node
|
||||
args:
|
||||
- /app/ee/packages/den-db/dist/scripts/bootstrap.js
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
className: ""
|
||||
annotations: {}
|
||||
web:
|
||||
host: openwork.example.com
|
||||
path: /
|
||||
pathType: Prefix
|
||||
api:
|
||||
enabled: true
|
||||
host: api.openwork.example.com
|
||||
path: /
|
||||
pathType: Prefix
|
||||
tls: []
|
||||
Reference in New Issue
Block a user