mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-19 19:16:17 +00:00
* feat(helm): add production-ready Helm chart for Kubernetes deployment Adds deploy/helm/deer-flow, a native-Kubernetes translation of the production docker-compose stack, plus CI to publish its images and chart. * ci(release): gate releases on version-source consistency Add a reusable verify-versions workflow invoked by both chart.yaml and container.yaml on v* tags. It runs scripts/verify_versions.sh against the tag and fails the release — skipping all image and chart publishing — when Chart.yaml (version + appVersion), backend/pyproject.toml, or frontend/package.json don't all match the tag. Add scripts/verify_versions.sh (the check, also runnable locally) and scripts/bump_version.sh (bumps all four sources in lockstep, then self-verifies). Document the release flow in RELEASING.md and link it from AGENTS.md. * fix(deploy): address Helm chart review feedback (#3987) Three review items from willem-bd: 1. nginx IPv6 listen strip never matched. The sed pattern required a `;` immediately after `2026`, but the rendered config emits `listen [::]:2026 default_server;` (space + `default_server` before the `;`), so the line was never deleted and nginx crash-looped on pods without IPv6 (`socket() :::2026 failed (97: Address family not supported)`). Drop the trailing `;` from the pattern so it matches. Same latent bug fixed in docker-compose-dev.yaml. 2. Passwords were spliced into DSNs verbatim, so a password containing URL-special chars (@ : / # ? % [ ] space) produced a malformed DSN and a confusing parse error. Add a `deer-flow.urlEscape` helper (replace-based: Sprig lacks urlqueryescape, and regexReplaceAllLiteral treats the replacement as a regex template so `[`/`]`/`?` break it) and apply it to the password in the postgres and redis DSNs. The raw `postgres-password` / `redis-password` keys stay unencoded - they back POSTGRES_PASSWORD / REDIS_PASSWORD, not a URL segment. 3. NODE_HOST defaulted to "gateway", which can never route: the gateway Service is ClusterIP:8001 and knows nothing of a sandbox NodePort, so a user who skips the caveat gets unreachable sandboxes with no error at install time. Default NODE_HOST to the provisioner pod's node IP via the downward API (status.hostIP) - a NodePort is exposed on every node, so <node-IP>:<NodePort> routes from the gateway on most clusters. `provisioner.nodeHost` remains an override for CNIs/policies that block pod->node-IP traffic. Updated NOTES.txt, values.yaml, and the chart README. (#3929 remains the long-term fix - ClusterIP + cluster-DNS URL removes NODE_HOST and the NodePort exposure entirely.) Validated with helm lint, helm template (incl. a special-char password rendering the encoded DSNs), and a sed pattern-match check. * fix(deploy): address round-2 Helm chart review feedback (#3987) Three "Medium" items from willem-bd: 1. No helm lint / helm template gate before publish. A template regression ships as an immutable OCI artifact (GHCR won't overwrite --version), so gate packaging on `helm lint` + `helm template --include-crds` in chart.yaml before `helm package`. (ct lint / helm-unittest deferred.) 2. Action pinning inconsistent + PR body overstates it. SHA-pin actions/checkout (v6.0.3, df4cb1c0) and actions/attest-build-provenance (v2.4.0, e8998f94) across the publishing workflows (chart.yaml, container.yaml, verify-versions.yml), matching the existing docker/* SHA-pin pattern. Resolves the checkout @v4/@v6 mismatch and makes the "SHA-pinned actions" claim accurate. Other pre-existing workflows left untouched (out of scope for this PR). 3. Provisioner RBAC broader than needed. Dropped the unused update/patch verbs and the pods/exec + events rules from the provisioner Role - audited against docker/provisioner/app.py, which only calls get/create/delete on pods and get/list/create/delete on services. Fixed NOTES.txt to accurately describe the grant instead of understating it as "create Pods and Services". The remaining scope concern - verbs apply to all Pods in the namespace, not just sandbox Pods - is still deferred (RBAC can't scope by label; needs a dedicated namespace or admission control), now noted in NOTES.txt and README. Validated with helm lint + helm template (narrowed Role renders with exactly get/list/watch/create/delete). * feat(helm): enable sandbox+web tools out of the box The chart's default config loaded zero agent tools (config.tools empty -> "Total tools loaded: 0"), so a fresh install gave an agent that could do nothing useful. Add tool_groups + tools to the default config block: - web: web_search (ddg), web_fetch (jina), image_search - no API key - file:read: ls, read_file, glob, grep - file:write: write_file, str_replace - bash The file/bash tools run inside the AIO sandbox the chart already configures; the web tools need outbound internet from the gateway pod (swap backends or drop entries for air-gapped clusters - see config.example.yaml). Also bump config_version 15 -> 19 to match config.example.yaml (the chart had drifted behind). NOTES.txt and the README example updated to match. * ci(helm): add chart validation + config_version drift check on PR Extend the chart workflow with a PR-triggered validate-chart job that runs helm lint, helm template --include-crds, and a config_version drift check: it parses config_version from both config.example.yaml and the chart's values.yaml and fails the build (with a ::error:: naming the files to bump) if the chart is behind the example. This catches the kind of drift this PR is fixing - the chart sat at v15 while the example moved to v19 - before it can merge again. verify-versions and publish-chart stay tag-only; publish-chart now needs: [verify-versions, validate-chart]. validate-chart runs on both PRs and tag pushes: the tag arm is required because a job that `needs` a skipped job is itself skipped under the default success() check, so validate-chart must actually run on tag pushes or publish-chart would never fire. * Bump config version to 20
156 lines
6.5 KiB
Smarty
156 lines
6.5 KiB
Smarty
{{/*
|
|
Common helpers for the DeerFlow chart.
|
|
*/}}
|
|
|
|
{{- define "deer-flow.name" -}}
|
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
|
{{- end -}}
|
|
|
|
{{- define "deer-flow.fullname" -}}
|
|
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
|
{{- end -}}
|
|
|
|
{{- define "deer-flow.chart" -}}
|
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
|
{{- end -}}
|
|
|
|
{{- define "deer-flow.labels" -}}
|
|
helm.sh/chart: {{ include "deer-flow.chart" . }}
|
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
{{- end -}}
|
|
|
|
{{- define "deer-flow.selectorLabels" -}}
|
|
app.kubernetes.io/name: {{ include "deer-flow.name" . }}
|
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
|
{{- end -}}
|
|
|
|
{{- define "deer-flow.namespace" -}}
|
|
{{- default .Release.Namespace .Values.namespace -}}
|
|
{{- end -}}
|
|
|
|
{{- define "deer-flow.imagePullSecrets" -}}
|
|
{{- with .Values.image.pullSecrets }}
|
|
imagePullSecrets:
|
|
{{- toYaml . | nindent 0 }}
|
|
{{- end }}
|
|
{{- end -}}
|
|
|
|
{{/* Fully-qualified image refs for the three DeerFlow images.
|
|
When `image.registry` is empty, omit the prefix so the ref is
|
|
`deer-flow-gateway:latest` (local-image mode, imagePullPolicy: Never). */}}
|
|
{{- define "deer-flow.gatewayImage" -}}
|
|
{{- if .Values.image.registry -}}{{- printf "%s/%s:%s" .Values.image.registry .Values.image.gatewayImage .Values.image.tag -}}
|
|
{{- else -}}{{- printf "%s:%s" .Values.image.gatewayImage .Values.image.tag -}}{{- end -}}
|
|
{{- end -}}
|
|
|
|
{{- define "deer-flow.frontendImage" -}}
|
|
{{- if .Values.image.registry -}}{{- printf "%s/%s:%s" .Values.image.registry .Values.image.frontendImage .Values.image.tag -}}
|
|
{{- else -}}{{- printf "%s:%s" .Values.image.frontendImage .Values.image.tag -}}{{- end -}}
|
|
{{- end -}}
|
|
|
|
{{- define "deer-flow.provisionerImage" -}}
|
|
{{- if .Values.image.registry -}}{{- printf "%s/%s:%s" .Values.image.registry .Values.image.provisionerImage .Values.image.tag -}}
|
|
{{- else -}}{{- printf "%s:%s" .Values.image.provisionerImage .Values.image.tag -}}{{- end -}}
|
|
{{- end -}}
|
|
|
|
{{- define "deer-flow.nginxImage" -}}
|
|
{{- printf "%s:%s" .Values.nginx.image.repository .Values.nginx.image.tag -}}
|
|
{{- end -}}
|
|
|
|
{{/* PVC name for the .deer-flow home directory. */}}
|
|
{{- define "deer-flow.homePVC" -}}
|
|
{{- printf "%s-home" (include "deer-flow.fullname" .) -}}
|
|
{{- end -}}
|
|
|
|
{{/* Name of the Secret holding provider/channel keys. */}}
|
|
{{- define "deer-flow.providerSecret" -}}
|
|
{{- if .Values.existingSecret -}}{{- .Values.existingSecret -}}
|
|
{{- else -}}{{- printf "%s-provider" (include "deer-flow.fullname" .) -}}{{- end -}}
|
|
{{- end -}}
|
|
|
|
{{/* Name of the Secret holding generated app secrets (auth token, better-auth). */}}
|
|
{{- define "deer-flow.appSecret" -}}
|
|
{{- printf "%s-app" (include "deer-flow.fullname" .) -}}
|
|
{{- end -}}
|
|
|
|
{{/* Name of the postgres StatefulSet/Service. */}}
|
|
{{- define "deer-flow.postgresFullname" -}}
|
|
{{- printf "%s-postgres" (include "deer-flow.fullname" .) -}}
|
|
{{- end -}}
|
|
|
|
{{/* Name of the Secret holding DATABASE_URL (and, in bundled mode, the
|
|
postgres superuser password). Resolution order:
|
|
1. postgresql.external.existingSecret (user-managed, key=database-url)
|
|
2. postgresql.existingSecret (user-managed, bundled image)
|
|
3. chart-managed secret `<release>-postgres`
|
|
Only #3 is created by this chart; #1/#2 must exist already. */}}
|
|
{{- define "deer-flow.databaseUrlSecret" -}}
|
|
{{- if .Values.postgresql.external.existingSecret -}}{{- .Values.postgresql.external.existingSecret -}}
|
|
{{- else if .Values.postgresql.existingSecret -}}{{- .Values.postgresql.existingSecret -}}
|
|
{{- else -}}{{- include "deer-flow.postgresFullname" . -}}{{- end -}}
|
|
{{- end -}}
|
|
|
|
{{/* Name of the redis StatefulSet/Service. */}}
|
|
{{- define "deer-flow.redisFullname" -}}
|
|
{{- printf "%s-redis" (include "deer-flow.fullname" .) -}}
|
|
{{- end -}}
|
|
|
|
{{/* Name of the Secret holding the redis stream-bridge URL (key `redis-url`,
|
|
plus `redis-password` in bundled mode when a password is set). Resolution:
|
|
1. redis.external.existingSecret (user-managed, key=redis-url)
|
|
2. redis.existingSecret (user-managed, bundled image)
|
|
3. chart-managed secret `<release>-redis`
|
|
Only #3 is created by this chart; #1/#2 must exist already. */}}
|
|
{{- define "deer-flow.redisUrlSecret" -}}
|
|
{{- if .Values.redis.external.existingSecret -}}{{- .Values.redis.external.existingSecret -}}
|
|
{{- else if .Values.redis.existingSecret -}}{{- .Values.redis.existingSecret -}}
|
|
{{- else -}}{{- include "deer-flow.redisFullname" . -}}{{- end -}}
|
|
{{- end -}}
|
|
|
|
{{/* Whether any redis stream-bridge backend is configured (bundled StatefulSet,
|
|
external URL, or a user-managed Secret). Drives the env injection in the
|
|
gateway deployment. */}}
|
|
{{- define "deer-flow.redisConfigured" -}}
|
|
{{- or .Values.redis.enabled .Values.redis.external.redisUrl .Values.redis.external.existingSecret .Values.redis.existingSecret -}}
|
|
{{- end -}}
|
|
|
|
{{/* SHA256 checksums of the ConfigMaps. Mount these as pod-template
|
|
annotations: ConfigMaps mounted via subPath do NOT receive live updates,
|
|
so a `helm upgrade` that only changes a ConfigMap would leave pods on stale
|
|
config. A checksum annotation makes any content change alter the pod spec,
|
|
which triggers a rolling restart. */}}
|
|
{{- define "deer-flow.configChecksum" -}}
|
|
{{- include (print $.Template.BasePath "/configmap-config.yaml") . | sha256sum -}}
|
|
{{- end -}}
|
|
|
|
{{- define "deer-flow.extensionsChecksum" -}}
|
|
{{- include (print $.Template.BasePath "/configmap-extensions.yaml") . | sha256sum -}}
|
|
{{- end -}}
|
|
|
|
{{- define "deer-flow.nginxChecksum" -}}
|
|
{{- include (print $.Template.BasePath "/configmap-nginx.yaml") . | sha256sum -}}
|
|
{{- end -}}
|
|
|
|
{{/* Percent-encode a string for safe interpolation into a URL userinfo
|
|
(password) segment of a DSN. Sprig lacks urlqueryescape, and
|
|
regexReplaceAllLiteral treats `replacement` as a regex template so chars
|
|
like `[`, `]`, `?` break it - so we chain plain `replace` calls instead.
|
|
`%` is encoded first to avoid double-encoding the percent signs emitted
|
|
for the other characters. Covers the URL-special chars a managed-DB
|
|
password might contain (`@ : / # ? % [ ]` and space). */}}
|
|
{{- define "deer-flow.urlEscape" -}}
|
|
{{- $s := . -}}
|
|
{{- $s = replace "%" "%25" $s -}}
|
|
{{- $s = replace "@" "%40" $s -}}
|
|
{{- $s = replace ":" "%3A" $s -}}
|
|
{{- $s = replace "/" "%2F" $s -}}
|
|
{{- $s = replace "#" "%23" $s -}}
|
|
{{- $s = replace "?" "%3F" $s -}}
|
|
{{- $s = replace "[" "%5B" $s -}}
|
|
{{- $s = replace "]" "%5D" $s -}}
|
|
{{- $s = replace " " "%20" $s -}}
|
|
{{- $s -}}
|
|
{{- end -}}
|