235 lines
11 KiB
YAML
235 lines
11 KiB
YAML
name: Nightly Build
|
|
|
|
# Nightly build of the three DeerFlow container images (backend, frontend,
|
|
# provisioner) and the Helm chart, published to GHCR from the default branch.
|
|
#
|
|
# This mirrors the tag-driven release workflows (container.yaml + chart.yaml)
|
|
# but trades the `v*` tag for date-based tags, since nightly ships unreleased
|
|
# `main`. The `verify-versions` gate is intentionally skipped - there is no tag
|
|
# to match - and `latest` is left untouched so it keeps tracking the last `v*`
|
|
# release. Images are amd64-only to match the release builds.
|
|
#
|
|
# Artifacts (under the running repo's owner):
|
|
# ghcr.io/<owner>/deer-flow-{backend,frontend,provisioner}:nightly
|
|
# ghcr.io/<owner>/deer-flow-{backend,frontend,provisioner}:nightly-YYYYMMDD
|
|
# oci://ghcr.io/<owner>/deer-flow chart version <base>-nightly.YYYYMMDD-<sha>
|
|
#
|
|
# The nightly chart defaults image.tag=nightly and image.registry=ghcr.io/<owner>
|
|
# (patched in-workflow, never committed), so installing it pulls the matching
|
|
# nightly images with no values overrides.
|
|
#
|
|
# Restricted to the upstream repo: every job is gated on
|
|
# `github.repository == 'bytedance/deer-flow'`, so a scheduled run or manual
|
|
# dispatch on a fork skips all jobs rather than pushing to the fork's own GHCR
|
|
# namespace. Scheduled workflows also only fire on the default branch.
|
|
|
|
on:
|
|
schedule:
|
|
- cron: "0 16 * * *" # 16:00 UTC daily (adjust as needed)
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: nightly
|
|
# Don't cancel a running nightly - a mid-cancel leaves a half-pushed set.
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
REGISTRY: ghcr.io
|
|
|
|
jobs:
|
|
prepare:
|
|
if: github.repository == 'bytedance/deer-flow'
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
date: ${{ steps.date.outputs.date }}
|
|
nightly_version: ${{ steps.nightly_version.outputs.nightly_version }}
|
|
steps:
|
|
- name: Set nightly date (UTC)
|
|
id: date
|
|
# Shared by build-images (image tag) and publish-chart (chart version)
|
|
# so the two stay in sync for a given run.
|
|
run: echo "date=$(date -u +%Y%m%d)" >> "$GITHUB_OUTPUT"
|
|
- name: Checkout repository
|
|
# Needed to read Chart.yaml's base version for the nightly version
|
|
# string computed below.
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 #v6.0.3
|
|
- name: Compute nightly version
|
|
id: nightly_version
|
|
# Single source of truth for the nightly version string:
|
|
# <base>-nightly.<YYYYMMDD>-<short_sha> (mirrors the chart's nightly
|
|
# scheme). build-images injects it into the frontend image (About-page
|
|
# version) and publish-chart stamps it on Chart.yaml, so the version
|
|
# users see and the chart version can't drift apart.
|
|
run: |
|
|
set -euo pipefail
|
|
BASE=$(grep -m1 '^version:' deploy/helm/deer-flow/Chart.yaml | awk '{print $2}')
|
|
# A malformed/missing Chart.yaml version would yield an empty BASE ->
|
|
# `-nightly.<date>-<sha>` (invalid semver) that now flows into both
|
|
# the chart publish and the frontend About-page version. Fail loudly.
|
|
# `pipefail` matters: without it the pipeline's exit code is awk's,
|
|
# so `set -e` alone wouldn't catch a grep miss.
|
|
test -n "$BASE" || { echo "::error::empty base version from Chart.yaml"; exit 1; }
|
|
SHORT_SHA="${GITHUB_SHA::7}"
|
|
NIGHTLY="${BASE}-nightly.${{ steps.date.outputs.date }}-${SHORT_SHA}"
|
|
echo "nightly_version=${NIGHTLY}" >> "$GITHUB_OUTPUT"
|
|
echo "Nightly version: ${NIGHTLY}"
|
|
|
|
validate-chart:
|
|
if: github.repository == 'bytedance/deer-flow'
|
|
# Catch a broken render or a stale config_version before publish. A broken
|
|
# chart published under an OCI version is immutable (GHCR won't let you
|
|
# overwrite --version), so a regression must fail here, not on install.
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 #v6.0.3
|
|
|
|
- name: Lint chart
|
|
run: helm lint deploy/helm/deer-flow
|
|
- name: Validate templates render
|
|
run: helm template deer-flow deploy/helm/deer-flow --include-crds >/dev/null
|
|
|
|
# The chart's `config:` block embeds a config_version that must not fall
|
|
# behind config.example.yaml. Shared with chart.yaml via
|
|
# scripts/check_config_version.sh so the two workflows can't drift.
|
|
- name: config_version drift check
|
|
run: bash scripts/check_config_version.sh
|
|
|
|
build-images:
|
|
needs: prepare
|
|
if: github.repository == 'bytedance/deer-flow'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
attestations: write
|
|
id-token: write
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- component: backend
|
|
context: .
|
|
file: backend/Dockerfile
|
|
# Bake the `postgres` extra so multi-replica (K8s/Helm) deployments
|
|
# can use shared Postgres. Matches container.yaml's release image.
|
|
build-args: "UV_EXTRAS=postgres"
|
|
- component: frontend
|
|
context: .
|
|
file: frontend/Dockerfile
|
|
build-args: ""
|
|
- component: provisioner
|
|
context: docker/provisioner
|
|
file: docker/provisioner/Dockerfile
|
|
build-args: ""
|
|
env:
|
|
IMAGE_NAME: ${{ github.repository }}-${{ matrix.component }}
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 #v6.0.3
|
|
- name: Log in to the Container registry
|
|
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 #v3.4.0
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
- name: Extract metadata (tags, labels) for Docker
|
|
id: meta
|
|
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 #v5.7.0
|
|
with:
|
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
# `nightly` rolls forward each run; `nightly-YYYYMMDD` is pinned to a
|
|
# day but mutable within it (a same-day re-dispatch overwrites it);
|
|
# `sha-<short>` is the only truly immutable tag. No `latest` (that
|
|
# stays on v* releases) and no branch/tag refs.
|
|
tags: |
|
|
type=raw,value=nightly
|
|
type=raw,value=nightly-${{ needs.prepare.outputs.date }}
|
|
type=sha
|
|
- name: Build and push Docker image
|
|
id: push
|
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 #v6.18.0
|
|
with:
|
|
context: ${{ matrix.context }}
|
|
file: ${{ matrix.file }}
|
|
push: true
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
# APP_VERSION is consumed only by the frontend Dockerfile (it stamps
|
|
# the About-page version); backend/provisioner Dockerfiles don't
|
|
# declare it. Passed via build-arg because build-push-action doesn't
|
|
# forward host env into the BuildKit build.
|
|
build-args: |
|
|
${{ matrix.build-args }}
|
|
${{ matrix.component == 'frontend' && format('APP_VERSION={0}', needs.prepare.outputs.nightly_version) || '' }}
|
|
- name: Generate artifact attestation
|
|
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be #v2.4.0
|
|
with:
|
|
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
subject-digest: ${{ steps.push.outputs.digest }}
|
|
push-to-registry: true
|
|
|
|
publish-chart:
|
|
# Ship the chart only after images build - it references all three, so a
|
|
# failed image build withholds the chart rather than publishing one that
|
|
# would fail to pull in-cluster.
|
|
needs: [prepare, validate-chart, build-images]
|
|
if: github.repository == 'bytedance/deer-flow'
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
env:
|
|
OWNER: ${{ github.repository_owner }}
|
|
NIGHTLY: ${{ needs.prepare.outputs.nightly_version }}
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 #v6.0.3
|
|
|
|
- name: Patch chart to a nightly version + nightly image defaults
|
|
# Bumps Chart.yaml version/appVersion to the nightly version computed in
|
|
# the prepare job (<base>-nightly.<date>-<sha>, a valid semver
|
|
# prerelease; the short SHA makes each dispatch's version unique, so a
|
|
# same-day re-dispatch re-publishes cleanly - OCI chart versions are
|
|
# immutable and otherwise can't be overwritten). The same string is
|
|
# injected into the frontend image, so the About-page version and the
|
|
# chart version match. Repoints the chart's default image registry/tag
|
|
# at the nightly build. Patches are in-workflow only - nothing is
|
|
# committed back.
|
|
run: |
|
|
set -eu
|
|
CHART=deploy/helm/deer-flow
|
|
echo "Nightly chart version: ${NIGHTLY}"
|
|
sed -i "s|^version:.*|version: ${NIGHTLY}|" "$CHART/Chart.yaml"
|
|
sed -i "s|^appVersion:.*|appVersion: \"${NIGHTLY}\"|" "$CHART/Chart.yaml"
|
|
sed -i "s|^ registry: \"\".*| registry: \"ghcr.io/${OWNER}\"|" "$CHART/values.yaml"
|
|
sed -i 's|^ tag: "latest"| tag: "nightly"|' "$CHART/values.yaml"
|
|
# Gate the patches: sed exits 0 even on zero matches, so a drifted
|
|
# Chart.yaml/values.yaml would otherwise ship a chart that silently
|
|
# pulls the wrong (release `latest`) images. Fail loudly if any patch
|
|
# missed.
|
|
grep -q "^version: ${NIGHTLY}$" "$CHART/Chart.yaml" || { echo "::error::Chart.yaml version sed did not apply"; exit 1; }
|
|
grep -q "^appVersion: \"${NIGHTLY}\"$" "$CHART/Chart.yaml" || { echo "::error::Chart.yaml appVersion sed did not apply"; exit 1; }
|
|
grep -q '^ registry: "ghcr.io/' "$CHART/values.yaml" || { echo "::error::values.yaml registry sed did not apply"; exit 1; }
|
|
grep -q '^ tag: "nightly"' "$CHART/values.yaml" || { echo "::error::values.yaml tag sed did not apply"; exit 1; }
|
|
echo "--- Chart.yaml (head) ---"; sed -n '1,7p' "$CHART/Chart.yaml"
|
|
echo "--- image block ---"; sed -n '11,14p' "$CHART/values.yaml"
|
|
|
|
- name: Lint patched chart
|
|
run: helm lint deploy/helm/deer-flow
|
|
|
|
- name: Log in to GHCR
|
|
run: |
|
|
echo "${{ secrets.GITHUB_TOKEN }}" | \
|
|
helm registry login ghcr.io -u ${{ github.actor }} --password-stdin
|
|
|
|
- name: Package and push chart
|
|
run: |
|
|
helm package deploy/helm/deer-flow --destination ./packages
|
|
for pkg in ./packages/*.tgz; do
|
|
echo "--- pushing $pkg"
|
|
helm push "$pkg" oci://ghcr.io/${{ github.repository_owner }}
|
|
done
|