diff --git a/.github/workflows/build-sandbox-server-image.yml b/.github/workflows/build-sandbox-server-image.yml deleted file mode 100644 index 9387a0904e..0000000000 --- a/.github/workflows/build-sandbox-server-image.yml +++ /dev/null @@ -1,132 +0,0 @@ -name: Build Sandbox Server Image - -on: - workflow_dispatch: - inputs: - tag: - description: 'Image tag (e.g., v1.0.0)' - required: true - type: string - -jobs: - build-sandbox-server-images: - permissions: - packages: write - contents: read - attestations: write - id-token: write - strategy: - matrix: - archs: - - arch: amd64 - - arch: arm64 - runs-on: ubuntu-24.04-arm - runs-on: ${{ matrix.archs.runs-on || 'ubuntu-24.04' }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - driver-opts: network=host - - - name: Cache Docker layers - uses: actions/cache@v4 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-${{ matrix.archs.arch }}-sandbox-server-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-${{ matrix.archs.arch }}-sandbox-server-buildx- - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to Ali Hub - uses: docker/login-action@v3 - with: - registry: registry.cn-hangzhou.aliyuncs.com - username: ${{ secrets.FASTGPT_ALI_IMAGE_USER }} - password: ${{ secrets.FASTGPT_ALI_IMAGE_PSW }} - - - name: Build for ${{ matrix.archs.arch }} - id: build - uses: docker/build-push-action@v6 - with: - context: ./projects/sandbox_server - file: ./projects/sandbox_server/Dockerfile - platforms: linux/${{ matrix.archs.arch }} - labels: | - org.opencontainers.image.source=https://github.com/${{ github.repository }} - org.opencontainers.image.description=FastGPT Sandbox Server image - outputs: type=image,"name=ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox-server,${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-sandbox-server",push-by-digest=true,push=true - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache - - - name: Export digest - run: | - mkdir -p ${{ runner.temp }}/digests/fastgpt-sandbox-server - digest="${{ steps.build.outputs.digest }}" - touch "${{ runner.temp }}/digests/fastgpt-sandbox-server/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digests-fastgpt-sandbox-server-${{ github.sha }}-${{ matrix.archs.arch }} - path: ${{ runner.temp }}/digests/fastgpt-sandbox-server/* - if-no-files-found: error - retention-days: 1 - - release-sandbox-server-images: - permissions: - packages: write - contents: read - attestations: write - id-token: write - needs: build-sandbox-server-images - runs-on: ubuntu-24.04 - steps: - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to Ali Hub - uses: docker/login-action@v3 - with: - registry: registry.cn-hangzhou.aliyuncs.com - username: ${{ secrets.FASTGPT_ALI_IMAGE_USER }} - password: ${{ secrets.FASTGPT_ALI_IMAGE_PSW }} - - - name: Download digests - uses: actions/download-artifact@v4 - with: - path: ${{ runner.temp }}/digests - pattern: digests-fastgpt-sandbox-server-${{ github.sha }}-* - merge-multiple: true - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Create manifest list and push - working-directory: ${{ runner.temp }}/digests - run: | - TAGS=( - "ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox-server:${{ inputs.tag }}" - "ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox-server:latest" - "${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-sandbox-server:${{ inputs.tag }}" - "${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-sandbox-server:latest" - ) - for TAG in "${TAGS[@]}"; do - docker buildx imagetools create -t $TAG \ - $(printf 'ghcr.io/${{ github.repository_owner }}/fastgpt-sandbox-server@sha256:%s ' *) - sleep 5 - done diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/deploy/docs.yml similarity index 100% rename from .github/workflows/docs-deploy.yml rename to .github/workflows/deploy/docs.yml diff --git a/.github/workflows/fastgpt-build-image.yml b/.github/workflows/deploy/fastgpt.yml similarity index 100% rename from .github/workflows/fastgpt-build-image.yml rename to .github/workflows/deploy/fastgpt.yml diff --git a/.github/workflows/marketplace-image.yml b/.github/workflows/deploy/marketplace-image.yml similarity index 100% rename from .github/workflows/marketplace-image.yml rename to .github/workflows/deploy/marketplace-image.yml diff --git a/.github/workflows/mcp_server-build-image.yml b/.github/workflows/deploy/mcp-server.yml similarity index 100% rename from .github/workflows/mcp_server-build-image.yml rename to .github/workflows/deploy/mcp-server.yml diff --git a/.github/workflows/sandbox-build-image.yml b/.github/workflows/deploy/sandbox.yml similarity index 92% rename from .github/workflows/sandbox-build-image.yml rename to .github/workflows/deploy/sandbox.yml index 7e6a0d1480..ad142d7e2e 100644 --- a/.github/workflows/sandbox-build-image.yml +++ b/.github/workflows/deploy/sandbox.yml @@ -38,24 +38,13 @@ jobs: restore-keys: | ${{ runner.os }}-sandbox-buildx- - # login docker + # login github - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Login to Ali Hub - uses: docker/login-action@v3 - with: - registry: registry.cn-hangzhou.aliyuncs.com - username: ${{ secrets.FASTGPT_ALI_IMAGE_USER }} - password: ${{ secrets.FASTGPT_ALI_IMAGE_PSW }} - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_HUB_NAME }} - password: ${{ secrets.DOCKER_HUB_PASSWORD }} - name: Build for ${{ matrix.arch }} id: build diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml deleted file mode 100644 index 7227777793..0000000000 --- a/.github/workflows/docs-preview.yml +++ /dev/null @@ -1,96 +0,0 @@ -name: Preview documents -on: - pull_request_target: - paths: - - 'document/**' - workflow_dispatch: - -permissions: - contents: read - packages: write - attestations: write - id-token: write - pull-requests: write - -jobs: - build-images: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - - name: Get current datetime - id: datetime - run: echo "datetime=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT - - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - # list of Docker images to use as base name for tags - images: | - ${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-docs - tags: | - ${{ steps.datetime.outputs.datetime }} - flavor: latest=false - - - name: Login to Aliyun - uses: docker/login-action@v3 - with: - registry: registry.cn-hangzhou.aliyuncs.com - username: ${{ secrets.FASTGPT_ALI_IMAGE_USER }} - password: ${{ secrets.FASTGPT_ALI_IMAGE_PSW }} - - - name: Build and push Docker images - uses: docker/build-push-action@v5 - with: - context: ./document - file: ./document/Dockerfile - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - FASTGPT_HOME_DOMAIN=https://fastgpt.io - outputs: - tags: ${{ steps.datetime.outputs.datetime }} - - update-images: - needs: build-images - runs-on: ubuntu-24.04 - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - # Add kubeconfig setup step to handle encoding issues - - name: Setup kubeconfig - run: | - mkdir -p $HOME/.kube - echo "${{ secrets.KUBE_CONFIG_CN }}" > $HOME/.kube/config - chmod 600 $HOME/.kube/config - - - name: Update deployment image - run: | - kubectl set image deployment/fastgpt-docs-preview fastgpt-docs-preview=${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-docs:${{ needs.build-images.outputs.tags }} - - - name: Annotate deployment - run: | - kubectl annotate deployment/fastgpt-docs-preview originImageName="${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-docs:${{ needs.build-images.outputs.tags }}" --overwrite - - - name: '@finleyge/github-tools' - uses: FinleyGe/github-tools@0.0.1 - id: print-image-label - if: success() - with: - token: ${{ secrets.GITHUB_TOKEN }} - tool: issue-comment - title: 'Docs Preview:' - body: | - --- - 🚀 **FastGPT Document Preview Ready!** - - 🔗 [👀 Click here to visit preview](https://pueuoharpgcl.sealoshzh.site) diff --git a/.github/workflows/fastgpt-build-image-personal.yml b/.github/workflows/fastgpt-build-image-personal.yml deleted file mode 100644 index b181bf06fe..0000000000 --- a/.github/workflows/fastgpt-build-image-personal.yml +++ /dev/null @@ -1,129 +0,0 @@ -name: Build FastGPT images in Personal warehouse -on: - workflow_dispatch: - push: - paths: - - 'projects/app/**' - - 'packages/**' - branches: - - 'main' - -jobs: - get-vars: - runs-on: ubuntu-24.04 - outputs: - docker_repo: ${{ steps.set_docker_repo.outputs.docker_repo }} - docker_tag: ${{ steps.set_docker_repo.outputs.docker_tag }} - steps: - - name: Set docker repository and tag - id: set_docker_repo - run: | - echo "docker_repo=ghcr.io/$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT - if [[ "${{ github.ref_name }}" == "main" ]]; then - echo "docker_tag=latest" >> $GITHUB_OUTPUT - else - echo "docker_tag=${{ github.ref_name }}" >> $GITHUB_OUTPUT - fi - - build-fastgpt-images: - needs: get-vars - permissions: - packages: write - contents: read - attestations: write - id-token: write - strategy: - matrix: - archs: - - arch: amd64 - runs-on: ubuntu-24.04 - - arch: arm64 - runs-on: ubuntu-24.04-arm - runs-on: ${{ matrix.archs.runs-on || 'ubuntu-24.04' }} - if: github.repository != 'labring/FastGPT' - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - driver-opts: network=host - - name: Cache Docker layers - uses: actions/cache@v3 - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-${{ matrix.archs.arch }}-buildx-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-${{ matrix.archs.arch }}-buildx- - - name: Login to GitHub Container Registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push for ${{ matrix.archs.arch }} - id: build - uses: docker/build-push-action@v6 - with: - context: . - file: projects/app/Dockerfile - platforms: linux/${{ matrix.archs.arch }} - labels: | - org.opencontainers.image.source=https://github.com/${{ github.repository }} - org.opencontainers.image.description=fastgpt image - outputs: type=image,"name=${{ needs.get-vars.outputs.docker_repo }}",push-by-digest=true,push=true - cache-from: type=local,src=/tmp/.buildx-cache - cache-to: type=local,dest=/tmp/.buildx-cache - - name: Export digest - run: | - mkdir -p ${{ runner.temp }}/digests - digest="${{ steps.build.outputs.digest }}" - touch "${{ runner.temp }}/digests/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digests-${{ github.sha }}-${{ matrix.archs.arch }} - path: ${{ runner.temp }}/digests/* - if-no-files-found: error - retention-days: 1 - - release-fastgpt-images: - permissions: - packages: write - contents: read - attestations: write - id-token: write - needs: [get-vars, build-fastgpt-images] - runs-on: ubuntu-24.04 - steps: - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Download digests - uses: actions/download-artifact@v4 - with: - path: ${{ runner.temp }}/digests - pattern: digests-${{ github.sha }}-* - merge-multiple: true - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Set image name and tag - run: | - echo "Git_Tag=${{ needs.get-vars.outputs.docker_repo }}:${{ needs.get-vars.outputs.docker_tag }}" >> $GITHUB_ENV - echo "Git_Latest=${{ needs.get-vars.outputs.docker_repo }}:latest" >> $GITHUB_ENV - - - name: Create manifest list and push - working-directory: ${{ runner.temp }}/digests - run: | - TAGS="$(echo -e "${Git_Tag}\n${Git_Latest}")" - for TAG in $TAGS; do - docker buildx imagetools create -t $TAG \ - $(printf '${{ needs.get-vars.outputs.docker_repo }}@sha256:%s ' *) - sleep 5 - done diff --git a/.github/workflows/preview/docs-build.yml b/.github/workflows/preview/docs-build.yml new file mode 100644 index 0000000000..86ca3139b9 --- /dev/null +++ b/.github/workflows/preview/docs-build.yml @@ -0,0 +1,72 @@ +name: Build Docs Preview (Unprivileged) + +on: + pull_request: + paths: + - 'document/**' + types: [opened, synchronize, reopened] + pull_request_target: + paths: + - 'document/**' + types: [labeled] + +jobs: + build-docs-image: + # 外部贡献者需要 'safe-to-build' 标签 + if: | + (github.event_name == 'pull_request') || + (github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-build')) + + permissions: + contents: read + pull-requests: write + + runs-on: ubuntu-latest + + steps: + - name: Checkout PR code + uses: actions/checkout@v4 + with: + # 对于 pull_request_target,检出 PR 的代码 + ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.sha }} + + - name: Get current datetime + id: datetime + run: echo "datetime=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image + uses: docker/build-push-action@v5 + with: + context: ./document + file: ./document/Dockerfile + push: false + tags: fastgpt-docs:${{ steps.datetime.outputs.datetime }} + labels: | + org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT + org.opencontainers.image.description=FastGPT Docs Preview + build-args: | + FASTGPT_HOME_DOMAIN=https://fastgpt.io + outputs: type=docker,dest=/tmp/fastgpt-docs-${{ steps.datetime.outputs.datetime }}.tar + + - name: Upload image artifact + uses: actions/upload-artifact@v4 + with: + name: fastgpt-docs-${{ steps.datetime.outputs.datetime }} + path: /tmp/fastgpt-docs-${{ steps.datetime.outputs.datetime }}.tar + retention-days: 1 + + - name: Comment build status + uses: FinleyGe/github-tools@0.0.1 + if: success() + with: + token: ${{ secrets.GITHUB_TOKEN }} + tool: issue-comment + title: 'Docs Preview Build:' + body: | + Build completed. Waiting for deployment... + + outputs: + datetime: ${{ steps.datetime.outputs.datetime }} diff --git a/.github/workflows/preview/docs-push.yml b/.github/workflows/preview/docs-push.yml new file mode 100644 index 0000000000..5b1c4c1351 --- /dev/null +++ b/.github/workflows/preview/docs-push.yml @@ -0,0 +1,125 @@ +name: Deploy Docs Preview (Privileged) + +on: + workflow_run: + workflows: ["Build Docs Preview (Unprivileged)"] + types: [completed] + +permissions: + contents: read + packages: write + attestations: write + id-token: write + pull-requests: write + +jobs: + push-and-deploy: + if: ${{ github.event.workflow_run.conclusion == 'success' }} + runs-on: ubuntu-24.04 + + steps: + - name: Get PR information + id: pr + uses: actions/github-script@v7 + with: + script: | + const { data: pullRequests } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}` + }); + + if (pullRequests.length === 0) { + core.setFailed('No open PR found for this branch'); + return; + } + + const pr = pullRequests[0]; + core.setOutput('number', pr.number); + + - name: Get workflow artifacts + uses: actions/github-script@v7 + id: artifacts + with: + script: | + const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.payload.workflow_run.id, + }); + + const artifact = artifacts.data.artifacts[0]; + if (!artifact) { + core.setFailed('No artifact found'); + return; + } + + // Extract datetime from artifact name + const datetime = artifact.name.replace('fastgpt-docs-', ''); + core.setOutput('datetime', datetime); + core.setOutput('artifact_name', artifact.name); + + - name: Download image artifact + uses: actions/download-artifact@v4 + with: + name: ${{ steps.artifacts.outputs.artifact_name }} + path: /tmp/ + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Load Docker image + run: | + docker load -i /tmp/fastgpt-docs-${{ steps.artifacts.outputs.datetime }}.tar + + - name: Login to Aliyun + uses: docker/login-action@v3 + with: + registry: registry.cn-hangzhou.aliyuncs.com + username: ${{ secrets.FASTGPT_ALI_IMAGE_USER }} + password: ${{ secrets.FASTGPT_ALI_IMAGE_PSW }} + + - name: Tag and push image + run: | + docker tag fastgpt-docs:${{ steps.artifacts.outputs.datetime }} \ + ${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-docs:${{ steps.artifacts.outputs.datetime }} + docker push ${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-docs:${{ steps.artifacts.outputs.datetime }} + + - name: Setup kubeconfig + run: | + mkdir -p $HOME/.kube + echo "${{ secrets.KUBE_CONFIG_CN }}" > $HOME/.kube/config + chmod 600 $HOME/.kube/config + + - name: Update deployment image + run: | + kubectl set image deployment/fastgpt-docs-preview \ + fastgpt-docs-preview=${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-docs:${{ steps.artifacts.outputs.datetime }} + + - name: Annotate deployment + run: | + kubectl annotate deployment/fastgpt-docs-preview \ + originImageName="${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-docs:${{ steps.artifacts.outputs.datetime }}" --overwrite + + - name: Comment deployment status + uses: FinleyGe/github-tools@0.0.1 + if: success() + with: + token: ${{ secrets.GITHUB_TOKEN }} + tool: issue-comment + title: 'Docs Preview:' + body: | + --- + 🚀 **FastGPT Document Preview Ready!** + + 🔗 [👀 Click here to visit preview](https://pueuoharpgcl.sealoshzh.site) + + - name: Comment on failure + uses: FinleyGe/github-tools@0.0.1 + if: failure() + with: + token: ${{ secrets.GITHUB_TOKEN }} + tool: issue-comment + title: 'Docs Preview Deployment Failed' + body: | + Failed to deploy docs preview. Please check workflow logs. diff --git a/.github/workflows/fastgpt-preview-image.yml b/.github/workflows/preview/fastgpt-build.yml similarity index 52% rename from .github/workflows/fastgpt-preview-image.yml rename to .github/workflows/preview/fastgpt-build.yml index 9c4795241b..b85d76c6b0 100644 --- a/.github/workflows/fastgpt-preview-image.yml +++ b/.github/workflows/preview/fastgpt-build.yml @@ -1,15 +1,22 @@ -name: Preview FastGPT images +name: FastGPT Build (Unprivileged) + on: + pull_request: + # 支持所有分支 + types: [opened, synchronize, reopened] pull_request_target: - workflow_dispatch: + # 外部贡献者需要标签批准 + types: [labeled] jobs: - preview-fastgpt-images: + build-preview-images: + # 外部贡献者需要 'safe-to-build' 标签 + if: | + (github.event_name == 'pull_request') || + (github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-build')) + permissions: contents: read - packages: write - attestations: write - id-token: write pull-requests: write runs-on: ubuntu-24.04 @@ -19,21 +26,20 @@ jobs: fail-fast: false # 即使一个镜像构建失败,也继续构建其他镜像 steps: - - name: Checkout - uses: actions/checkout@v3 + - name: Checkout PR code + uses: actions/checkout@v4 with: - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{ github.event.pull_request.head.repo.full_name }} + # 对于 pull_request_target,检出 PR 的代码 + ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.sha }} fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 with: driver-opts: network=host - name: Cache Docker layers - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }}-${{ matrix.image }} @@ -41,50 +47,54 @@ jobs: ${{ runner.os }}-buildx-${{ github.sha }}- ${{ runner.os }}-buildx- - - name: Login to Aliyun Container Registry - uses: docker/login-action@v3 - with: - registry: registry.cn-hangzhou.aliyuncs.com - username: ${{ secrets.FASTGPT_ALI_IMAGE_USER }} - password: ${{ secrets.FASTGPT_ALI_IMAGE_PSW }} - - name: Set image config id: config run: | if [[ "${{ matrix.image }}" == "fastgpt" ]]; then echo "DOCKERFILE=projects/app/Dockerfile" >> $GITHUB_OUTPUT echo "DESCRIPTION=fastgpt-pr image" >> $GITHUB_OUTPUT - echo "DOCKER_REPO_TAGGED=${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-pr:fatsgpt_${{ github.event.pull_request.head.sha }}" >> $GITHUB_OUTPUT + echo "IMAGE_NAME=fastgpt" >> $GITHUB_OUTPUT elif [[ "${{ matrix.image }}" == "sandbox" ]]; then echo "DOCKERFILE=projects/sandbox/Dockerfile" >> $GITHUB_OUTPUT echo "DESCRIPTION=fastgpt-sandbox-pr image" >> $GITHUB_OUTPUT - echo "DOCKER_REPO_TAGGED=${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-pr:fatsgpt_sandbox_${{ github.event.pull_request.head.sha }}" >> $GITHUB_OUTPUT + echo "IMAGE_NAME=fastgpt-sandbox" >> $GITHUB_OUTPUT elif [[ "${{ matrix.image }}" == "mcp_server" ]]; then echo "DOCKERFILE=projects/mcp_server/Dockerfile" >> $GITHUB_OUTPUT echo "DESCRIPTION=fastgpt-mcp_server-pr image" >> $GITHUB_OUTPUT - echo "DOCKER_REPO_TAGGED=${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-pr:fatsgpt_mcp_server_${{ github.event.pull_request.head.sha }}" >> $GITHUB_OUTPUT + echo "IMAGE_NAME=fastgpt-mcp-server" >> $GITHUB_OUTPUT fi - - name: Build ${{ matrix.image }} image for PR + - name: Build ${{ matrix.image }} image run: | docker buildx build \ -f ${{ steps.config.outputs.DOCKERFILE }} \ --label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \ --label "org.opencontainers.image.description=${{ steps.config.outputs.DESCRIPTION }}" \ - --push \ + --label "org.opencontainers.image.revision=${{ github.sha }}" \ --cache-from=type=local,src=/tmp/.buildx-cache \ - -t ${{ steps.config.outputs.DOCKER_REPO_TAGGED }} \ + --cache-to=type=local,dest=/tmp/.buildx-cache-new,mode=max \ + --output type=docker,dest=/tmp/${{ steps.config.outputs.IMAGE_NAME }}-${{ github.sha }}.tar \ + -t preview-image:${{ github.sha }} \ . - - name: '@finleyge/github-tools' + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache + + - name: Upload image artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.config.outputs.IMAGE_NAME }}-${{ github.sha }} + path: /tmp/${{ steps.config.outputs.IMAGE_NAME }}-${{ github.sha }}.tar + retention-days: 1 + + - name: Comment build status uses: FinleyGe/github-tools@0.0.1 - id: print-image-label if: success() with: token: ${{ secrets.GITHUB_TOKEN }} tool: issue-comment - title: 'Preview ${{ matrix.image }} Image:' + title: 'Preview ${{ matrix.image }} Image Built:' body: | - ``` - ${{ steps.config.outputs.DOCKER_REPO_TAGGED }} - ``` + Build completed. Waiting for push workflow... diff --git a/.github/workflows/preview/fastgpt-push.yml b/.github/workflows/preview/fastgpt-push.yml new file mode 100644 index 0000000000..b2831c03af --- /dev/null +++ b/.github/workflows/preview/fastgpt-push.yml @@ -0,0 +1,123 @@ +name: FastGPT Push (Privileged) + +on: + workflow_run: + workflows: ["FastGPT Build (Unprivileged)"] + types: [completed] + +jobs: + push-preview-images: + # 只在构建成功时运行 + if: ${{ github.event.workflow_run.conclusion == 'success' }} + + permissions: + contents: read + packages: write + attestations: write + id-token: write + pull-requests: write + + runs-on: ubuntu-24.04 + strategy: + matrix: + image: [fastgpt, sandbox, mcp_server] + fail-fast: false + + steps: + - name: Get PR information + id: pr + uses: actions/github-script@v7 + with: + script: | + const { data: pullRequests } = await github.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + head: `${context.repo.owner}:${context.payload.workflow_run.head_branch}` + }); + + if (pullRequests.length === 0) { + core.setFailed('No open PR found for this branch'); + return; + } + + const pr = pullRequests[0]; + core.setOutput('number', pr.number); + core.setOutput('sha', context.payload.workflow_run.head_sha); + + - name: Set image config + id: config + run: | + SHA="${{ steps.pr.outputs.sha }}" + + if [[ "${{ matrix.image }}" == "fastgpt" ]]; then + echo "IMAGE_NAME=fastgpt" >> $GITHUB_OUTPUT + echo "DESCRIPTION=fastgpt-pr image" >> $GITHUB_OUTPUT + echo "DOCKER_REPO_TAGGED=${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-pr:fastgpt_${SHA}" >> $GITHUB_OUTPUT + elif [[ "${{ matrix.image }}" == "sandbox" ]]; then + echo "IMAGE_NAME=fastgpt-sandbox" >> $GITHUB_OUTPUT + echo "DESCRIPTION=fastgpt-sandbox-pr image" >> $GITHUB_OUTPUT + echo "DOCKER_REPO_TAGGED=${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-pr:fastgpt_sandbox_${SHA}" >> $GITHUB_OUTPUT + elif [[ "${{ matrix.image }}" == "mcp_server" ]]; then + echo "IMAGE_NAME=fastgpt-mcp-server" >> $GITHUB_OUTPUT + echo "DESCRIPTION=fastgpt-mcp_server-pr image" >> $GITHUB_OUTPUT + echo "DOCKER_REPO_TAGGED=${{ secrets.FASTGPT_ALI_IMAGE_PREFIX }}/fastgpt-pr:fastgpt_mcp_server_${SHA}" >> $GITHUB_OUTPUT + fi + + - name: Download image artifact + uses: actions/download-artifact@v4 + with: + name: ${{ steps.config.outputs.IMAGE_NAME }}-${{ steps.pr.outputs.sha }} + path: /tmp/ + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Load Docker image + run: | + docker load -i /tmp/${{ steps.config.outputs.IMAGE_NAME }}-${{ steps.pr.outputs.sha }}.tar + + - name: Scan image for vulnerabilities + continue-on-error: true + run: | + # 安装 Trivy + wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add - + echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | sudo tee -a /etc/apt/sources.list.d/trivy.list + sudo apt-get update + sudo apt-get install trivy -y + + # 扫描镜像 + trivy image --severity HIGH,CRITICAL --exit-code 0 preview-image:${{ steps.pr.outputs.sha }} + + - name: Login to Aliyun Container Registry + uses: docker/login-action@v3 + with: + registry: registry.cn-hangzhou.aliyuncs.com + username: ${{ secrets.FASTGPT_ALI_IMAGE_USER }} + password: ${{ secrets.FASTGPT_ALI_IMAGE_PSW }} + + - name: Tag and push image + run: | + docker tag preview-image:${{ steps.pr.outputs.sha }} ${{ steps.config.outputs.DOCKER_REPO_TAGGED }} + docker push ${{ steps.config.outputs.DOCKER_REPO_TAGGED }} + + - name: Comment push status + uses: FinleyGe/github-tools@0.0.1 + if: success() + with: + token: ${{ secrets.GITHUB_TOKEN }} + tool: issue-comment + title: 'Preview ${{ matrix.image }} Image:' + body: | + ``` + ${{ steps.config.outputs.DOCKER_REPO_TAGGED }} + ``` + + - name: Comment on failure + uses: FinleyGe/github-tools@0.0.1 + if: failure() + with: + token: ${{ secrets.GITHUB_TOKEN }} + tool: issue-comment + title: 'Preview ${{ matrix.image }} Image Push Failed' + body: | + Failed to push preview image. Please check workflow logs. diff --git a/.github/workflows/fastgpt-test.yaml b/.github/workflows/test/fastgpt-test.yaml similarity index 100% rename from .github/workflows/fastgpt-test.yaml rename to .github/workflows/test/fastgpt-test.yaml diff --git a/.github/workflows/sandbox-test.yaml b/.github/workflows/test/sandbox-test.yaml similarity index 100% rename from .github/workflows/sandbox-test.yaml rename to .github/workflows/test/sandbox-test.yaml diff --git a/packages/service/package.json b/packages/service/package.json index c8cae12a54..6545ab423e 100644 --- a/packages/service/package.json +++ b/packages/service/package.json @@ -8,7 +8,7 @@ }, "dependencies": { "@apidevtools/json-schema-ref-parser": "^11.7.2", - "@fastgpt-sdk/sandbox-adapter": "^0.0.18", + "@fastgpt-sdk/sandbox-adapter": "^0.0.19", "@fastgpt-sdk/storage": "catalog:", "@fastgpt-sdk/logger": "catalog:", "@fastgpt/global": "workspace:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf78a96f95..62d5b66a68 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -247,8 +247,8 @@ importers: specifier: 'catalog:' version: 0.1.2 '@fastgpt-sdk/sandbox-adapter': - specifier: ^0.0.18 - version: 0.0.18 + specifier: ^0.0.19 + version: 0.0.19 '@fastgpt-sdk/storage': specifier: 'catalog:' version: 0.6.15(@opentelemetry/api@1.9.0)(@types/node@24.0.13)(jiti@2.6.0)(lightningcss@1.30.1)(proxy-agent@6.5.0)(sass@1.85.1)(terser@5.39.0)(tsx@4.20.6)(yaml@2.8.1) @@ -2640,8 +2640,8 @@ packages: '@fastgpt-sdk/plugin@0.3.8': resolution: {integrity: sha512-GjKrXMHxeF5UMkYGXawrUpzZjVRw3DICNYODeYwsUVOy+/ltu5zuwsqLkuuGQ7Arp/SBCmYRjG/MHmeNp4xxfw==} - '@fastgpt-sdk/sandbox-adapter@0.0.18': - resolution: {integrity: sha512-5xjBQzG9wHi7oRxAlMHz5Sz282QEHCmJnqaX4o0H0vfDgOwanBfglwejmF9bPlqy+HnIZi3rIJseCleE3MqH2g==} + '@fastgpt-sdk/sandbox-adapter@0.0.19': + resolution: {integrity: sha512-024C9Ljoic7/oQm1awyLMWVl7kk9NuOGgUa8NC3wOS4GQrCVZCPCHK8YwqkRbKX9T0Akczc6RFaZj+kRJd3m4Q==} engines: {node: '>=18'} '@fastgpt-sdk/storage@0.6.15': @@ -13433,7 +13433,7 @@ snapshots: '@fortaine/fetch-event-source': 3.0.6 zod: 4.1.12 - '@fastgpt-sdk/sandbox-adapter@0.0.18': + '@fastgpt-sdk/sandbox-adapter@0.0.19': dependencies: '@alibaba-group/opensandbox': 0.1.4 diff --git a/projects/sandbox-sync-agent/Dockerfile b/projects/sandbox-sync-agent/Dockerfile new file mode 100644 index 0000000000..f601c5b19a --- /dev/null +++ b/projects/sandbox-sync-agent/Dockerfile @@ -0,0 +1,32 @@ +# 基于 base/ 目录构建的 fastgpt-agent-sandbox:latest,在其基础上注入 Sync Agent +# +# 构建顺序: +# 1. cd base && docker build -t fastgpt-agent-sandbox:latest . +# 2. docker build -f Dockerfile -t fastgpt-agent-sandbox:k8s . + +FROM fastgpt-agent-sandbox:latest + +USER root + +# 安装 Sync Agent 依赖 +RUN apt-get update && apt-get install -y \ + inotify-tools \ + && rm -rf /var/lib/apt/lists/* + +# 安装 MinIO Client (mc) +RUN curl -O https://dl.min.io/client/mc/release/linux-amd64/mc && \ + chmod +x mc && \ + mv mc /usr/local/bin/ + +COPY sync.sh /sync.sh +COPY entrypoint.sh /entrypoint.sh +COPY http_server.py /http_server.py + +RUN chmod +x /sync.sh /entrypoint.sh + +# 8081: Sync Agent HTTP API(健康检查 / 手动触发同步) +EXPOSE 8081 + +USER sandbox + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/projects/sandbox-sync-agent/Dockerfile.docker-runtime b/projects/sandbox-sync-agent/Dockerfile.docker-runtime new file mode 100644 index 0000000000..61c35f4540 --- /dev/null +++ b/projects/sandbox-sync-agent/Dockerfile.docker-runtime @@ -0,0 +1,35 @@ +# Docker 双进程模式镜像 +# 基于 base/ 目录构建的 fastgpt-agent-sandbox:latest,在其基础上注入 Sync Agent +# +# 构建顺序: +# 1. cd base && docker build -t fastgpt-agent-sandbox:latest . +# 2. docker build -f Dockerfile.docker-runtime -t fastgpt-agent-sandbox:docker . +FROM fastgpt-agent-sandbox:latest + +USER root + +# 安装 Sync Agent 依赖 +RUN apt-get update && apt-get install -y \ + inotify-tools \ + supervisor \ + && rm -rf /var/lib/apt/lists/* + +# 安装 MinIO Client +RUN curl -O https://dl.min.io/client/mc/release/linux-amd64/mc && \ + chmod +x mc && \ + mv mc /usr/local/bin/ + +# 复制 Sync Agent 脚本 +COPY sync.sh /opt/sync-agent/sync.sh +COPY http_server.py /opt/sync-agent/http_server.py +COPY docker-entrypoint.sh /opt/sync-agent/docker-entrypoint.sh +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf + +RUN chmod +x /opt/sync-agent/sync.sh \ + /opt/sync-agent/docker-entrypoint.sh && \ + mkdir -p /var/log/supervisor && \ + chown -R sandbox:sandbox /var/log/supervisor + +USER sandbox + +ENTRYPOINT ["/opt/sync-agent/docker-entrypoint.sh"] diff --git a/projects/sandbox-sync-agent/base/Dockerfile b/projects/sandbox-sync-agent/base/Dockerfile new file mode 100644 index 0000000000..efce855685 --- /dev/null +++ b/projects/sandbox-sync-agent/base/Dockerfile @@ -0,0 +1,46 @@ +# Skill Sandbox 基础镜像 +# 提供 code-server 开发环境,供 K8s Sidecar 和 Docker 双进程两种运行时使用 +# +# 构建:docker build -t fastgpt-agent-sandbox:latest . +# 产物:fastgpt-agent-sandbox:latest +FROM ubuntu:24.04 + +# Avoid prompts during installation +ENV DEBIAN_FRONTEND=noninteractive + +# Install dependencies +RUN apt-get update && apt-get install -y \ + git \ + jq \ + ripgrep \ + vim-tiny \ + tree \ + zip \ + unzip \ + curl \ + ca-certificates +RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - +RUN apt install -y python3 nodejs +RUN rm -rf /var/lib/apt/lists/* + +# Install code-server using the official script +RUN curl -fsSL https://code-server.dev/install.sh | sh + +# Create a non-root user for security +RUN useradd --create-home --shell /bin/bash sandbox + +USER sandbox +WORKDIR /home/sandbox + +# Copy VS Code settings +RUN mkdir -p /home/sandbox/.local/share/code-server/User +COPY --chown=sandbox:sandbox settings.json /home/sandbox/.local/share/code-server/User/settings.json + +# Copy and configure entrypoint +COPY --chown=sandbox:sandbox entrypoint.sh /home/sandbox/entrypoint.sh +RUN chmod +x /home/sandbox/entrypoint.sh + +# Expose code-server port +EXPOSE 8080 + +ENTRYPOINT ["/home/sandbox/entrypoint.sh"] diff --git a/projects/sandbox-sync-agent/base/entrypoint.sh b/projects/sandbox-sync-agent/base/entrypoint.sh new file mode 100644 index 0000000000..52eed6a5b7 --- /dev/null +++ b/projects/sandbox-sync-agent/base/entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Set work directory from environment variable, default to /home/sandbox +WORKDIR="${FASTGPT_WORKDIR:-/home/sandbox}" +mkdir -p "${WORKDIR}" + +# Start code-server +# --bind-addr 0.0.0.0:8080 allows access from outside the container +# --auth none removes password protection +exec code-server \ + --bind-addr 0.0.0.0:8080 \ + --auth none \ + --disable-telemetry \ + --disable-update-check \ + --disable-workspace-trust \ + --disable-getting-started-override \ + --app-name "Skills" \ + --user-data-dir /home/sandbox/.local/share/code-server \ + "${WORKDIR}" diff --git a/projects/sandbox-sync-agent/base/settings.json b/projects/sandbox-sync-agent/base/settings.json new file mode 100644 index 0000000000..5d1d675036 --- /dev/null +++ b/projects/sandbox-sync-agent/base/settings.json @@ -0,0 +1,31 @@ +{ + "workbench.startupEditor": "none", + "workbench.welcomePage.tasks.showOnStart": false, + "telemetry.telemetryLevel": "off", + "editor.minimap.enabled": false, + "workbench.tips.enabled": false, + "extensions.autoCheckUpdates": false, + "extensions.autoUpdate": false, + "editor.accessibilitySupport": "off", + "editor.hover.enabled": "off", + "editor.hover.sticky": false, + "editor.acceptSuggestionOnCommitCharacter": false, + "editor.acceptSuggestionOnEnter": "off", + "editor.inlineSuggest.edits.allowCodeShifting": "never", + "editor.inlineSuggest.edits.renderSideBySide": "never", + "editor.inlineSuggest.edits.showLongDistanceHint": false, + "editor.inlineSuggest.enabled": false, + "editor.inlineSuggest.experimental.emptyResponseInformation": false, + "editor.inlineSuggest.showToolbar": "never", + "editor.inlineSuggest.suppressInSnippetMode": false, + "workbench.commandPalette.showAskInChat": false, + "workbench.commandPalette.experimental.enableNaturalLanguageSearch": false, + "workbench.tree.enableStickyScroll": false, + "chat.disableAIFeatures": true, + "workbench.activityBar.location": "hidden", + "workbench.statusBar.visible": false, + "workbench.editor.showTabs": "none", + "window.commandCenter": false, + "workbench.editor.editorActionsLocation": "hidden", + "workbench.layoutControl.enabled": false +} diff --git a/projects/sandbox-sync-agent/build.sh b/projects/sandbox-sync-agent/build.sh new file mode 100755 index 0000000000..5447132157 --- /dev/null +++ b/projects/sandbox-sync-agent/build.sh @@ -0,0 +1,227 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Build script for sandbox-sync-agent images. +# Usage: ./build.sh [OPTIONS] +# +# Images: +# base/Dockerfile -> fastgpt-agent-sandbox:latest (base image) +# Dockerfile -> fastgpt-agent-sandbox:k8s (K8s sidecar) +# Dockerfile.docker-runtime -> fastgpt-agent-sandbox:docker (Docker dual-process) + +# --------------------------------------------------------------------------- +# Defaults +# --------------------------------------------------------------------------- +REGISTRY="" +TAG="latest" +TARGET="all" +NO_CACHE="" +PLATFORM="" + +# --------------------------------------------------------------------------- +# Parse arguments +# --------------------------------------------------------------------------- +while [[ $# -gt 0 ]]; do + case "$1" in + --registry) + REGISTRY="${2:?'--registry requires a value'}" + shift 2 + ;; + --tag) + TAG="${2:?'--tag requires a value'}" + shift 2 + ;; + --target) + TARGET="${2:?'--target requires a value (base|k8s|docker|all)'}" + shift 2 + ;; + --no-cache) + NO_CACHE="--no-cache" + shift + ;; + --platform) + PLATFORM="${2:?'--platform requires a value, e.g. linux/amd64'}" + shift 2 + ;; + -h|--help) + echo "Usage: $0 [--registry ] [--tag ] [--target base|k8s|docker|all] [--no-cache] [--platform ]" + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + exit 1 + ;; + esac +done + +# Validate --target +case "$TARGET" in + base|k8s|docker|all) ;; + *) + echo "Error: --target must be one of: base, k8s, docker, all" >&2 + exit 1 + ;; +esac + +# --------------------------------------------------------------------------- +# Always run from the directory that contains this script +# --------------------------------------------------------------------------- +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# --------------------------------------------------------------------------- +# Helper: build a docker image name +# $1 = variant suffix (latest | k8s | docker) +# Returns the full image reference based on registry / tag settings. +# --------------------------------------------------------------------------- +image_name() { + local suffix="$1" + local name="fastgpt-agent-sandbox:${suffix}" + + # Override the tag portion when the user supplied --tag and suffix == "latest" + # (base image is always tagged :latest locally; remote tag uses user-supplied tag) + if [[ -n "$REGISTRY" ]]; then + if [[ "$suffix" == "latest" ]]; then + echo "${REGISTRY}/fastgpt-agent-sandbox:${TAG}" + else + echo "${REGISTRY}/fastgpt-agent-sandbox-${suffix}:${TAG}" + fi + else + if [[ "$suffix" == "latest" && "$TAG" != "latest" ]]; then + echo "fastgpt-agent-sandbox:${TAG}" + else + echo "$name" + fi + fi +} + +# --------------------------------------------------------------------------- +# Helper: build extra docker flags +# --------------------------------------------------------------------------- +extra_flags() { + local flags="$NO_CACHE" + if [[ -n "$PLATFORM" ]]; then + flags="$flags --platform $PLATFORM" + fi + echo "$flags" +} + +# --------------------------------------------------------------------------- +# Print a section header +# --------------------------------------------------------------------------- +section() { + echo "" + echo "========================================" + echo " $*" + echo "========================================" +} + +# --------------------------------------------------------------------------- +# Build base image +# --------------------------------------------------------------------------- +build_base() { + section "Building BASE image" + + # The base/ subdirectory is the build context + local local_tag="fastgpt-agent-sandbox:latest" + # shellcheck disable=SC2046 + docker build \ + -t "$local_tag" \ + $(extra_flags) \ + base/ + + echo "Built: $local_tag" + + # If a registry or non-default tag is requested, add the remote tag as well + if [[ -n "$REGISTRY" ]] || [[ "$TAG" != "latest" ]]; then + local remote_tag + remote_tag="$(image_name latest)" + if [[ "$remote_tag" != "$local_tag" ]]; then + docker tag "$local_tag" "$remote_tag" + echo "Tagged: $remote_tag" + fi + fi +} + +# --------------------------------------------------------------------------- +# Build K8s sidecar image +# --------------------------------------------------------------------------- +build_k8s() { + section "Building K8S image" + + local tag + if [[ -n "$REGISTRY" ]]; then + tag="${REGISTRY}/fastgpt-agent-sandbox-k8s:${TAG}" + else + tag="fastgpt-agent-sandbox:k8s" + fi + + # shellcheck disable=SC2046 + docker build \ + -f Dockerfile \ + -t "$tag" \ + $(extra_flags) \ + . + + echo "Built: $tag" +} + +# --------------------------------------------------------------------------- +# Build Docker dual-process image +# --------------------------------------------------------------------------- +build_docker() { + section "Building DOCKER-RUNTIME image" + + local tag + if [[ -n "$REGISTRY" ]]; then + tag="${REGISTRY}/fastgpt-agent-sandbox-docker:${TAG}" + else + tag="fastgpt-agent-sandbox:docker" + fi + + # shellcheck disable=SC2046 + docker build \ + -f Dockerfile.docker-runtime \ + -t "$tag" \ + $(extra_flags) \ + . + + echo "Built: $tag" +} + +# --------------------------------------------------------------------------- +# Print summary of built images +# --------------------------------------------------------------------------- +print_summary() { + section "Build Summary" + echo "" + docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}" \ + | grep -E "REPOSITORY|fastgpt-agent-sandbox" || true +} + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- +echo "Target : $TARGET" +echo "Tag : $TAG" +echo "Registry: ${REGISTRY:-'(none)'}" +echo "Platform: ${PLATFORM:-'(default)'}" +echo "No-cache: ${NO_CACHE:-'(no)'}" + +# base must be built before k8s / docker when building all +if [[ "$TARGET" == "all" || "$TARGET" == "base" ]]; then + build_base +fi + +if [[ "$TARGET" == "all" || "$TARGET" == "k8s" ]]; then + build_k8s +fi + +if [[ "$TARGET" == "all" || "$TARGET" == "docker" ]]; then + build_docker +fi + +print_summary + +echo "" +echo "Done." diff --git a/projects/sandbox-sync-agent/docker-entrypoint.sh b/projects/sandbox-sync-agent/docker-entrypoint.sh new file mode 100644 index 0000000000..fb7879f1cd --- /dev/null +++ b/projects/sandbox-sync-agent/docker-entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -e + +# 配置 MinIO Client +mc alias set minio ${FASTGPT_MINIO_ENDPOINT} ${FASTGPT_MINIO_ACCESS_KEY} ${FASTGPT_MINIO_SECRET_KEY} --api S3v4 + +# 确保 bucket 存在 +mc mb minio/${FASTGPT_MINIO_BUCKET} --ignore-existing || true + +# Prepare work directory with correct permissions +export FASTGPT_WORKDIR="${FASTGPT_WORKDIR:-/home/sandbox}" +mkdir -p "${FASTGPT_WORKDIR}" + +# 是否启动 code-server(默认 true) +# 仅需文件同步时设置 FASTGPT_ENABLE_CODE_SERVER=false +export FASTGPT_ENABLE_CODE_SERVER=${FASTGPT_ENABLE_CODE_SERVER:-true} + +# 使用 supervisord 启动进程 +exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf diff --git a/projects/sandbox-sync-agent/entrypoint.sh b/projects/sandbox-sync-agent/entrypoint.sh new file mode 100644 index 0000000000..8118ad2ef5 --- /dev/null +++ b/projects/sandbox-sync-agent/entrypoint.sh @@ -0,0 +1,16 @@ +#!/bin/sh +set -e + +# 配置 MinIO Client +mc alias set minio ${FASTGPT_MINIO_ENDPOINT} ${FASTGPT_MINIO_ACCESS_KEY} ${FASTGPT_MINIO_SECRET_KEY} --api S3v4 + +# 确保 bucket 存在 +mc mb minio/${FASTGPT_MINIO_BUCKET} --ignore-existing || true + +# Pass FASTGPT_WORKDIR as FASTGPT_SYNC_PATH if FASTGPT_SYNC_PATH is not explicitly set +if [ -z "${FASTGPT_SYNC_PATH}" ] && [ -n "${FASTGPT_WORKDIR}" ]; then + export FASTGPT_SYNC_PATH="${FASTGPT_WORKDIR}" +fi + +# 启动 sync 服务 +exec /sync.sh diff --git a/projects/sandbox-sync-agent/http_server.py b/projects/sandbox-sync-agent/http_server.py new file mode 100644 index 0000000000..a45026574d --- /dev/null +++ b/projects/sandbox-sync-agent/http_server.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +""" +Sync Agent HTTP 服务 +提供健康检查和手动触发同步接口,读取 sync.sh 写入的状态文件。 +""" +import http.server +import json +import os +import pathlib +from datetime import datetime, timezone + +STATE_DIR = pathlib.Path(os.environ.get('STATE_DIR', '/tmp/sync-state')) +HTTP_PORT = int(os.environ.get('HTTP_PORT', '8081')) + + +class SyncAgentHandler(http.server.BaseHTTPRequestHandler): + def log_message(self, format, *args): + pass # 抑制每次请求的访问日志 + + def _read_state(self): + try: + last_sync = (STATE_DIR / 'last_sync').read_text().strip() + except Exception: + last_sync = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') + try: + pending = int((STATE_DIR / 'pending_count').read_text().strip()) + except Exception: + pending = 0 + return last_sync, pending + + def _send_json(self, code, body): + data = json.dumps(body).encode() + self.send_response(code) + self.send_header('Content-Type', 'application/json') + self.send_header('Content-Length', str(len(data))) + self.end_headers() + self.wfile.write(data) + + def do_GET(self): + if self.path == '/health': + last_sync, pending = self._read_state() + self._send_json(200, { + 'status': 'healthy', + 'lastSync': last_sync, + 'pendingCount': pending + }) + else: + self.send_response(404) + self.end_headers() + + def do_POST(self): + if self.path == '/sync': + STATE_DIR.mkdir(parents=True, exist_ok=True) + (STATE_DIR / 'trigger').touch() + self._send_json(200, {'success': True}) + else: + self.send_response(404) + self.end_headers() + + +if __name__ == '__main__': + STATE_DIR.mkdir(parents=True, exist_ok=True) + server = http.server.HTTPServer(('', HTTP_PORT), SyncAgentHandler) + print(f'[Sync] HTTP server listening on :{HTTP_PORT}', flush=True) + server.serve_forever() diff --git a/projects/sandbox-sync-agent/pool-skill-sandbox.yaml b/projects/sandbox-sync-agent/pool-skill-sandbox.yaml new file mode 100644 index 0000000000..2a2eb07f8b --- /dev/null +++ b/projects/sandbox-sync-agent/pool-skill-sandbox.yaml @@ -0,0 +1,112 @@ +apiVersion: sandbox.opensandbox.io/v1alpha1 +kind: Pool +metadata: + name: skill-sandbox-with-sync + namespace: opensandbox + labels: + app: skill-sandbox + component: sync-enabled +spec: + # 预热池大小 + minReady: 2 + maxSize: 20 + + template: + metadata: + labels: + app: skill-sandbox + spec: + volumes: + - name: workspace + emptyDir: + sizeLimit: 1Gi + + containers: + # 主容器:Skill Sandbox(code-server 开发环境) + - name: sandbox + image: fastgpt-agent-sandbox:latest + imagePullPolicy: IfNotPresent + env: + # FASTGPT_WORKDIR: the workspace directory opened by code-server. + # This is a subdirectory of the volume mountPath (/home/sandbox), + # keeping code under /home/sandbox/workspace separate from home files. + - name: FASTGPT_WORKDIR + value: "/home/sandbox/workspace" + volumeMounts: + - name: workspace + mountPath: /home/sandbox + ports: + - containerPort: 8080 + name: code-server + resources: + requests: + cpu: 500m + memory: 512Mi + limits: + cpu: 2 + memory: 2Gi + readinessProbe: + httpGet: + path: / + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 10 + + # Sidecar:Sync Agent(MinIO 文件同步) + - name: sync-agent + image: fastgpt-agent-sandbox:k8s + imagePullPolicy: IfNotPresent + env: + - name: FASTGPT_MINIO_ENDPOINT + valueFrom: + secretKeyRef: + name: minio-credentials + key: endpoint + - name: FASTGPT_MINIO_ACCESS_KEY + valueFrom: + secretKeyRef: + name: minio-credentials + key: accessKey + - name: FASTGPT_MINIO_SECRET_KEY + valueFrom: + secretKeyRef: + name: minio-credentials + key: secretKey + - name: FASTGPT_MINIO_BUCKET + value: "fastgpt-private" + - name: FASTGPT_SESSION_ID + valueFrom: + fieldRef: + fieldPath: metadata.labels['session-id'] + - name: FASTGPT_SYNC_PATH + value: "/home/sandbox/workspace" + - name: SYNC_INTERVAL + value: "60" + - name: HTTP_PORT + value: "8081" + volumeMounts: + - name: workspace + mountPath: /home/sandbox + ports: + - containerPort: 8081 + name: sync-api + resources: + requests: + cpu: 100m + memory: 64Mi + limits: + cpu: 500m + memory: 256Mi + livenessProbe: + httpGet: + path: /health + port: 8081 + initialDelaySeconds: 10 + periodSeconds: 30 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 diff --git a/projects/sandbox-sync-agent/supervisord.conf b/projects/sandbox-sync-agent/supervisord.conf new file mode 100644 index 0000000000..c0d0e6a689 --- /dev/null +++ b/projects/sandbox-sync-agent/supervisord.conf @@ -0,0 +1,24 @@ +[supervisord] +nodaemon=true +logfile=/var/log/supervisor/supervisord.log +pidfile=/tmp/supervisord.pid + +# Sandbox 主进程(code-server) +# 通过环境变量 FASTGPT_ENABLE_CODE_SERVER=true|false 控制是否启动,默认 true +[program:code-server] +command=/home/sandbox/entrypoint.sh +user=sandbox +autostart=%(ENV_FASTGPT_ENABLE_CODE_SERVER)s +autorestart=true +stdout_logfile=/var/log/supervisor/code-server.log +stderr_logfile=/var/log/supervisor/code-server-error.log + +# Sync Agent 后台进程(始终启动) +[program:sync-agent] +command=/opt/sync-agent/sync.sh +user=sandbox +autostart=true +autorestart=true +environment=FASTGPT_SYNC_PATH="%(ENV_FASTGPT_WORKDIR)s",HTTP_SERVER_PATH="/opt/sync-agent/http_server.py" +stdout_logfile=/var/log/supervisor/sync-agent.log +stderr_logfile=/var/log/supervisor/sync-agent-error.log diff --git a/projects/sandbox-sync-agent/sync.sh b/projects/sandbox-sync-agent/sync.sh new file mode 100644 index 0000000000..dd988c3051 --- /dev/null +++ b/projects/sandbox-sync-agent/sync.sh @@ -0,0 +1,83 @@ +#!/bin/sh + +SYNC_PATH=${FASTGPT_SYNC_PATH:-/home/sandbox} +BUCKET_PATH="minio/${FASTGPT_MINIO_BUCKET}/agent-sessions/${FASTGPT_SESSION_ID}" +SYNC_INTERVAL=${SYNC_INTERVAL:-60} +HTTP_PORT=${HTTP_PORT:-8081} +STATE_DIR="${STATE_DIR:-/tmp/sync-state}" +# K8s 模式默认 /http_server.py,Docker 模式通过 supervisord.conf 注入 /opt/sync-agent/http_server.py +HTTP_SERVER_PATH="${HTTP_SERVER_PATH:-/http_server.py}" + +mkdir -p "${STATE_DIR}" + +LAST_SYNC_FILE="${STATE_DIR}/last_sync" +PENDING_FILE="${STATE_DIR}/pending_count" +TRIGGER_FILE="${STATE_DIR}/trigger" + +# 初始化状态 +date -u +%Y-%m-%dT%H:%M:%SZ > "${LAST_SYNC_FILE}" +echo "0" > "${PENDING_FILE}" + +# 1. 启动时下载历史文件 +echo "[Sync] Downloading files from ${BUCKET_PATH}..." +mc mirror "${BUCKET_PATH}" "${SYNC_PATH}" --overwrite || true +date -u +%Y-%m-%dT%H:%M:%SZ > "${LAST_SYNC_FILE}" + +# 2. 启动 HTTP 健康检查服务(后台) +echo "[Sync] Starting HTTP server on port ${HTTP_PORT}..." +python3 "${HTTP_SERVER_PATH}" & + +# 3. 启动后台全量同步(定时 + 手动触发) +( + while true; do + sleep "${SYNC_INTERVAL}" + + # 检查手动触发 + if [ -f "${TRIGGER_FILE}" ]; then + rm -f "${TRIGGER_FILE}" + echo "[Sync] Manual sync triggered via POST /sync" + fi + + echo "[Sync] Periodic sync to MinIO..." + mc mirror "${SYNC_PATH}" "${BUCKET_PATH}" --overwrite + date -u +%Y-%m-%dT%H:%M:%SZ > "${LAST_SYNC_FILE}" + echo "0" > "${PENDING_FILE}" + done +) & + +# 4. 使用 inotify 监听实时变更(前台,保持进程存活) +echo "[Sync] Watching ${SYNC_PATH} for changes..." +inotifywait -m -r -e create,modify,move,delete --format '%w%f' "${SYNC_PATH}" | while read -r file; do + # 过滤临时文件(锚定行尾) + if echo "$file" | grep -qE '\.(tmp|swp|~)$'; then + continue + fi + + echo "[Sync] Change detected: $file" + + # 更新待同步计数 + PENDING=$(cat "${PENDING_FILE}" 2>/dev/null || echo "0") + echo $((PENDING + 1)) > "${PENDING_FILE}" + + # 计算相对路径 + rel_path="${file#${SYNC_PATH}/}" + + if [ -f "$file" ]; then + # 文件创建/修改:上传到 MinIO + mc cp "$file" "${BUCKET_PATH}/${rel_path}" + date -u +%Y-%m-%dT%H:%M:%SZ > "${LAST_SYNC_FILE}" + # 成功后将 pending 减 1 + PENDING=$(cat "${PENDING_FILE}" 2>/dev/null || echo "1") + PENDING=$((PENDING - 1)) + [ "${PENDING}" -lt 0 ] && PENDING=0 + echo "${PENDING}" > "${PENDING_FILE}" + elif [ ! -e "$file" ]; then + # 文件删除 + mc rm "${BUCKET_PATH}/${rel_path}" || true + date -u +%Y-%m-%dT%H:%M:%SZ > "${LAST_SYNC_FILE}" + PENDING=$(cat "${PENDING_FILE}" 2>/dev/null || echo "1") + PENDING=$((PENDING - 1)) + [ "${PENDING}" -lt 0 ] && PENDING=0 + echo "${PENDING}" > "${PENDING_FILE}" + fi +done