name: release on: schedule: - cron: '0 13 * * *' # This schedule runs every 13:00:00Z(21:00:00+08:00) # https://github.com/orgs/community/discussions/26286?utm_source=chatgpt.com#discussioncomment-3251208 # "The create event does not support branch filter and tag filter." # The "create tags" trigger is specifically focused on the creation of new tags, while the "push tags" trigger is activated when tags are pushed, including both new tag creations and updates to existing tags. push: tags: - "v*.*.*" # normal release - 'nightly' # mutable tag permissions: contents: write actions: read checks: read statuses: read # https://docs.github.com/en/actions/using-jobs/using-concurrency concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: prepare: runs-on: [ "self-hosted", "ragflow-release" ] outputs: release_tag: ${{ steps.release.outputs.release_tag }} prerelease: ${{ steps.release.outputs.prerelease }} steps: - name: Ensure workspace ownership run: echo "chown -R ${USER} ${GITHUB_WORKSPACE}" && sudo chown -R ${USER} ${GITHUB_WORKSPACE} # https://github.com/actions/checkout/blob/v6/README.md - name: Check out code uses: actions/checkout@v6 with: token: ${{ secrets.GITHUB_TOKEN }} # Use the secret as an environment variable fetch-depth: 0 fetch-tags: true # https://github.com/actions/setup-go - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: go.mod cache: true - name: Prepare release metadata id: release run: | if [[ ${GITHUB_EVENT_NAME} != "schedule" ]]; then RELEASE_TAG=${GITHUB_REF#refs/tags/} if [[ ${RELEASE_TAG} == v* ]]; then PRERELEASE=false else PRERELEASE=true fi echo "Workflow triggered by create tag: ${RELEASE_TAG}" else RELEASE_TAG=nightly PRERELEASE=true echo "Workflow triggered by schedule" fi echo "RELEASE_TAG=${RELEASE_TAG}" >> ${GITHUB_ENV} echo "PRERELEASE=${PRERELEASE}" >> ${GITHUB_ENV} echo "release_tag=${RELEASE_TAG}" >> ${GITHUB_OUTPUT} echo "prerelease=${PRERELEASE}" >> ${GITHUB_OUTPUT} - name: Move the existing mutable tag # https://github.com/softprops/action-gh-release/issues/171 run: | git fetch --tags if [[ ${GITHUB_EVENT_NAME} == "schedule" ]]; then # Determine if a given tag exists and matches a specific Git commit. # actions/checkout@v6 fetch-tags doesn't work when triggered by schedule if [ "$(git rev-parse -q --verify "refs/tags/${RELEASE_TAG}")" = "${GITHUB_SHA}" ]; then echo "mutable tag ${RELEASE_TAG} exists and matches ${GITHUB_SHA}" else git tag -f ${RELEASE_TAG} ${GITHUB_SHA} git push -f origin ${RELEASE_TAG}:refs/tags/${RELEASE_TAG} echo "created/moved mutable tag ${RELEASE_TAG} to ${GITHUB_SHA}" fi fi build_cli: needs: prepare strategy: fail-fast: false matrix: include: - goos: linux goarch: amd64 runner: ubuntu-24.04 native_asset: native-linux-x86_64.tar.gz - goos: linux goarch: arm64 runner: ubuntu-24.04-arm native_asset: native-linux-aarch64.tar.gz - goos: darwin goarch: amd64 runner: macos-15-intel native_asset: native-macos-x86_64.tar.gz - goos: darwin goarch: arm64 runner: macos-14 native_asset: native-macos-aarch64.tar.gz - goos: windows goarch: amd64 runner: windows-latest native_asset: native-windows-x86_64.zip msystem: CLANG64 rust_target: x86_64-pc-windows-gnullvm msys2_packages: >- mingw-w64-clang-x86_64-clang mingw-w64-clang-x86_64-lld mingw-w64-clang-x86_64-cmake mingw-w64-clang-x86_64-ninja mingw-w64-clang-x86_64-pcre2 mingw-w64-clang-x86_64-pkgconf output_ext: .exe - goos: windows goarch: arm64 runner: windows-11-arm native_asset: native-windows-aarch64.zip msystem: CLANGARM64 rust_target: aarch64-pc-windows-gnullvm msys2_packages: >- mingw-w64-clang-aarch64-clang mingw-w64-clang-aarch64-lld mingw-w64-clang-aarch64-cmake mingw-w64-clang-aarch64-ninja mingw-w64-clang-aarch64-pcre2 mingw-w64-clang-aarch64-pkgconf output_ext: .exe runs-on: ${{ matrix.runner }} env: CLI_NAME: ragflow-cli CLI_MAIN: ./cmd/ragflow-cli.go DIST_DIR: dist/cli OFFICE_OXIDE_VERSION: "0.1.2" RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }} steps: # https://github.com/actions/checkout/blob/v6/README.md - name: Check out code uses: actions/checkout@v6 with: token: ${{ secrets.GITHUB_TOKEN }} fetch-depth: 0 fetch-tags: true # https://github.com/actions/setup-go - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: go.mod cache: true - name: Install Unix native build dependencies if: runner.os != 'Windows' shell: bash run: | set -euo pipefail if [[ "${{ matrix.goos }}" == "linux" ]]; then sudo apt-get update sudo apt-get install -y build-essential libpcre2-dev libsimde-dev pkg-config python3-pip elif [[ "${{ matrix.goos }}" == "darwin" ]]; then brew list pcre2 >/dev/null 2>&1 || brew install pcre2 brew list simde >/dev/null 2>&1 || brew install simde fi python3 -m pip install --user --upgrade 'cmake>=4.0,<5' || \ python3 -m pip install --user --break-system-packages --upgrade 'cmake>=4.0,<5' python_user_base="$(python3 -m site --user-base)" echo "${python_user_base}/bin" >> "${GITHUB_PATH}" export PATH="${python_user_base}/bin:${PATH}" cmake --version - name: Download office_oxide native library if: runner.os != 'Windows' shell: bash run: | set -euo pipefail OFFICE_OXIDE_PREFIX="${RUNNER_TEMP}/office_oxide" OFFICE_OXIDE_URL="https://github.com/yfedoseev/office_oxide/releases/download/v${OFFICE_OXIDE_VERSION}/${{ matrix.native_asset }}" mkdir -p "${OFFICE_OXIDE_PREFIX}" curl -fsSL "${OFFICE_OXIDE_URL}" -o "${RUNNER_TEMP}/${{ matrix.native_asset }}" tar xzf "${RUNNER_TEMP}/${{ matrix.native_asset }}" -C "${OFFICE_OXIDE_PREFIX}" test -f "${OFFICE_OXIDE_PREFIX}/include/office_oxide_c/office_oxide.h" test -f "${OFFICE_OXIDE_PREFIX}/lib/liboffice_oxide.a" ln -sf "office_oxide_c/office_oxide.h" "${OFFICE_OXIDE_PREFIX}/include/office_oxide.h" test -f "${OFFICE_OXIDE_PREFIX}/include/office_oxide.h" echo "OFFICE_OXIDE_PREFIX=${OFFICE_OXIDE_PREFIX}" >> "${GITHUB_ENV}" - name: Set up MSYS2 if: runner.os == 'Windows' uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.msystem }} update: true install: ${{ matrix.msys2_packages }} path-type: inherit - name: Install Windows SIMDe headers if: runner.os == 'Windows' shell: msys2 {0} run: | set -euo pipefail simde_dir="$(cygpath -u "${RUNNER_TEMP}")/simde" simde_archive="$(cygpath -u "${RUNNER_TEMP}")/simde-v0.8.2.tar.gz" github_env="$(cygpath -u "${GITHUB_ENV}")" rm -rf "${simde_dir}" mkdir -p "${simde_dir}" curl -fsSL "https://github.com/simd-everywhere/simde/archive/refs/tags/v0.8.2.tar.gz" -o "${simde_archive}" tar xzf "${simde_archive}" -C "${simde_dir}" --strip-components=1 test -f "${simde_dir}/simde/x86/sse4.1.h" # Install SIMDe headers into the MSYS2 toolchain include directory. # CMake/Ninja invokes clang-scan-deps as a Windows-native executable; # a POSIX-style include path like /c/a/_temp/simde may not be resolved # there, so keep the headers under ${MINGW_PREFIX}/include instead. rm -rf "${MINGW_PREFIX}/include/simde" cp -R "${simde_dir}/simde" "${MINGW_PREFIX}/include/simde" test -f "${MINGW_PREFIX}/include/simde/x86/sse4.1.h" - name: Configure Windows C compiler if: runner.os == 'Windows' shell: msys2 {0} run: | set -euo pipefail cc_path="$(command -v clang.exe 2>/dev/null || command -v clang)" cxx_path="$(command -v clang++.exe 2>/dev/null || command -v clang++)" if [[ ! -f "${cc_path}" && -f "${cc_path}.exe" ]]; then cc_path="${cc_path}.exe" fi if [[ ! -f "${cxx_path}" && -f "${cxx_path}.exe" ]]; then cxx_path="${cxx_path}.exe" fi test -f "${cc_path}" test -f "${cxx_path}" cc="$(cygpath -m "${cc_path}")" cxx="$(cygpath -m "${cxx_path}")" github_env="$(cygpath -u "${GITHUB_ENV}")" pcre2_libdir="$(pkg-config --variable=libdir libpcre2-8)" pcre2_includedir="$(pkg-config --variable=includedir libpcre2-8)" echo "CC=${cc}" >> "${github_env}" echo "CXX=${cxx}" >> "${github_env}" echo "PCRE2_LIBDIR=$(cygpath -m "${pcre2_libdir}")" >> "${github_env}" echo "PCRE2_INCLUDEDIR=$(cygpath -m "${pcre2_includedir}")" >> "${github_env}" echo "Resolved MSYS2 clang: ${cc}" "${cc_path}" --version echo "Resolved MSYS2 clang++: ${cxx}" "${cxx_path}" --version - name: Set up Rust for Windows office_oxide staticlib if: runner.os == 'Windows' uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.rust_target }} - name: Build office_oxide native library for Windows GNU ABI if: runner.os == 'Windows' shell: msys2 {0} run: | set -euo pipefail office_oxide_prefix="$(cygpath -u "${RUNNER_TEMP}")/office_oxide" office_oxide_src="$(cygpath -u "${RUNNER_TEMP}")/office_oxide-src" archive_path="$(cygpath -u "${RUNNER_TEMP}")/office_oxide-v${OFFICE_OXIDE_VERSION}.tar.gz" github_env="$(cygpath -u "${GITHUB_ENV}")" rm -rf "${office_oxide_prefix}" "${office_oxide_src}" mkdir -p "${office_oxide_prefix}/include/office_oxide_c" "${office_oxide_prefix}/lib" "${office_oxide_src}" curl -fsSL "https://github.com/yfedoseev/office_oxide/archive/refs/tags/v${OFFICE_OXIDE_VERSION}.tar.gz" -o "${archive_path}" tar xzf "${archive_path}" -C "${office_oxide_src}" --strip-components=1 cd "${office_oxide_src}" cc_path="$(command -v clang.exe 2>/dev/null || command -v clang)" cxx_path="$(command -v clang++.exe 2>/dev/null || command -v clang++)" if [[ ! -f "${cc_path}" && -f "${cc_path}.exe" ]]; then cc_path="${cc_path}.exe" fi if [[ ! -f "${cxx_path}" && -f "${cxx_path}.exe" ]]; then cxx_path="${cxx_path}.exe" fi test -f "${cc_path}" test -f "${cxx_path}" export CC="${cc_path}" export CXX="${cxx_path}" export AR="$(command -v llvm-ar || command -v ar)" export CARGO_BUILD_TARGET="${{ matrix.rust_target }}" export RUSTFLAGS="-C target-feature=+crt-static -C link-arg=-fuse-ld=lld" case "${{ matrix.rust_target }}" in x86_64-pc-windows-gnullvm) export CARGO_TARGET_X86_64_PC_WINDOWS_GNULLVM_LINKER="${CC}" export CARGO_TARGET_X86_64_PC_WINDOWS_GNULLVM_AR="${AR}" ;; aarch64-pc-windows-gnullvm) export CARGO_TARGET_AARCH64_PC_WINDOWS_GNULLVM_LINKER="${CC}" export CARGO_TARGET_AARCH64_PC_WINDOWS_GNULLVM_AR="${AR}" ;; *) echo "Unsupported Rust target: ${{ matrix.rust_target }}" exit 1 ;; esac # The release workflow only needs the static archive for cgo. # Building cdylib on Windows pulls in extra MinGW runtime libraries, # which can fail under the CLANG64/CLANGARM64 environments. perl -0pi -e 's/crate-type\s*=\s*\[[^\]]+\]/crate-type = ["staticlib"]/s' Cargo.toml cargo build --release --lib --target "${CARGO_BUILD_TARGET}" --no-default-features cp "include/office_oxide_c/office_oxide.h" "${office_oxide_prefix}/include/office_oxide_c/office_oxide.h" cp "include/office_oxide_c/office_oxide.h" "${office_oxide_prefix}/include/office_oxide.h" cp "target/${CARGO_BUILD_TARGET}/release/liboffice_oxide.a" "${office_oxide_prefix}/lib/liboffice_oxide.a" test -f "${office_oxide_prefix}/include/office_oxide_c/office_oxide.h" test -f "${office_oxide_prefix}/include/office_oxide.h" test -f "${office_oxide_prefix}/lib/liboffice_oxide.a" echo "OFFICE_OXIDE_PREFIX=$(cygpath -m "${office_oxide_prefix}")" >> "${github_env}" - name: Build rag tokenizer native library if: runner.os != 'Windows' shell: bash run: | set -euo pipefail cmake_args=( -S internal/cpp -B internal/cpp/cmake-build-release -DCMAKE_BUILD_TYPE=Release ) if [[ "${{ matrix.goos }}" == "darwin" ]]; then simde_prefix="$(brew --prefix simde)" pcre2_prefix="$(brew --prefix pcre2)" cmake_args+=( -DCMAKE_PREFIX_PATH="${pcre2_prefix};${simde_prefix}" -DCMAKE_C_FLAGS="-I${simde_prefix}/include" -DCMAKE_CXX_FLAGS="-I${simde_prefix}/include" ) fi cmake "${cmake_args[@]}" cmake --build internal/cpp/cmake-build-release --target rag_tokenizer_c_api --parallel test -f internal/cpp/cmake-build-release/librag_tokenizer_c_api.a - name: Build rag tokenizer native library if: runner.os == 'Windows' shell: msys2 {0} run: | set -euo pipefail cc_path="$(command -v clang.exe 2>/dev/null || command -v clang)" cxx_path="$(command -v clang++.exe 2>/dev/null || command -v clang++)" if [[ ! -f "${cc_path}" && -f "${cc_path}.exe" ]]; then cc_path="${cc_path}.exe" fi if [[ ! -f "${cxx_path}" && -f "${cxx_path}.exe" ]]; then cxx_path="${cxx_path}.exe" fi test -f "${cc_path}" test -f "${cxx_path}" test -f "${MINGW_PREFIX}/include/simde/x86/sse4.1.h" cmake -S internal/cpp -B internal/cpp/cmake-build-release -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_C_COMPILER="$(cygpath -m "${cc_path}")" \ -DCMAKE_CXX_COMPILER="$(cygpath -m "${cxx_path}")" \ -DCMAKE_C_FLAGS="-I${MINGW_PREFIX}/include" \ -DCMAKE_CXX_FLAGS="-I${MINGW_PREFIX}/include" cmake --build internal/cpp/cmake-build-release --target rag_tokenizer_c_api --parallel test -f internal/cpp/cmake-build-release/librag_tokenizer_c_api.a - name: Build Go CLI release binaries if: runner.os != 'Windows' shell: bash run: | set -euo pipefail mkdir -p "${DIST_DIR}" if [[ ! -e "${CLI_MAIN}" ]]; then echo "::error::Go CLI entry does not exist: ${CLI_MAIN}" echo "::error::Please update CLI_MAIN in .github/workflows/release.yml" exit 1 fi echo "Building Go CLI release binaries" echo "CLI name: ${CLI_NAME}" echo "CLI main: ${CLI_MAIN}" echo "Release tag: ${RELEASE_TAG}" echo "Commit: ${GITHUB_SHA}" output="${DIST_DIR}/${CLI_NAME}-${RELEASE_TAG}-${{ matrix.goos }}-${{ matrix.goarch }}" echo "Building ${{ matrix.goos }}/${{ matrix.goarch }} -> ${output}" case "${{ matrix.goos }}" in linux) cgo_ldflags="${OFFICE_OXIDE_PREFIX}/lib/liboffice_oxide.a -lm -ldl -lpthread" ;; darwin) cgo_ldflags="${OFFICE_OXIDE_PREFIX}/lib/liboffice_oxide.a" ;; *) echo "::error::Unsupported Unix target: ${{ matrix.goos }}" exit 1 ;; esac cgo_cflags="-I${OFFICE_OXIDE_PREFIX}/include -I${OFFICE_OXIDE_PREFIX}/include/office_oxide_c" echo "office_oxide prefix: ${OFFICE_OXIDE_PREFIX}" echo "CGO_CFLAGS: ${cgo_cflags}" echo "CGO_LDFLAGS: ${cgo_ldflags}" CGO_ENABLED=1 \ CGO_CFLAGS="${cgo_cflags}" \ CGO_LDFLAGS="${cgo_ldflags}" \ GOOS="${{ matrix.goos }}" \ GOARCH="${{ matrix.goarch }}" \ go build \ -trimpath \ -ldflags="-s -w -X main.version=${RELEASE_TAG} -X main.commit=${GITHUB_SHA}" \ -o "${output}" \ "${CLI_MAIN}" chmod +x "${output}" if [[ "${{ matrix.goos }}" == "linux" ]] && command -v ldd >/dev/null 2>&1; then if ldd "${output}" 2>&1 | grep -q "liboffice_oxide"; then echo "::error::linux CLI unexpectedly links liboffice_oxide dynamically" ldd "${output}" || true exit 1 fi echo "Verified linux CLI does not require liboffice_oxide.so at runtime" fi - name: Build Go CLI release binaries if: runner.os == 'Windows' shell: pwsh run: | New-Item -ItemType Directory -Force -Path $env:DIST_DIR | Out-Null if (-not (Test-Path $env:CLI_MAIN)) { Write-Error "Go CLI entry does not exist: $env:CLI_MAIN" exit 1 } $output = Join-Path $env:DIST_DIR "${env:CLI_NAME}-${env:RELEASE_TAG}-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.output_ext }}" Write-Host "Building ${{ matrix.goos }}/${{ matrix.goarch }} -> $output" $officeOxidePrefix = $env:OFFICE_OXIDE_PREFIX -replace "\\", "/" $cc = $env:CC if ([string]::IsNullOrWhiteSpace($cc)) { Write-Error "CC is not set" exit 1 } if (-not (Test-Path $cc)) { Write-Error "C compiler does not exist: $cc" exit 1 } if (-not (Test-Path "${officeOxidePrefix}/lib/liboffice_oxide.a")) { Write-Error "liboffice_oxide.a does not exist: ${officeOxidePrefix}/lib/liboffice_oxide.a" exit 1 } if (-not (Test-Path "internal/cpp/cmake-build-release/librag_tokenizer_c_api.a")) { Write-Error "librag_tokenizer_c_api.a does not exist" exit 1 } if ([string]::IsNullOrWhiteSpace($env:PCRE2_LIBDIR) -or -not (Test-Path $env:PCRE2_LIBDIR)) { Write-Error "PCRE2_LIBDIR is not set or does not exist: $env:PCRE2_LIBDIR" exit 1 } $ragTokenizerLib = (Resolve-Path "internal/cpp/cmake-build-release/librag_tokenizer_c_api.a").Path -replace '\\', '/' $pcre2LibDir = $env:PCRE2_LIBDIR -replace '\\', '/' $pcre2IncludeDir = $env:PCRE2_INCLUDEDIR -replace '\\', '/' $env:CGO_ENABLED = "1" $env:CC = $cc $env:CGO_CFLAGS = "-I${officeOxidePrefix}/include -I${officeOxidePrefix}/include/office_oxide_c -I${pcre2IncludeDir}" $env:CGO_LDFLAGS = "${officeOxidePrefix}/lib/liboffice_oxide.a ${ragTokenizerLib} -L${pcre2LibDir} -lpcre2-8 -lc++ -static -static-libgcc -static-libstdc++ -lws2_32 -lbcrypt -lntdll -luserenv -ladvapi32" $env:GOOS = "${{ matrix.goos }}" $env:GOARCH = "${{ matrix.goarch }}" Write-Host "CC: $env:CC" & $env:CC --version Write-Host "office_oxide prefix: $officeOxidePrefix" Write-Host "CGO_CFLAGS: $env:CGO_CFLAGS" Write-Host "CGO_LDFLAGS: $env:CGO_LDFLAGS" go build ` -trimpath ` -ldflags="-s -w -X main.version=$env:RELEASE_TAG -X main.commit=$env:GITHUB_SHA" ` -o "$output" ` "$env:CLI_MAIN" - name: Upload CLI artifact uses: actions/upload-artifact@v4 with: name: cli-${{ matrix.goos }}-${{ matrix.goarch }} path: dist/cli/* if-no-files-found: error publish_cli_assets: needs: - prepare - build_cli runs-on: [ "self-hosted", "ragflow-release" ] steps: - name: Ensure workspace ownership run: echo "chown -R ${USER} ${GITHUB_WORKSPACE}" && sudo chown -R ${USER} ${GITHUB_WORKSPACE} # https://github.com/actions/checkout/blob/v6/README.md - name: Check out code uses: actions/checkout@v6 with: token: ${{ secrets.GITHUB_TOKEN }} fetch-depth: 0 fetch-tags: true - name: Download CLI artifacts uses: actions/download-artifact@v5 with: pattern: cli-* path: dist/cli merge-multiple: true - name: Prepare CLI release assets env: RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }} run: | set -euo pipefail RELEASE_DATETIME=$(date --rfc-3339=seconds) echo Release ${RELEASE_TAG} created from ${GITHUB_SHA} at ${RELEASE_DATETIME} > release_body.md cd dist/cli sha256sum * > SHA256SUMS cd - echo "Generated CLI release assets:" ls -lh dist/cli - name: Upload Go CLI release assets uses: softprops/action-gh-release@v2 with: token: ${{ secrets.GITHUB_TOKEN }} prerelease: ${{ needs.prepare.outputs.prerelease }} tag_name: ${{ needs.prepare.outputs.release_tag }} body_path: release_body.md files: | dist/cli/* tools/scripts/install.sh tools/scripts/install.ps1 release: needs: - prepare - publish_cli_assets runs-on: [ "self-hosted", "ragflow-release" ] env: RELEASE_TAG: ${{ needs.prepare.outputs.release_tag }} steps: - name: Ensure workspace ownership run: echo "chown -R ${USER} ${GITHUB_WORKSPACE}" && sudo chown -R ${USER} ${GITHUB_WORKSPACE} # https://github.com/actions/checkout/blob/v6/README.md - name: Check out code uses: actions/checkout@v6 with: token: ${{ secrets.GITHUB_TOKEN }} fetch-depth: 0 fetch-tags: true - name: Build and push image run: | sudo docker login --username infiniflow --password-stdin <<< ${{ secrets.DOCKERHUB_TOKEN }} sudo docker build -t infiniflow/ragflow:${RELEASE_TAG} -f Dockerfile . sudo docker tag infiniflow/ragflow:${RELEASE_TAG} infiniflow/ragflow:latest sudo docker push infiniflow/ragflow:${RELEASE_TAG} sudo docker push infiniflow/ragflow:latest - name: Build and push ragflow-sdk if: startsWith(github.ref, 'refs/tags/v') run: | cd sdk/python && uv build && uv publish --token ${{ secrets.PYPI_API_TOKEN }} - name: Build and push ragflow-cli if: startsWith(github.ref, 'refs/tags/v') run: | cd admin/client && uv build && uv publish --token ${{ secrets.PYPI_API_TOKEN }}