V4.14.9 dev (#6566)

* sandbox-sync-agent (#6565)

* action

* action

---------

Co-authored-by: Ryo <whoeverimf5@gmail.com>
This commit is contained in:
Archer
2026-03-16 18:11:00 +08:00
committed by GitHub
parent aaa7d17ef1
commit 7101ba5fee
28 changed files with 1078 additions and 407 deletions
@@ -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
@@ -38,24 +38,13 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-sandbox-buildx- ${{ runner.os }}-sandbox-buildx-
# login docker # login github
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} 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 }} - name: Build for ${{ matrix.arch }}
id: build id: build
-96
View File
@@ -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)
@@ -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
+72
View File
@@ -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 }}
+125
View File
@@ -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.
@@ -1,15 +1,22 @@
name: Preview FastGPT images name: FastGPT Build (Unprivileged)
on: on:
pull_request:
# 支持所有分支
types: [opened, synchronize, reopened]
pull_request_target: pull_request_target:
workflow_dispatch: # 外部贡献者需要标签批准
types: [labeled]
jobs: 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: permissions:
contents: read contents: read
packages: write
attestations: write
id-token: write
pull-requests: write pull-requests: write
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
@@ -19,21 +26,20 @@ jobs:
fail-fast: false # 即使一个镜像构建失败,也继续构建其他镜像 fail-fast: false # 即使一个镜像构建失败,也继续构建其他镜像
steps: steps:
- name: Checkout - name: Checkout PR code
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
ref: ${{ github.event.pull_request.head.ref }} # 对于 pull_request_target,检出 PR 的代码
repository: ${{ github.event.pull_request.head.repo.full_name }} ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3
with: with:
driver-opts: network=host driver-opts: network=host
- name: Cache Docker layers - name: Cache Docker layers
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: /tmp/.buildx-cache path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}-${{ matrix.image }} key: ${{ runner.os }}-buildx-${{ github.sha }}-${{ matrix.image }}
@@ -41,50 +47,54 @@ jobs:
${{ runner.os }}-buildx-${{ github.sha }}- ${{ runner.os }}-buildx-${{ github.sha }}-
${{ runner.os }}-buildx- ${{ 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 - name: Set image config
id: config id: config
run: | run: |
if [[ "${{ matrix.image }}" == "fastgpt" ]]; then if [[ "${{ matrix.image }}" == "fastgpt" ]]; then
echo "DOCKERFILE=projects/app/Dockerfile" >> $GITHUB_OUTPUT echo "DOCKERFILE=projects/app/Dockerfile" >> $GITHUB_OUTPUT
echo "DESCRIPTION=fastgpt-pr image" >> $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 elif [[ "${{ matrix.image }}" == "sandbox" ]]; then
echo "DOCKERFILE=projects/sandbox/Dockerfile" >> $GITHUB_OUTPUT echo "DOCKERFILE=projects/sandbox/Dockerfile" >> $GITHUB_OUTPUT
echo "DESCRIPTION=fastgpt-sandbox-pr image" >> $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 elif [[ "${{ matrix.image }}" == "mcp_server" ]]; then
echo "DOCKERFILE=projects/mcp_server/Dockerfile" >> $GITHUB_OUTPUT echo "DOCKERFILE=projects/mcp_server/Dockerfile" >> $GITHUB_OUTPUT
echo "DESCRIPTION=fastgpt-mcp_server-pr image" >> $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 fi
- name: Build ${{ matrix.image }} image for PR - name: Build ${{ matrix.image }} image
run: | run: |
docker buildx build \ docker buildx build \
-f ${{ steps.config.outputs.DOCKERFILE }} \ -f ${{ steps.config.outputs.DOCKERFILE }} \
--label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \ --label "org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/FastGPT" \
--label "org.opencontainers.image.description=${{ steps.config.outputs.DESCRIPTION }}" \ --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 \ --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 uses: FinleyGe/github-tools@0.0.1
id: print-image-label
if: success() if: success()
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
tool: issue-comment tool: issue-comment
title: 'Preview ${{ matrix.image }} Image:' title: 'Preview ${{ matrix.image }} Image Built:'
body: | body: |
``` Build completed. Waiting for push workflow...
${{ steps.config.outputs.DOCKER_REPO_TAGGED }}
```
+123
View File
@@ -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.
+1 -1
View File
@@ -8,7 +8,7 @@
}, },
"dependencies": { "dependencies": {
"@apidevtools/json-schema-ref-parser": "^11.7.2", "@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/storage": "catalog:",
"@fastgpt-sdk/logger": "catalog:", "@fastgpt-sdk/logger": "catalog:",
"@fastgpt/global": "workspace:*", "@fastgpt/global": "workspace:*",
+5 -5
View File
@@ -247,8 +247,8 @@ importers:
specifier: 'catalog:' specifier: 'catalog:'
version: 0.1.2 version: 0.1.2
'@fastgpt-sdk/sandbox-adapter': '@fastgpt-sdk/sandbox-adapter':
specifier: ^0.0.18 specifier: ^0.0.19
version: 0.0.18 version: 0.0.19
'@fastgpt-sdk/storage': '@fastgpt-sdk/storage':
specifier: 'catalog:' 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) 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': '@fastgpt-sdk/plugin@0.3.8':
resolution: {integrity: sha512-GjKrXMHxeF5UMkYGXawrUpzZjVRw3DICNYODeYwsUVOy+/ltu5zuwsqLkuuGQ7Arp/SBCmYRjG/MHmeNp4xxfw==} resolution: {integrity: sha512-GjKrXMHxeF5UMkYGXawrUpzZjVRw3DICNYODeYwsUVOy+/ltu5zuwsqLkuuGQ7Arp/SBCmYRjG/MHmeNp4xxfw==}
'@fastgpt-sdk/sandbox-adapter@0.0.18': '@fastgpt-sdk/sandbox-adapter@0.0.19':
resolution: {integrity: sha512-5xjBQzG9wHi7oRxAlMHz5Sz282QEHCmJnqaX4o0H0vfDgOwanBfglwejmF9bPlqy+HnIZi3rIJseCleE3MqH2g==} resolution: {integrity: sha512-024C9Ljoic7/oQm1awyLMWVl7kk9NuOGgUa8NC3wOS4GQrCVZCPCHK8YwqkRbKX9T0Akczc6RFaZj+kRJd3m4Q==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@fastgpt-sdk/storage@0.6.15': '@fastgpt-sdk/storage@0.6.15':
@@ -13433,7 +13433,7 @@ snapshots:
'@fortaine/fetch-event-source': 3.0.6 '@fortaine/fetch-event-source': 3.0.6
zod: 4.1.12 zod: 4.1.12
'@fastgpt-sdk/sandbox-adapter@0.0.18': '@fastgpt-sdk/sandbox-adapter@0.0.19':
dependencies: dependencies:
'@alibaba-group/opensandbox': 0.1.4 '@alibaba-group/opensandbox': 0.1.4
+32
View File
@@ -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"]
@@ -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"]
@@ -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"]
@@ -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}"
@@ -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
}
+227
View File
@@ -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 <prefix>] [--tag <version>] [--target base|k8s|docker|all] [--no-cache] [--platform <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."
@@ -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
+16
View File
@@ -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
@@ -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()
@@ -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 Sandboxcode-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
# SidecarSync AgentMinIO 文件同步)
- 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
@@ -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
+83
View File
@@ -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.pyDocker 模式通过 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