deer-flow/scripts/bump_version.sh
Zheng Feng bc9ee9645c
Some checks are pending
Backend Blocking IO / backend-blocking-io (push) Waiting to run
Unit Tests / backend-unit-tests (push) Waiting to run
Frontend Unit Tests / frontend-unit-tests (push) Waiting to run
Lint Check / lint-backend (push) Waiting to run
Lint Check / lint-frontend (push) Waiting to run
Replay E2E (front-back contract) / Layer 1 — backend golden (no API key) (push) Waiting to run
Replay E2E (front-back contract) / Layer 2 — full-stack render (no API key) (push) Waiting to run
feat(deploy): first-class Helm chart for Kubernetes deployment (#3987)
* 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
2026-07-09 15:40:53 +08:00

97 lines
3 KiB
Bash
Executable file

#!/usr/bin/env bash
# Bump the project version across every version source in lockstep.
#
# Usage:
# scripts/bump_version.sh <version> # e.g. scripts/bump_version.sh 2.2.0
#
# Updates:
# backend/pyproject.toml (version = "...")
# frontend/package.json ("version": "...")
# deploy/helm/deer-flow/Chart.yaml (version: + appVersion:)
#
# This does NOT edit CHANGELOG.md or create/push a git tag — keep those manual.
# After running, commit and tag v<version> to trigger the release workflows
# (container.yaml + chart.yaml), which gate on scripts/verify_versions.sh.
set -euo pipefail
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <version> (e.g. $0 2.2.0)" >&2
exit 1
fi
VERSION="${1#v}" # tolerate a leading "v" (tag form) if passed by mistake
if ! printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([0-9A-Za-z.+-]+)?$'; then
echo "error: '$VERSION' is not a valid SemVer (expected X.Y.Z[, -prerelease])" >&2
exit 1
fi
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PYPROJECT="$ROOT/backend/pyproject.toml"
PACKAGE="$ROOT/frontend/package.json"
CHART="$ROOT/deploy/helm/deer-flow/Chart.yaml"
for f in "$PYPROJECT" "$PACKAGE" "$CHART"; do
if [ ! -f "$f" ]; then
echo "error: expected version file not found: $f" >&2
exit 1
fi
done
python3 - "$PYPROJECT" "$PACKAGE" "$CHART" "$VERSION" <<'PY'
import re
import sys
pyproject, package, chart, version = sys.argv[1:5]
# backend/pyproject.toml — version = "..."
with open(pyproject) as f:
src = f.read()
new = re.sub(r'(?m)^version\s*=\s*".*?"', f'version = "{version}"', src, count=1)
if new == src:
sys.exit(f"error: no top-level 'version' field in {pyproject}")
with open(pyproject, "w") as f:
f.write(new)
# frontend/package.json — "version": "..." (preserve indentation; minimal diff)
with open(package) as f:
src = f.read()
new = re.sub(
r'(?m)^(\s*)"version"\s*:\s*".*?"',
lambda m: f'{m.group(1)}"version": "{version}"',
src,
count=1,
)
if new == src:
sys.exit(f'error: no top-level "version" field in {package}')
with open(package, "w") as f:
f.write(new)
# deploy/helm/deer-flow/Chart.yaml — version: X.Y.Z and appVersion: "X.Y.Z"
with open(chart) as f:
src = f.read()
new = re.sub(r'(?m)^version:\s*\S+', f'version: {version}', src, count=1)
new = re.sub(r'(?m)^appVersion:\s*".*?"', f'appVersion: "{version}"', new, count=1)
if new == src:
sys.exit(f"error: could not find version/appVersion in {chart}")
with open(chart, "w") as f:
f.write(new)
PY
echo "Bumped version to $VERSION in:"
echo " backend/pyproject.toml"
echo " frontend/package.json"
echo " deploy/helm/deer-flow/Chart.yaml (version + appVersion)"
echo
if ! bash "$ROOT/scripts/verify_versions.sh" "$VERSION"; then
echo "error: post-bump verification failed." >&2
exit 1
fi
echo
echo "Next steps:"
echo " 1. Update CHANGELOG.md"
echo " 2. git add -A && git commit -m \"release: v$VERSION\""
echo " 3. git tag v$VERSION && git push origin v$VERSION"