diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 6435e5e35e..4f9852e81a 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,7 +1,12 @@ { "permissions": { "allow": [ - "Bash(tree:*)" + "Bash(tree:*)", + "Bash(pip show:*)", + "Bash(git rev-parse:*)", + "Bash(chmod:*)", + "Bash(test:*)", + "Bash(lsof:*)" ], "deny": [], "ask": [] diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml index a8f9266e9d..f6216b10f7 100644 --- a/.github/workflows/docker-build.yaml +++ b/.github/workflows/docker-build.yaml @@ -1,11 +1,10 @@ -name: Create and publish Docker images with specific build args +name: Build and Push Docker Image (Production) on: workflow_dispatch: push: branches: - main - - dev tags: - v* @@ -13,429 +12,14 @@ env: REGISTRY: ghcr.io jobs: - build-main-image: - runs-on: ${{ matrix.runner }} - permissions: - contents: read - packages: write - strategy: - fail-fast: false - matrix: - include: - - platform: linux/amd64 - runner: ubuntu-latest - - platform: linux/arm64 - runner: ubuntu-24.04-arm - - steps: - # GitHub Packages requires the entire repository name to be in lowercase - # although the repository owner has a lowercase username, this prevents some people from running actions after forking - - name: Set repository and image name to lowercase - run: | - echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} - echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} - env: - IMAGE_NAME: '${{ github.repository }}' - - - name: Prepare - run: | - platform=${{ matrix.platform }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - - name: Checkout repository - uses: actions/checkout@v5 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker images (default latest tag) - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.FULL_IMAGE_NAME }} - tags: | - type=ref,event=branch - type=ref,event=tag - type=sha,prefix=git- - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - flavor: | - latest=${{ github.ref == 'refs/heads/main' }} - - - name: Extract metadata for Docker cache - id: cache-meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.FULL_IMAGE_NAME }} - tags: | - type=ref,event=branch - ${{ github.ref_type == 'tag' && 'type=raw,value=main' || '' }} - flavor: | - prefix=cache-${{ matrix.platform }}- - latest=false - - - name: Build Docker image (latest) - uses: docker/build-push-action@v5 - id: build - with: - context: . - push: true - platforms: ${{ matrix.platform }} - labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true - cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }} - cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max - build-args: | - BUILD_HASH=${{ github.sha }} - - - name: Export digest - run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digests-main-${{ env.PLATFORM_PAIR }} - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - - build-cuda-image: - runs-on: ${{ matrix.runner }} - permissions: - contents: read - packages: write - strategy: - fail-fast: false - matrix: - include: - - platform: linux/amd64 - runner: ubuntu-latest - - platform: linux/arm64 - runner: ubuntu-24.04-arm - - steps: - # GitHub Packages requires the entire repository name to be in lowercase - # although the repository owner has a lowercase username, this prevents some people from running actions after forking - - name: Set repository and image name to lowercase - run: | - echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} - echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} - env: - IMAGE_NAME: '${{ github.repository }}' - - - name: Prepare - run: | - platform=${{ matrix.platform }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - - name: Checkout repository - uses: actions/checkout@v5 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker images (cuda tag) - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.FULL_IMAGE_NAME }} - tags: | - type=ref,event=branch - type=ref,event=tag - type=sha,prefix=git- - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=cuda - flavor: | - latest=${{ github.ref == 'refs/heads/main' }} - suffix=-cuda,onlatest=true - - - name: Extract metadata for Docker cache - id: cache-meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.FULL_IMAGE_NAME }} - tags: | - type=ref,event=branch - ${{ github.ref_type == 'tag' && 'type=raw,value=main' || '' }} - flavor: | - prefix=cache-cuda-${{ matrix.platform }}- - latest=false - - - name: Build Docker image (cuda) - uses: docker/build-push-action@v5 - id: build - with: - context: . - push: true - platforms: ${{ matrix.platform }} - labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true - cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }} - cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max - build-args: | - BUILD_HASH=${{ github.sha }} - USE_CUDA=true - - - name: Export digest - run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digests-cuda-${{ env.PLATFORM_PAIR }} - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - - build-cuda126-image: - runs-on: ${{ matrix.runner }} - permissions: - contents: read - packages: write - strategy: - fail-fast: false - matrix: - include: - - platform: linux/amd64 - runner: ubuntu-latest - - platform: linux/arm64 - runner: ubuntu-24.04-arm - - steps: - # GitHub Packages requires the entire repository name to be in lowercase - # although the repository owner has a lowercase username, this prevents some people from running actions after forking - - name: Set repository and image name to lowercase - run: | - echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} - echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} - env: - IMAGE_NAME: '${{ github.repository }}' - - - name: Prepare - run: | - platform=${{ matrix.platform }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - - name: Checkout repository - uses: actions/checkout@v5 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker images (cuda126 tag) - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.FULL_IMAGE_NAME }} - tags: | - type=ref,event=branch - type=ref,event=tag - type=sha,prefix=git- - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=cuda126 - flavor: | - latest=${{ github.ref == 'refs/heads/main' }} - suffix=-cuda126,onlatest=true - - - name: Extract metadata for Docker cache - id: cache-meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.FULL_IMAGE_NAME }} - tags: | - type=ref,event=branch - ${{ github.ref_type == 'tag' && 'type=raw,value=main' || '' }} - flavor: | - prefix=cache-cuda126-${{ matrix.platform }}- - latest=false - - - name: Build Docker image (cuda126) - uses: docker/build-push-action@v5 - id: build - with: - context: . - push: true - platforms: ${{ matrix.platform }} - labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true - cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }} - cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max - build-args: | - BUILD_HASH=${{ github.sha }} - USE_CUDA=true - USE_CUDA_VER=cu126 - - - name: Export digest - run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digests-cuda126-${{ env.PLATFORM_PAIR }} - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - - build-ollama-image: - runs-on: ${{ matrix.runner }} - permissions: - contents: read - packages: write - strategy: - fail-fast: false - matrix: - include: - - platform: linux/amd64 - runner: ubuntu-latest - - platform: linux/arm64 - runner: ubuntu-24.04-arm - - steps: - # GitHub Packages requires the entire repository name to be in lowercase - # although the repository owner has a lowercase username, this prevents some people from running actions after forking - - name: Set repository and image name to lowercase - run: | - echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} - echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} - env: - IMAGE_NAME: '${{ github.repository }}' - - - name: Prepare - run: | - platform=${{ matrix.platform }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - - name: Checkout repository - uses: actions/checkout@v5 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker images (ollama tag) - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.FULL_IMAGE_NAME }} - tags: | - type=ref,event=branch - type=ref,event=tag - type=sha,prefix=git- - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=ollama - flavor: | - latest=${{ github.ref == 'refs/heads/main' }} - suffix=-ollama,onlatest=true - - - name: Extract metadata for Docker cache - id: cache-meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.FULL_IMAGE_NAME }} - tags: | - type=ref,event=branch - ${{ github.ref_type == 'tag' && 'type=raw,value=main' || '' }} - flavor: | - prefix=cache-ollama-${{ matrix.platform }}- - latest=false - - - name: Build Docker image (ollama) - uses: docker/build-push-action@v5 - id: build - with: - context: . - push: true - platforms: ${{ matrix.platform }} - labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true - cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }} - cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max - build-args: | - BUILD_HASH=${{ github.sha }} - USE_OLLAMA=true - - - name: Export digest - run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digests-ollama-${{ env.PLATFORM_PAIR }} - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - build-slim-image: - runs-on: ${{ matrix.runner }} + name: Build Slim Image (amd64) + runs-on: ubuntu-latest permissions: contents: read packages: write - strategy: - fail-fast: false - matrix: - include: - - platform: linux/amd64 - runner: ubuntu-latest - - platform: linux/arm64 - runner: ubuntu-24.04-arm steps: - # GitHub Packages requires the entire repository name to be in lowercase - # although the repository owner has a lowercase username, this prevents some people from running actions after forking - name: Set repository and image name to lowercase run: | echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} @@ -443,28 +27,20 @@ jobs: env: IMAGE_NAME: '${{ github.repository }}' - - name: Prepare - run: | - platform=${{ matrix.platform }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - name: Checkout repository uses: actions/checkout@v5 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to the Container registry + - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata for Docker images (slim tag) + - name: Extract metadata for Docker images id: meta uses: docker/metadata-action@v5 with: @@ -489,312 +65,37 @@ jobs: type=ref,event=branch ${{ github.ref_type == 'tag' && 'type=raw,value=main' || '' }} flavor: | - prefix=cache-slim-${{ matrix.platform }}- + prefix=cache-slim-linux-amd64- latest=false - - name: Build Docker image (slim) + - name: Build and push Docker image (slim) uses: docker/build-push-action@v5 - id: build with: context: . push: true - platforms: ${{ matrix.platform }} + platforms: linux/amd64 + tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }} cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max build-args: | BUILD_HASH=${{ github.sha }} USE_SLIM=true - - name: Export digest - run: | - mkdir -p /tmp/digests - digest="${{ steps.build.outputs.digest }}" - touch "/tmp/digests/${digest#sha256:}" - - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digests-slim-${{ env.PLATFORM_PAIR }} - path: /tmp/digests/* - if-no-files-found: error - retention-days: 1 - - merge-main-images: - runs-on: ubuntu-latest - needs: [build-main-image] - steps: - # GitHub Packages requires the entire repository name to be in lowercase - # although the repository owner has a lowercase username, this prevents some people from running actions after forking - - name: Set repository and image name to lowercase - run: | - echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} - echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} - env: - IMAGE_NAME: '${{ github.repository }}' - - - name: Download digests - uses: actions/download-artifact@v5 - with: - pattern: digests-main-* - path: /tmp/digests - merge-multiple: true - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker images (default latest tag) - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.FULL_IMAGE_NAME }} - tags: | - type=ref,event=branch - type=ref,event=tag - type=sha,prefix=git- - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - flavor: | - latest=${{ github.ref == 'refs/heads/main' }} - - - name: Create manifest list and push - working-directory: /tmp/digests - run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.FULL_IMAGE_NAME }}@sha256:%s ' *) - - name: Inspect image run: | docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }} - merge-cuda-images: - runs-on: ubuntu-latest - needs: [build-cuda-image] - steps: - # GitHub Packages requires the entire repository name to be in lowercase - # although the repository owner has a lowercase username, this prevents some people from running actions after forking - - name: Set repository and image name to lowercase + - name: Output image tags run: | - echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} - echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} - env: - IMAGE_NAME: '${{ github.repository }}' - - - name: Download digests - uses: actions/download-artifact@v5 - with: - pattern: digests-cuda-* - path: /tmp/digests - merge-multiple: true - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker images (default latest tag) - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.FULL_IMAGE_NAME }} - tags: | - type=ref,event=branch - type=ref,event=tag - type=sha,prefix=git- - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=cuda - flavor: | - latest=${{ github.ref == 'refs/heads/main' }} - suffix=-cuda,onlatest=true - - - name: Create manifest list and push - working-directory: /tmp/digests - run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.FULL_IMAGE_NAME }}@sha256:%s ' *) - - - name: Inspect image - run: | - docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }} - - merge-cuda126-images: - runs-on: ubuntu-latest - needs: [build-cuda126-image] - steps: - # GitHub Packages requires the entire repository name to be in lowercase - # although the repository owner has a lowercase username, this prevents some people from running actions after forking - - name: Set repository and image name to lowercase - run: | - echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} - echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} - env: - IMAGE_NAME: '${{ github.repository }}' - - - name: Download digests - uses: actions/download-artifact@v5 - with: - pattern: digests-cuda126-* - path: /tmp/digests - merge-multiple: true - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker images (default latest tag) - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.FULL_IMAGE_NAME }} - tags: | - type=ref,event=branch - type=ref,event=tag - type=sha,prefix=git- - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=cuda126 - flavor: | - latest=${{ github.ref == 'refs/heads/main' }} - suffix=-cuda126,onlatest=true - - - name: Create manifest list and push - working-directory: /tmp/digests - run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.FULL_IMAGE_NAME }}@sha256:%s ' *) - - - name: Inspect image - run: | - docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }} - - merge-ollama-images: - runs-on: ubuntu-latest - needs: [build-ollama-image] - steps: - # GitHub Packages requires the entire repository name to be in lowercase - # although the repository owner has a lowercase username, this prevents some people from running actions after forking - - name: Set repository and image name to lowercase - run: | - echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} - echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} - env: - IMAGE_NAME: '${{ github.repository }}' - - - name: Download digests - uses: actions/download-artifact@v5 - with: - pattern: digests-ollama-* - path: /tmp/digests - merge-multiple: true - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker images (default ollama tag) - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.FULL_IMAGE_NAME }} - tags: | - type=ref,event=branch - type=ref,event=tag - type=sha,prefix=git- - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=ollama - flavor: | - latest=${{ github.ref == 'refs/heads/main' }} - suffix=-ollama,onlatest=true - - - name: Create manifest list and push - working-directory: /tmp/digests - run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.FULL_IMAGE_NAME }}@sha256:%s ' *) - - - name: Inspect image - run: | - docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }} - - merge-slim-images: - runs-on: ubuntu-latest - needs: [build-slim-image] - steps: - # GitHub Packages requires the entire repository name to be in lowercase - # although the repository owner has a lowercase username, this prevents some people from running actions after forking - - name: Set repository and image name to lowercase - run: | - echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} - echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} - env: - IMAGE_NAME: '${{ github.repository }}' - - - name: Download digests - uses: actions/download-artifact@v5 - with: - pattern: digests-slim-* - path: /tmp/digests - merge-multiple: true - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to the Container registry - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata for Docker images (default slim tag) - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.FULL_IMAGE_NAME }} - tags: | - type=ref,event=branch - type=ref,event=tag - type=sha,prefix=git- - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=slim - flavor: | - latest=${{ github.ref == 'refs/heads/main' }} - suffix=-slim,onlatest=true - - - name: Create manifest list and push - working-directory: /tmp/digests - run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.FULL_IMAGE_NAME }}@sha256:%s ' *) - - - name: Inspect image - run: | - docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }} + echo "## 🐳 Docker 镜像构建成功" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**镜像标签:**" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**拉取命令:**" >> $GITHUB_STEP_SUMMARY + echo '```bash' >> $GITHUB_STEP_SUMMARY + echo "docker pull ${{ env.FULL_IMAGE_NAME }}:slim" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/build-release.yml b/.github/workflows_bak/build-release.yml similarity index 100% rename from .github/workflows/build-release.yml rename to .github/workflows_bak/build-release.yml diff --git a/.github/workflows/codespell.disabled b/.github/workflows_bak/codespell.disabled similarity index 100% rename from .github/workflows/codespell.disabled rename to .github/workflows_bak/codespell.disabled diff --git a/.github/workflows/deploy-to-hf-spaces.yml b/.github/workflows_bak/deploy-to-hf-spaces.yml similarity index 100% rename from .github/workflows/deploy-to-hf-spaces.yml rename to .github/workflows_bak/deploy-to-hf-spaces.yml diff --git a/.github/workflows_bak/docker-build.yaml b/.github/workflows_bak/docker-build.yaml new file mode 100644 index 0000000000..a8f9266e9d --- /dev/null +++ b/.github/workflows_bak/docker-build.yaml @@ -0,0 +1,800 @@ +name: Create and publish Docker images with specific build args + +on: + workflow_dispatch: + push: + branches: + - main + - dev + tags: + - v* + +env: + REGISTRY: ghcr.io + +jobs: + build-main-image: + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + - platform: linux/arm64 + runner: ubuntu-24.04-arm + + steps: + # GitHub Packages requires the entire repository name to be in lowercase + # although the repository owner has a lowercase username, this prevents some people from running actions after forking + - name: Set repository and image name to lowercase + run: | + echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} + echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} + env: + IMAGE_NAME: '${{ github.repository }}' + + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker images (default latest tag) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.FULL_IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=sha,prefix=git- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + flavor: | + latest=${{ github.ref == 'refs/heads/main' }} + + - name: Extract metadata for Docker cache + id: cache-meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.FULL_IMAGE_NAME }} + tags: | + type=ref,event=branch + ${{ github.ref_type == 'tag' && 'type=raw,value=main' || '' }} + flavor: | + prefix=cache-${{ matrix.platform }}- + latest=false + + - name: Build Docker image (latest) + uses: docker/build-push-action@v5 + id: build + with: + context: . + push: true + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true + cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }} + cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max + build-args: | + BUILD_HASH=${{ github.sha }} + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-main-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + build-cuda-image: + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + - platform: linux/arm64 + runner: ubuntu-24.04-arm + + steps: + # GitHub Packages requires the entire repository name to be in lowercase + # although the repository owner has a lowercase username, this prevents some people from running actions after forking + - name: Set repository and image name to lowercase + run: | + echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} + echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} + env: + IMAGE_NAME: '${{ github.repository }}' + + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker images (cuda tag) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.FULL_IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=sha,prefix=git- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=cuda + flavor: | + latest=${{ github.ref == 'refs/heads/main' }} + suffix=-cuda,onlatest=true + + - name: Extract metadata for Docker cache + id: cache-meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.FULL_IMAGE_NAME }} + tags: | + type=ref,event=branch + ${{ github.ref_type == 'tag' && 'type=raw,value=main' || '' }} + flavor: | + prefix=cache-cuda-${{ matrix.platform }}- + latest=false + + - name: Build Docker image (cuda) + uses: docker/build-push-action@v5 + id: build + with: + context: . + push: true + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true + cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }} + cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max + build-args: | + BUILD_HASH=${{ github.sha }} + USE_CUDA=true + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-cuda-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + build-cuda126-image: + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + - platform: linux/arm64 + runner: ubuntu-24.04-arm + + steps: + # GitHub Packages requires the entire repository name to be in lowercase + # although the repository owner has a lowercase username, this prevents some people from running actions after forking + - name: Set repository and image name to lowercase + run: | + echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} + echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} + env: + IMAGE_NAME: '${{ github.repository }}' + + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker images (cuda126 tag) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.FULL_IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=sha,prefix=git- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=cuda126 + flavor: | + latest=${{ github.ref == 'refs/heads/main' }} + suffix=-cuda126,onlatest=true + + - name: Extract metadata for Docker cache + id: cache-meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.FULL_IMAGE_NAME }} + tags: | + type=ref,event=branch + ${{ github.ref_type == 'tag' && 'type=raw,value=main' || '' }} + flavor: | + prefix=cache-cuda126-${{ matrix.platform }}- + latest=false + + - name: Build Docker image (cuda126) + uses: docker/build-push-action@v5 + id: build + with: + context: . + push: true + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true + cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }} + cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max + build-args: | + BUILD_HASH=${{ github.sha }} + USE_CUDA=true + USE_CUDA_VER=cu126 + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-cuda126-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + build-ollama-image: + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + - platform: linux/arm64 + runner: ubuntu-24.04-arm + + steps: + # GitHub Packages requires the entire repository name to be in lowercase + # although the repository owner has a lowercase username, this prevents some people from running actions after forking + - name: Set repository and image name to lowercase + run: | + echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} + echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} + env: + IMAGE_NAME: '${{ github.repository }}' + + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker images (ollama tag) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.FULL_IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=sha,prefix=git- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=ollama + flavor: | + latest=${{ github.ref == 'refs/heads/main' }} + suffix=-ollama,onlatest=true + + - name: Extract metadata for Docker cache + id: cache-meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.FULL_IMAGE_NAME }} + tags: | + type=ref,event=branch + ${{ github.ref_type == 'tag' && 'type=raw,value=main' || '' }} + flavor: | + prefix=cache-ollama-${{ matrix.platform }}- + latest=false + + - name: Build Docker image (ollama) + uses: docker/build-push-action@v5 + id: build + with: + context: . + push: true + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true + cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }} + cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max + build-args: | + BUILD_HASH=${{ github.sha }} + USE_OLLAMA=true + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-ollama-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + build-slim-image: + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + - platform: linux/arm64 + runner: ubuntu-24.04-arm + + steps: + # GitHub Packages requires the entire repository name to be in lowercase + # although the repository owner has a lowercase username, this prevents some people from running actions after forking + - name: Set repository and image name to lowercase + run: | + echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} + echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} + env: + IMAGE_NAME: '${{ github.repository }}' + + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker images (slim tag) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.FULL_IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=sha,prefix=git- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=slim + flavor: | + latest=${{ github.ref == 'refs/heads/main' }} + suffix=-slim,onlatest=true + + - name: Extract metadata for Docker cache + id: cache-meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.FULL_IMAGE_NAME }} + tags: | + type=ref,event=branch + ${{ github.ref_type == 'tag' && 'type=raw,value=main' || '' }} + flavor: | + prefix=cache-slim-${{ matrix.platform }}- + latest=false + + - name: Build Docker image (slim) + uses: docker/build-push-action@v5 + id: build + with: + context: . + push: true + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=${{ env.FULL_IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true + cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }} + cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max + build-args: | + BUILD_HASH=${{ github.sha }} + USE_SLIM=true + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-slim-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge-main-images: + runs-on: ubuntu-latest + needs: [build-main-image] + steps: + # GitHub Packages requires the entire repository name to be in lowercase + # although the repository owner has a lowercase username, this prevents some people from running actions after forking + - name: Set repository and image name to lowercase + run: | + echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} + echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} + env: + IMAGE_NAME: '${{ github.repository }}' + + - name: Download digests + uses: actions/download-artifact@v5 + with: + pattern: digests-main-* + path: /tmp/digests + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker images (default latest tag) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.FULL_IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=sha,prefix=git- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + flavor: | + latest=${{ github.ref == 'refs/heads/main' }} + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.FULL_IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }} + + merge-cuda-images: + runs-on: ubuntu-latest + needs: [build-cuda-image] + steps: + # GitHub Packages requires the entire repository name to be in lowercase + # although the repository owner has a lowercase username, this prevents some people from running actions after forking + - name: Set repository and image name to lowercase + run: | + echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} + echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} + env: + IMAGE_NAME: '${{ github.repository }}' + + - name: Download digests + uses: actions/download-artifact@v5 + with: + pattern: digests-cuda-* + path: /tmp/digests + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker images (default latest tag) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.FULL_IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=sha,prefix=git- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=cuda + flavor: | + latest=${{ github.ref == 'refs/heads/main' }} + suffix=-cuda,onlatest=true + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.FULL_IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }} + + merge-cuda126-images: + runs-on: ubuntu-latest + needs: [build-cuda126-image] + steps: + # GitHub Packages requires the entire repository name to be in lowercase + # although the repository owner has a lowercase username, this prevents some people from running actions after forking + - name: Set repository and image name to lowercase + run: | + echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} + echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} + env: + IMAGE_NAME: '${{ github.repository }}' + + - name: Download digests + uses: actions/download-artifact@v5 + with: + pattern: digests-cuda126-* + path: /tmp/digests + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker images (default latest tag) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.FULL_IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=sha,prefix=git- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=cuda126 + flavor: | + latest=${{ github.ref == 'refs/heads/main' }} + suffix=-cuda126,onlatest=true + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.FULL_IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }} + + merge-ollama-images: + runs-on: ubuntu-latest + needs: [build-ollama-image] + steps: + # GitHub Packages requires the entire repository name to be in lowercase + # although the repository owner has a lowercase username, this prevents some people from running actions after forking + - name: Set repository and image name to lowercase + run: | + echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} + echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} + env: + IMAGE_NAME: '${{ github.repository }}' + + - name: Download digests + uses: actions/download-artifact@v5 + with: + pattern: digests-ollama-* + path: /tmp/digests + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker images (default ollama tag) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.FULL_IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=sha,prefix=git- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=ollama + flavor: | + latest=${{ github.ref == 'refs/heads/main' }} + suffix=-ollama,onlatest=true + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.FULL_IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }} + + merge-slim-images: + runs-on: ubuntu-latest + needs: [build-slim-image] + steps: + # GitHub Packages requires the entire repository name to be in lowercase + # although the repository owner has a lowercase username, this prevents some people from running actions after forking + - name: Set repository and image name to lowercase + run: | + echo "IMAGE_NAME=${IMAGE_NAME,,}" >>${GITHUB_ENV} + echo "FULL_IMAGE_NAME=ghcr.io/${IMAGE_NAME,,}" >>${GITHUB_ENV} + env: + IMAGE_NAME: '${{ github.repository }}' + + - name: Download digests + uses: actions/download-artifact@v5 + with: + pattern: digests-slim-* + path: /tmp/digests + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker images (default slim tag) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.FULL_IMAGE_NAME }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=sha,prefix=git- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,enable=${{ github.ref == 'refs/heads/main' }},prefix=,suffix=,value=slim + flavor: | + latest=${{ github.ref == 'refs/heads/main' }} + suffix=-slim,onlatest=true + + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.FULL_IMAGE_NAME }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.FULL_IMAGE_NAME }}:${{ steps.meta.outputs.version }} diff --git a/.github/workflows/format-backend.yaml b/.github/workflows_bak/format-backend.yaml similarity index 100% rename from .github/workflows/format-backend.yaml rename to .github/workflows_bak/format-backend.yaml diff --git a/.github/workflows/format-build-frontend.yaml b/.github/workflows_bak/format-build-frontend.yaml similarity index 100% rename from .github/workflows/format-build-frontend.yaml rename to .github/workflows_bak/format-build-frontend.yaml diff --git a/.github/workflows/integration-test.disabled b/.github/workflows_bak/integration-test.disabled similarity index 100% rename from .github/workflows/integration-test.disabled rename to .github/workflows_bak/integration-test.disabled diff --git a/.github/workflows/lint-backend.disabled b/.github/workflows_bak/lint-backend.disabled similarity index 100% rename from .github/workflows/lint-backend.disabled rename to .github/workflows_bak/lint-backend.disabled diff --git a/.github/workflows/lint-frontend.disabled b/.github/workflows_bak/lint-frontend.disabled similarity index 100% rename from .github/workflows/lint-frontend.disabled rename to .github/workflows_bak/lint-frontend.disabled diff --git a/.github/workflows/release-pypi.yml b/.github/workflows_bak/release-pypi.yml similarity index 100% rename from .github/workflows/release-pypi.yml rename to .github/workflows_bak/release-pypi.yml diff --git a/.gitignore b/.gitignore index 0d048b2624..15c359e794 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +data # PyInstaller # Usually these files are written by a python script from a template diff --git a/Dockerfile b/Dockerfile index ad393338d8..7555433aaf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ FROM --platform=$BUILDPLATFORM node:22-alpine3.20 AS build ARG BUILD_HASH # Set Node.js options (heap limit Allocation failed - JavaScript heap out of memory) -# ENV NODE_OPTIONS="--max-old-space-size=4096" +ENV NODE_OPTIONS="--max-old-space-size=4096" WORKDIR /app diff --git a/docs/DOCKER-DEPLOYMENT.md b/docs/DOCKER-DEPLOYMENT.md new file mode 100644 index 0000000000..b63b408476 --- /dev/null +++ b/docs/DOCKER-DEPLOYMENT.md @@ -0,0 +1,1088 @@ +# Open WebUI Docker 生产环境部署指南 + +本指南介绍如何在生产环境中部署 Open WebUI Docker 镜像,并连接到外部 MySQL 数据库。 + +## 目录 + +- [镜像说明](#镜像说明) +- [镜像获取方式](#镜像获取方式) +- [前置准备](#前置准备) +- [快速开始](#快速开始) +- [MySQL 数据库配置](#mysql-数据库配置) +- [环境变量完整列表](#环境变量完整列表) +- [高级配置](#高级配置) +- [故障排查](#故障排查) + +--- + +## 镜像说明 + +### 镜像仓库 +``` +ghcr.io//open-webui-next:slim +``` + +### 镜像特性 +- **架构**: linux/amd64 (x86_64) +- **变体**: Slim 精简版(不预装 AI 模型,首次运行时自动下载) +- **包含**: 前端 (SvelteKit) + 后端 (FastAPI) +- **不包含**: 数据库服务(需要外部 MySQL) + +### 镜像标签规则 + +| 标签 | 说明 | 示例 | +|------|------|------| +| `slim` | 主分支最新精简版 | `ghcr.io/user/repo:slim` | +| `main-slim` | 主分支最新构建 | `ghcr.io/user/repo:main-slim` | +| `v1.2.3-slim` | 版本标签 | `ghcr.io/user/repo:v1.2.3-slim` | +| `git-abc1234-slim` | Git commit SHA | `ghcr.io/user/repo:git-abc1234-slim` | + +--- + +## 镜像获取方式 + +### 从 GitHub Container Registry (GHCR) 拉取 + +#### 方式一:拉取公开镜像(推荐) + +如果仓库是公开的,直接拉取即可: + +```bash +# 拉取最新 slim 版本 +docker pull ghcr.io//open-webui-next:slim + +# 拉取特定版本标签 +docker pull ghcr.io//open-webui-next:v1.2.3-slim + +# 拉取特定 Git commit +docker pull ghcr.io//open-webui-next:git-abc1234-slim +``` + +验证镜像拉取成功: + +```bash +# 查看本地镜像列表 +docker images | grep open-webui-next + +# 查看镜像详细信息 +docker inspect ghcr.io//open-webui-next:slim +``` + +#### 方式二:拉取私有镜像(需要认证) + +如果仓库是私有的,需要先登录 GHCR: + +**步骤 1: 创建 GitHub Personal Access Token (PAT)** + +1. 访问 GitHub Settings → Developer settings → Personal access tokens → Tokens (classic) +2. 点击 "Generate new token (classic)" +3. 设置以下权限: + - `read:packages` - 读取容器镜像 + - `write:packages` - (可选) 推送镜像 +4. 生成并保存 Token (只显示一次) + +**步骤 2: 登录 GHCR** + +```bash +# 使用 PAT 登录 +echo "YOUR_PERSONAL_ACCESS_TOKEN" | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdin + +# 或交互式输入密码 +docker login ghcr.io -u YOUR_GITHUB_USERNAME +# Password: [输入 PAT] +``` + +登录成功后会显示: +``` +Login Succeeded +``` + +**步骤 3: 拉取镜像** + +```bash +docker pull ghcr.io//open-webui-next:slim +``` + +**步骤 4: 退出登录(可选)** + +```bash +docker logout ghcr.io +``` + +### 生产服务器部署流程 + +#### 场景一:单服务器部署 + +```bash +# 1. SSH 登录生产服务器 +ssh user@production-server + +# 2. 登录 GHCR (如果是私有仓库) +echo "YOUR_PAT" | docker login ghcr.io -u YOUR_USERNAME --password-stdin + +# 3. 拉取最新镜像 +docker pull ghcr.io//open-webui-next:slim + +# 4. 停止旧容器 (如果存在) +docker stop open-webui || true +docker rm open-webui || true + +# 5. 启动新容器 +docker run -d \ + --name open-webui \ + --restart unless-stopped \ + -p 8080:8080 \ + -e DATABASE_URL="mysql://..." \ + -v /data/open-webui/data:/app/backend/data \ + ghcr.io//open-webui-next:slim + +# 6. 验证部署 +docker ps | grep open-webui +curl -f http://localhost:8080/health || echo "Health check failed" +``` + +#### 场景二:CI/CD 自动化部署 + +**GitHub Actions 自动部署示例**: + +```yaml +# .github/workflows/deploy.yml +name: Deploy to Production + +on: + workflow_run: + workflows: ["Build and Push Docker Image (Production)"] + types: + - completed + +jobs: + deploy: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} + + steps: + - name: Deploy to server + uses: appleboy/ssh-action@v1.0.0 + with: + host: ${{ secrets.PROD_SERVER_HOST }} + username: ${{ secrets.PROD_SERVER_USER }} + key: ${{ secrets.PROD_SERVER_SSH_KEY }} + script: | + # 拉取最新镜像 + docker pull ghcr.io/${{ github.repository }}:slim + + # 滚动更新 + docker stop open-webui || true + docker rm open-webui || true + + docker run -d \ + --name open-webui \ + --restart unless-stopped \ + -p 8080:8080 \ + --env-file /opt/openwebui/.env \ + -v /data/open-webui/data:/app/backend/data \ + ghcr.io/${{ github.repository }}:slim + + # 健康检查 + sleep 10 + docker ps | grep open-webui + curl -f http://localhost:8080/health +``` + +#### 场景三:多服务器部署(使用镜像同步) + +如果有多台服务器,可以先拉取到一台,然后导出/导入: + +```bash +# 服务器 A: 从 GHCR 拉取 +docker pull ghcr.io//open-webui-next:slim + +# 导出镜像到文件 +docker save ghcr.io//open-webui-next:slim | gzip > open-webui-slim.tar.gz + +# 传输到其他服务器 +scp open-webui-slim.tar.gz user@server-b:/tmp/ +scp open-webui-slim.tar.gz user@server-c:/tmp/ + +# 服务器 B/C: 导入镜像 +gunzip -c /tmp/open-webui-slim.tar.gz | docker load +``` + +### 镜像更新策略 + +#### 自动更新(使用 Watchtower) + +```bash +# 部署 Watchtower 容器监控镜像更新 +docker run -d \ + --name watchtower \ + --restart unless-stopped \ + -v /var/run/docker.sock:/var/run/docker.sock \ + containrrr/watchtower \ + --interval 3600 \ + --cleanup \ + open-webui +``` + +Watchtower 会每小时检查一次 `open-webui` 容器的镜像更新,如果有新版本会自动拉取并重启容器。 + +#### 手动更新(推荐生产环境) + +```bash +# 1. 拉取最新镜像 +docker pull ghcr.io//open-webui-next:slim + +# 2. 检查镜像是否有更新 +OLD_ID=$(docker inspect --format='{{.Image}}' open-webui) +NEW_ID=$(docker inspect --format='{{.Id}}' ghcr.io//open-webui-next:slim) + +if [ "$OLD_ID" != "$NEW_ID" ]; then + echo "New image available, updating..." + + # 3. 备份当前容器配置 + docker inspect open-webui > /backup/open-webui-config-$(date +%Y%m%d).json + + # 4. 停止并删除旧容器 + docker stop open-webui + docker rm open-webui + + # 5. 启动新容器 + docker run -d \ + --name open-webui \ + --restart unless-stopped \ + -p 8080:8080 \ + --env-file /opt/openwebui/.env \ + -v /data/open-webui/data:/app/backend/data \ + ghcr.io//open-webui-next:slim + + # 6. 清理旧镜像 + docker image prune -f +else + echo "Already up to date" +fi +``` + +### 镜像访问故障排查 + +#### 问题 1: 拉取失败 - 认证错误 + +``` +Error response from daemon: unauthorized: authentication required +``` + +**解决方案**: +- 检查是否已登录: `docker info | grep Username` +- 重新登录: `docker login ghcr.io -u YOUR_USERNAME` +- 确认 PAT 有 `read:packages` 权限 +- 确认仓库可见性(公开/私有) + +#### 问题 2: 拉取失败 - 网络超时 + +``` +Error response from daemon: Get https://ghcr.io/v2/: dial tcp: i/o timeout +``` + +**解决方案**: +```bash +# 检查 DNS 解析 +nslookup ghcr.io + +# 检查网络连接 +ping ghcr.io +curl -I https://ghcr.io + +# 配置 Docker 镜像代理(如果在国内服务器) +# /etc/docker/daemon.json +{ + "registry-mirrors": [ + "https://mirror.gcr.io" + ] +} + +# 重启 Docker +sudo systemctl restart docker +``` + +#### 问题 3: 拉取失败 - 镜像不存在 + +``` +Error response from daemon: manifest for ghcr.io/user/repo:slim not found +``` + +**解决方案**: +- 确认镜像名称和标签拼写正确 +- 检查 GitHub Actions 构建是否成功 +- 查看仓库 Packages 页面确认镜像已发布 +- 使用 `docker search ghcr.io/` 搜索可用镜像 + +#### 问题 4: 权限不足 + +``` +Error response from daemon: pull access denied for ghcr.io/user/repo +``` + +**解决方案**: +- 确认 GitHub 用户有仓库访问权限 +- 如果是组织仓库,确认用户在组织中 +- 管理员在仓库 Settings → Actions → General → Workflow permissions 中启用 "Read and write permissions" + +--- + +## 前置准备 + +### 1. MySQL 数据库准备 + +在您的 MySQL 服务器上创建数据库和用户: + +```sql +-- 创建数据库 +CREATE DATABASE openwebui CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- 创建用户并授权(替换密码) +CREATE USER 'openwebui'@'%' IDENTIFIED BY 'your_secure_password'; +GRANT ALL PRIVILEGES ON openwebui.* TO 'openwebui'@'%'; +FLUSH PRIVILEGES; + +-- 验证创建 +SHOW DATABASES LIKE 'openwebui'; +SELECT User, Host FROM mysql.user WHERE User = 'openwebui'; +``` + +**安全建议**: +- 生产环境使用强密码(至少 16 位,包含大小写字母、数字、特殊字符) +- 如果 MySQL 和 Docker 在同一网络,限制 `Host` 为具体 IP 而非 `%` +- 启用 MySQL SSL 连接(见高级配置) + +### 2. 网络连通性检查 + +确保 Docker 容器可以访问 MySQL 服务器: + +```bash +# 在 Docker 宿主机测试 MySQL 连接 +mysql -h -P 3306 -u openwebui -p + +# 或使用 telnet 测试端口 +telnet 3306 +``` + +### 3. 数据持久化目录 + +创建用于挂载的数据目录: + +```bash +mkdir -p /data/open-webui/data +chmod 755 /data/open-webui/data +``` + +--- + +## 快速开始 + +### 方式一:使用环境变量(推荐) + +```bash +docker run -d \ + --name open-webui \ + --restart unless-stopped \ + -p 8080:8080 \ + -e DATABASE_URL="mysql://openwebui:your_secure_password@mysql.example.com:3306/openwebui" \ + -v /data/open-webui/data:/app/backend/data \ + ghcr.io//open-webui-next:slim +``` + +### 方式二:使用分离的数据库环境变量 + +```bash +docker run -d \ + --name open-webui \ + --restart unless-stopped \ + -p 8080:8080 \ + -e DATABASE_TYPE="mysql" \ + -e DATABASE_HOST="mysql.example.com" \ + -e DATABASE_PORT="3306" \ + -e DATABASE_NAME="openwebui" \ + -e DATABASE_USER="openwebui" \ + -e DATABASE_PASSWORD="your_secure_password" \ + -v /data/open-webui/data:/app/backend/data \ + ghcr.io//open-webui-next:slim +``` + +### 方式三:使用 .env 文件(推荐生产环境) + +创建 `.env` 文件: + +```bash +# .env +DATABASE_URL=mysql://openwebui:your_secure_password@mysql.example.com:3306/openwebui +WEBUI_NAME=Open WebUI Production +WEBUI_SECRET_KEY=your_random_secret_key_here +``` + +运行容器: + +```bash +docker run -d \ + --name open-webui \ + --restart unless-stopped \ + -p 8080:8080 \ + --env-file .env \ + -v /data/open-webui/data:/app/backend/data \ + ghcr.io//open-webui-next:slim +``` + +--- + +## MySQL 数据库配置 + +### 数据库连接方式 + +#### 选项 1: 使用完整 DATABASE_URL(推荐) + +```bash +# 标准 MySQL 连接 +DATABASE_URL=mysql://username:password@host:port/database + +# 带参数的连接(UTF-8 编码) +DATABASE_URL=mysql://username:password@host:port/database?charset=utf8mb4 + +# MySQL 8.0+ 使用 PyMySQL 驱动 +DATABASE_URL=mysql+pymysql://username:password@host:port/database + +# SSL 连接 +DATABASE_URL=mysql://username:password@host:port/database?ssl=true +``` + +#### 选项 2: 使用分离的环境变量 + +```bash +DATABASE_TYPE=mysql # 数据库类型 +DATABASE_HOST=mysql.example.com # 主机地址 +DATABASE_PORT=3306 # 端口 +DATABASE_NAME=openwebui # 数据库名 +DATABASE_USER=openwebui # 用户名 +DATABASE_PASSWORD=password # 密码 +``` + +### 支持的数据库类型 + +| 数据库 | DATABASE_TYPE | 驱动 | 示例 URL | +|--------|---------------|------|----------| +| MySQL 5.7+ | `mysql` | mysqlclient | `mysql://user:pass@host/db` | +| MySQL 8.0+ | `mysql+pymysql` | pymysql | `mysql+pymysql://user:pass@host/db` | +| PostgreSQL | `postgresql` | psycopg2 | `postgresql://user:pass@host/db` | +| SQLite | `sqlite` | 内置 | `sqlite:///path/to/db.db` | + +### 数据库性能优化 + +#### 连接池配置 + +```bash +# 连接池大小(默认:10) +DATABASE_POOL_SIZE=20 + +# 最大溢出连接数(默认:0) +DATABASE_POOL_MAX_OVERFLOW=10 + +# 连接回收时间(秒,防止断线) +DATABASE_POOL_RECYCLE=3600 + +# 连接超时时间(秒) +DATABASE_CONNECT_TIMEOUT=10 +``` + +#### MySQL 特定优化 + +在 MySQL 服务器配置文件 `my.cnf` 中: + +```ini +[mysqld] +# 连接数限制 +max_connections = 500 + +# InnoDB 缓冲池大小(推荐物理内存的 50-70%) +innodb_buffer_pool_size = 2G + +# 字符集 +character-set-server = utf8mb4 +collation-server = utf8mb4_unicode_ci + +# 二进制日志(用于备份和恢复) +log_bin = /var/log/mysql/mysql-bin.log +expire_logs_days = 7 +``` + +--- + +## 环境变量完整列表 + +### 数据库配置 + +| 环境变量 | 说明 | 默认值 | 示例 | +|---------|------|--------|------| +| `DATABASE_URL` | 完整数据库连接 URL | `sqlite:///{DATA_DIR}/webui.db` | `mysql://user:pass@host/db` | +| `DATABASE_TYPE` | 数据库类型 | - | `mysql`, `postgresql` | +| `DATABASE_HOST` | 数据库主机 | - | `mysql.example.com` | +| `DATABASE_PORT` | 数据库端口 | - | `3306` | +| `DATABASE_NAME` | 数据库名称 | - | `openwebui` | +| `DATABASE_USER` | 数据库用户名 | - | `openwebui` | +| `DATABASE_PASSWORD` | 数据库密码 | - | `password` | +| `DATABASE_SCHEMA` | 数据库 Schema | - | `public` | +| `DATABASE_POOL_SIZE` | 连接池大小 | `10` | `20` | +| `DATABASE_POOL_MAX_OVERFLOW` | 最大溢出连接 | `0` | `10` | + +### 应用基础配置 + +| 环境变量 | 说明 | 默认值 | 示例 | +|---------|------|--------|------| +| `WEBUI_NAME` | 应用名称 | `Open WebUI` | `My AI Platform` | +| `WEBUI_URL` | 外部访问 URL | `http://localhost:8080` | `https://ai.example.com` | +| `WEBUI_SECRET_KEY` | JWT 密钥(必须设置) | 自动生成 | 随机字符串 | +| `PORT` | 服务端口 | `8080` | `8080` | +| `HOST` | 监听地址 | `0.0.0.0` | `0.0.0.0` | +| `DATA_DIR` | 数据目录 | `/app/backend/data` | - | + +### 认证与安全 + +| 环境变量 | 说明 | 默认值 | 示例 | +|---------|------|--------|------| +| `WEBUI_AUTH` | 启用认证 | `true` | `true`, `false` | +| `WEBUI_AUTH_TRUSTED_EMAIL_HEADER` | 信任的邮箱 Header | - | `X-User-Email` | +| `DEFAULT_USER_ROLE` | 默认用户角色 | `pending` | `user`, `admin` | +| `ENABLE_SIGNUP` | 允许注册 | `true` | `true`, `false` | +| `JWT_EXPIRES_IN` | JWT 过期时间 | `30d` | `7d`, `24h` | + +### LLM 提供商配置 + +| 环境变量 | 说明 | 默认值 | +|---------|------|--------| +| `OPENAI_API_BASE_URL` | OpenAI API 基础 URL | `https://api.openai.com/v1` | +| `OPENAI_API_KEY` | OpenAI API 密钥 | - | +| `OLLAMA_BASE_URL` | Ollama 服务地址 | `http://localhost:11434` | +| `ANTHROPIC_API_KEY` | Claude API 密钥 | - | +| `GOOGLE_API_KEY` | Gemini API 密钥 | - | + +### RAG(向量数据库)配置 + +| 环境变量 | 说明 | 默认值 | +|---------|------|--------| +| `VECTOR_DB` | 向量数据库类型 | `chroma` | +| `CHROMA_DATA_PATH` | ChromaDB 数据路径 | `{DATA_DIR}/vector_db` | +| `QDRANT_URI` | Qdrant 连接地址 | - | +| `OPENSEARCH_URI` | OpenSearch 连接地址 | - | + +### 日志配置 + +| 环境变量 | 说明 | 默认值 | +|---------|------|--------| +| `GLOBAL_LOG_LEVEL` | 全局日志级别 | `INFO` | +| `LOG_LEVEL` | 应用日志级别 | `INFO` | +| `UVICORN_LOG_LEVEL` | Uvicorn 日志级别 | `info` | + +--- + +## 高级配置 + +### 1. 使用 Docker Compose(推荐) + +创建 `docker-compose.yml`: + +```yaml +version: '3.8' + +services: + open-webui: + image: ghcr.io//open-webui-next:slim + container_name: open-webui + restart: unless-stopped + ports: + - "8080:8080" + environment: + # 数据库配置 + DATABASE_URL: mysql://openwebui:${DB_PASSWORD}@mysql.example.com:3306/openwebui + + # 应用配置 + WEBUI_NAME: "Open WebUI Production" + WEBUI_SECRET_KEY: ${WEBUI_SECRET_KEY} + WEBUI_URL: https://ai.example.com + + # 认证配置 + ENABLE_SIGNUP: "false" + DEFAULT_USER_ROLE: "pending" + + # LLM 配置 + OPENAI_API_KEY: ${OPENAI_API_KEY} + OLLAMA_BASE_URL: http://ollama:11434 + + # 日志配置 + GLOBAL_LOG_LEVEL: INFO + + volumes: + - /data/open-webui/data:/app/backend/data + + # 健康检查 + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8080/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + # 资源限制 + deploy: + resources: + limits: + cpus: '2' + memory: 4G + reservations: + cpus: '1' + memory: 2G + + # 可选:如果需要在同一网络部署 Ollama + ollama: + image: ollama/ollama:latest + container_name: ollama + restart: unless-stopped + volumes: + - /data/ollama:/root/.ollama + deploy: + resources: + limits: + memory: 8G +``` + +创建 `.env` 文件(不要提交到 Git): + +```bash +# .env +DB_PASSWORD=your_secure_mysql_password +WEBUI_SECRET_KEY=your_random_secret_key_here +OPENAI_API_KEY=sk-your-openai-key +``` + +启动服务: + +```bash +docker-compose up -d +``` + +### 2. MySQL SSL 连接 + +如果 MySQL 启用了 SSL,配置连接: + +```bash +# 方式 1: 在 DATABASE_URL 中指定 +DATABASE_URL="mysql://user:pass@host/db?ssl=true&ssl_ca=/path/to/ca-cert.pem" + +# 方式 2: 使用环境变量 +DATABASE_SSL=true +DATABASE_SSL_CA=/path/to/ca-cert.pem +DATABASE_SSL_CERT=/path/to/client-cert.pem +DATABASE_SSL_KEY=/path/to/client-key.pem +``` + +挂载证书到容器: + +```bash +docker run -d \ + -v /path/to/certs:/certs:ro \ + -e DATABASE_URL="mysql://user:pass@host/db?ssl_ca=/certs/ca-cert.pem" \ + ghcr.io//open-webui-next:slim +``` + +### 3. 反向代理(Nginx) + +生产环境推荐在 Docker 前配置 Nginx 反向代理: + +```nginx +# /etc/nginx/sites-available/openwebui +upstream open_webui { + server 127.0.0.1:8080; +} + +server { + listen 80; + server_name ai.example.com; + + # HTTPS 重定向 + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl http2; + server_name ai.example.com; + + # SSL 证书 + ssl_certificate /etc/nginx/ssl/cert.pem; + ssl_certificate_key /etc/nginx/ssl/key.pem; + + # SSL 配置 + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + + # 日志 + access_log /var/log/nginx/openwebui_access.log; + error_log /var/log/nginx/openwebui_error.log; + + # 客户端最大请求体大小(上传文件) + client_max_body_size 100M; + + location / { + proxy_pass http://open_webui; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket 支持(用于实时聊天) + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # 超时配置 + proxy_connect_timeout 60s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + } +} +``` + +启用配置: + +```bash +sudo ln -s /etc/nginx/sites-available/openwebui /etc/nginx/sites-enabled/ +sudo nginx -t +sudo systemctl reload nginx +``` + +### 4. 数据备份策略 + +#### MySQL 数据库备份 + +```bash +#!/bin/bash +# backup-mysql.sh + +BACKUP_DIR="/backup/mysql" +DATE=$(date +%Y%m%d_%H%M%S) +BACKUP_FILE="$BACKUP_DIR/openwebui_$DATE.sql.gz" + +# 创建备份目录 +mkdir -p $BACKUP_DIR + +# 备份数据库 +mysqldump -h mysql.example.com -u openwebui -p openwebui | gzip > $BACKUP_FILE + +# 删除 7 天前的备份 +find $BACKUP_DIR -name "openwebui_*.sql.gz" -mtime +7 -delete + +echo "Backup completed: $BACKUP_FILE" +``` + +设置 cron 定时任务: + +```bash +# 每天凌晨 2 点备份 +0 2 * * * /path/to/backup-mysql.sh +``` + +#### 数据目录备份 + +```bash +#!/bin/bash +# backup-data.sh + +BACKUP_DIR="/backup/openwebui" +DATE=$(date +%Y%m%d_%H%M%S) +DATA_DIR="/data/open-webui/data" + +mkdir -p $BACKUP_DIR + +# 备份数据目录(包含上传的文件、模型等) +tar -czf "$BACKUP_DIR/data_$DATE.tar.gz" -C "$DATA_DIR" . + +# 删除 30 天前的备份 +find $BACKUP_DIR -name "data_*.tar.gz" -mtime +30 -delete + +echo "Data backup completed: $BACKUP_DIR/data_$DATE.tar.gz" +``` + +### 5. 监控与告警 + +#### 健康检查脚本 + +```bash +#!/bin/bash +# healthcheck.sh + +HEALTH_URL="http://localhost:8080/health" +MAX_RETRIES=3 +RETRY_COUNT=0 + +while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" $HEALTH_URL) + + if [ $HTTP_CODE -eq 200 ]; then + echo "Health check passed" + exit 0 + fi + + RETRY_COUNT=$((RETRY_COUNT + 1)) + sleep 5 +done + +echo "Health check failed after $MAX_RETRIES retries" +exit 1 +``` + +#### Docker 健康检查 + +在 `docker-compose.yml` 中已配置健康检查: + +```bash +# 查看健康状态 +docker ps | grep open-webui + +# 查看健康检查日志 +docker inspect --format='{{json .State.Health}}' open-webui | jq +``` + +--- + +## 故障排查 + +### 1. 数据库连接失败 + +**症状**: 容器启动后立即退出,日志显示数据库连接错误 + +**排查步骤**: + +```bash +# 查看容器日志 +docker logs open-webui + +# 进入容器测试数据库连接 +docker exec -it open-webui bash +apt-get update && apt-get install -y mysql-client +mysql -h mysql.example.com -u openwebui -p +``` + +**常见问题**: +- ✅ 确认 MySQL 用户权限:`GRANT ALL PRIVILEGES ON openwebui.* TO 'openwebui'@'%'` +- ✅ 确认防火墙规则:允许 Docker 容器 IP 访问 MySQL 3306 端口 +- ✅ 确认 `DATABASE_URL` 格式正确 +- ✅ 如果使用 MySQL 8.0,尝试 `mysql+pymysql://` 驱动 + +### 2. 模型下载失败(Slim 镜像首次运行) + +**症状**: 应用启动缓慢,日志显示模型下载错误 + +**解决方案**: + +```bash +# 方式 1: 预先下载模型到挂载目录 +# 在宿主机上执行 +mkdir -p /data/open-webui/data/cache +cd /data/open-webui/data/cache + +# 下载 sentence-transformers 模型 +wget https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/pytorch_model.bin + +# 方式 2: 设置 HuggingFace 镜像 +docker run -d \ + -e HF_ENDPOINT=https://hf-mirror.com \ + ... +``` + +### 3. 容器内存不足 + +**症状**: 容器频繁重启,日志显示 OOM (Out of Memory) + +**解决方案**: + +```bash +# 限制容器内存使用 +docker run -d \ + --memory="4g" \ + --memory-swap="4g" \ + ... + +# 或在 docker-compose.yml 中配置资源限制(见高级配置) +``` + +### 4. 文件上传失败 + +**症状**: 上传大文件时出现 413 错误 + +**解决方案**: + +```bash +# 如果使用 Nginx 反向代理,增加上传大小限制 +# /etc/nginx/nginx.conf +http { + client_max_body_size 100M; +} + +# 重启 Nginx +sudo systemctl reload nginx +``` + +### 5. WebSocket 连接断开 + +**症状**: 实时聊天功能不工作,日志显示 WebSocket 错误 + +**解决方案**: + +检查 Nginx 配置是否支持 WebSocket: + +```nginx +location / { + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + ... +} +``` + +### 6. 数据库迁移错误 + +**症状**: 从 SQLite 迁移到 MySQL 后数据丢失 + +**解决方案**: + +Open WebUI 不支持自动数据迁移,需要手动导出导入: + +```bash +# 1. 从旧容器导出数据(使用 SQLite) +docker exec old-container python -m open_webui.utils.export_data > data.json + +# 2. 启动新容器(使用 MySQL) +docker run -d \ + -e DATABASE_URL=mysql://... \ + --name new-container \ + ghcr.io//open-webui-next:slim + +# 3. 导入数据 +docker exec -i new-container python -m open_webui.utils.import_data < data.json +``` + +--- + +## 维护操作 + +### 更新镜像 + +```bash +# 拉取最新镜像 +docker pull ghcr.io//open-webui-next:slim + +# 停止并删除旧容器 +docker stop open-webui +docker rm open-webui + +# 启动新容器(数据卷保留) +docker run -d \ + --name open-webui \ + ... # 相同参数 + ghcr.io//open-webui-next:slim +``` + +### 查看日志 + +```bash +# 实时查看日志 +docker logs -f open-webui + +# 查看最近 100 行日志 +docker logs --tail 100 open-webui + +# 查看特定时间范围日志 +docker logs --since "2024-01-01T00:00:00" open-webui +``` + +### 进入容器调试 + +```bash +# 进入容器 bash +docker exec -it open-webui bash + +# 查看环境变量 +docker exec open-webui env + +# 查看进程 +docker exec open-webui ps aux +``` + +--- + +## 安全建议 + +1. **不要在 DATABASE_URL 中明文暴露密码** + - 使用 Docker secrets 或环境变量文件 + - `.env` 文件添加到 `.gitignore` + +2. **定期更新镜像** + - 订阅 GitHub 仓库 Release 通知 + - 定期执行 `docker pull` 更新 + +3. **限制容器权限** + ```bash + docker run -d \ + --security-opt=no-new-privileges \ + --cap-drop=ALL \ + --read-only \ + --tmpfs /tmp \ + ... + ``` + +4. **使用 HTTPS** + - 生产环境必须使用 HTTPS(Nginx + Let's Encrypt) + - 设置 `WEBUI_URL=https://...` + +5. **禁用不必要的功能** + ```bash + -e ENABLE_SIGNUP=false # 禁止公开注册 + -e ENABLE_API_KEY=true # 启用 API Key 认证 + ``` + +--- + +## 性能优化建议 + +### 1. 数据库优化 + +- 使用 MySQL 8.0+ 以获得更好性能 +- 启用 InnoDB 缓冲池:`innodb_buffer_pool_size = 2G` +- 定期执行 `OPTIMIZE TABLE` 优化表 + +### 2. 应用优化 + +```bash +# 增加 Worker 数量(根据 CPU 核心数) +-e UVICORN_WORKERS=4 + +# 增加数据库连接池 +-e DATABASE_POOL_SIZE=20 +-e DATABASE_POOL_MAX_OVERFLOW=10 +``` + +### 3. 缓存优化 + +```bash +# 启用 Redis 缓存(可选) +-e REDIS_URL=redis://redis:6379/0 + +# 增加 AI 模型缓存 +-v /data/open-webui/cache:/root/.cache +``` + +--- + +## 联系与支持 + +- **文档**: 项目 README.md 和 CLAUDE.md +- **问题反馈**: GitHub Issues +- **社区讨论**: GitHub Discussions + +--- + +**最后更新**: 2024-11-14 diff --git a/docs/LOCAL-PUSH-GUIDE.md b/docs/LOCAL-PUSH-GUIDE.md new file mode 100644 index 0000000000..f6746c2fce --- /dev/null +++ b/docs/LOCAL-PUSH-GUIDE.md @@ -0,0 +1,676 @@ +# 本地镜像构建与推送指南 + +本指南介绍如何在本地构建 Docker 镜像并手动推送到 GitHub Container Registry (GHCR)。 + +## 当前仓库信息 + +- **仓库**: `ai-friend-coming/open-webui-next` +- **镜像仓库**: `ghcr.io/ai-friend-coming/open-webui-next` +- **当前分支**: `main` +- **当前 commit**: `88396a16e` + +--- + +## 前置准备 + +### 1. 登录 GitHub Container Registry + +#### 创建 Personal Access Token (PAT) + +1. 访问 https://github.com/settings/tokens +2. 点击 "Generate new token" → "Generate new token (classic)" +3. 设置权限: + - `write:packages` - 推送容器镜像 + - `read:packages` - 拉取容器镜像 + - `delete:packages` - (可选) 删除镜像 +4. 生成并保存 Token + +#### 登录 GHCR + +```bash +# 方式 1: 使用 PAT 登录 (推荐) +export CR_PAT=YOUR_PERSONAL_ACCESS_TOKEN +echo $CR_PAT | docker login ghcr.io -u ai-friend-coming --password-stdin + +# 方式 2: 交互式登录 +docker login ghcr.io -u ai-friend-coming +# Password: [输入 PAT] +``` + +成功登录后会显示: +``` +Login Succeeded +``` + +### 2. 验证 Docker 环境 + +```bash +# 检查 Docker 版本 +docker --version +# 推荐: Docker version 24.0.0 或更高 + +# 检查 Buildx 插件 +docker buildx version +# 推荐: v0.11.0 或更高 + +# 创建 Buildx builder (如果不存在) +docker buildx create --name multiarch-builder --use +docker buildx inspect --bootstrap +``` + +--- + +## 构建与推送流程 + +### 方式一: 模拟 GitHub Actions 流程 (推荐) + +完全模拟 `.github/workflows/docker-build.yaml` 的构建流程: + +```bash +#!/bin/bash +# build-and-push.sh + +set -e # 遇到错误立即退出 + +# ============ 配置变量 ============ +REGISTRY="ghcr.io" +IMAGE_NAME="ai-friend-coming/open-webui-next" +FULL_IMAGE_NAME="${REGISTRY}/${IMAGE_NAME}" + +# 获取 Git 信息 +BUILD_HASH=$(git rev-parse HEAD) +SHORT_HASH=$(git rev-parse --short HEAD) +BRANCH=$(git branch --show-current) +TIMESTAMP=$(date +%Y%m%d-%H%M%S) + +echo "=========================================" +echo "构建信息:" +echo " 仓库: ${IMAGE_NAME}" +echo " 分支: ${BRANCH}" +echo " Commit: ${SHORT_HASH}" +echo " 时间: ${TIMESTAMP}" +echo "=========================================" + +# ============ 镜像标签生成 ============ +TAGS=( + "${FULL_IMAGE_NAME}:slim" # 主标签 + "${FULL_IMAGE_NAME}:${BRANCH}-slim" # 分支标签 + "${FULL_IMAGE_NAME}:git-${SHORT_HASH}-slim" # Git commit 标签 + "${FULL_IMAGE_NAME}:${TIMESTAMP}-slim" # 时间戳标签 +) + +# 如果在 main 分支,添加 latest-slim 标签 +if [ "$BRANCH" = "main" ]; then + TAGS+=("${FULL_IMAGE_NAME}:latest-slim") +fi + +# 构建标签参数 +TAG_ARGS="" +for tag in "${TAGS[@]}"; do + TAG_ARGS="${TAG_ARGS} -t ${tag}" +done + +echo "" +echo "将构建以下标签:" +for tag in "${TAGS[@]}"; do + echo " - ${tag}" +done +echo "" + +# ============ 构建镜像 ============ +echo "开始构建镜像..." +docker buildx build \ + --platform linux/amd64 \ + --build-arg BUILD_HASH="${BUILD_HASH}" \ + --build-arg USE_SLIM=true \ + ${TAG_ARGS} \ + --load \ + . + +echo "" +echo "✅ 镜像构建成功!" +echo "" + +# ============ 推送镜像 ============ +read -p "是否推送镜像到 GHCR? (y/n): " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "开始推送镜像..." + + for tag in "${TAGS[@]}"; do + echo "推送: ${tag}" + docker push "${tag}" + done + + echo "" + echo "✅ 所有镜像推送成功!" + echo "" + echo "拉取命令:" + echo " docker pull ${FULL_IMAGE_NAME}:slim" + echo "" + echo "查看镜像:" + echo " https://github.com/${IMAGE_NAME}/pkgs/container/open-webui-next" +else + echo "跳过推送" +fi + +# ============ 清理 ============ +echo "" +read -p "是否清理本地构建缓存? (y/n): " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "清理构建缓存..." + docker builder prune -f + echo "✅ 缓存清理完成" +fi +``` + +**使用方法**: + +```bash +# 添加执行权限 +chmod +x build-and-push.sh + +# 执行构建 +./build-and-push.sh +``` + +### 方式二: 手动分步执行 + +#### 1. 构建镜像 + +```bash +# 获取当前 commit SHA +BUILD_HASH=$(git rev-parse HEAD) +SHORT_HASH=$(git rev-parse --short HEAD) + +# 构建镜像 +docker buildx build \ + --platform linux/amd64 \ + --build-arg BUILD_HASH="${BUILD_HASH}" \ + --build-arg USE_SLIM=true \ + -t ghcr.io/ai-friend-coming/open-webui-next:slim \ + -t ghcr.io/ai-friend-coming/open-webui-next:main-slim \ + -t ghcr.io/ai-friend-coming/open-webui-next:git-${SHORT_HASH}-slim \ + --load \ + . +``` + +**构建参数说明**: +- `--platform linux/amd64`: 构建 x86_64 架构镜像 +- `--build-arg BUILD_HASH`: 传入 Git commit SHA +- `--build-arg USE_SLIM=true`: 构建精简版 (不预装模型) +- `-t`: 指定镜像标签 (可以多个) +- `--load`: 加载到本地 Docker (用于单平台构建) + +#### 2. 验证镜像 + +```bash +# 查看镜像大小 +docker images | grep open-webui-next + +# 查看镜像详细信息 +docker inspect ghcr.io/ai-friend-coming/open-webui-next:slim + +# 测试运行 +docker run --rm -p 8080:8080 ghcr.io/ai-friend-coming/open-webui-next:slim +``` + +#### 3. 推送镜像 + +```bash +# 推送所有标签 +docker push ghcr.io/ai-friend-coming/open-webui-next:slim +docker push ghcr.io/ai-friend-coming/open-webui-next:main-slim +docker push ghcr.io/ai-friend-coming/open-webui-next:git-${SHORT_HASH}-slim +``` + +或批量推送: + +```bash +# 批量推送 +docker images | grep "ghcr.io/ai-friend-coming/open-webui-next" | awk '{print $1":"$2}' | xargs -I {} docker push {} +``` + +#### 4. 验证推送 + +```bash +# 从 GHCR 拉取验证 +docker pull ghcr.io/ai-friend-coming/open-webui-next:slim + +# 访问 GitHub Packages 页面 +# https://github.com/ai-friend-coming/open-webui-next/pkgs/container/open-webui-next +``` + +--- + +## 构建不同镜像变体 + +### Slim 版本 (默认, 推荐) + +```bash +docker buildx build \ + --platform linux/amd64 \ + --build-arg BUILD_HASH=$(git rev-parse HEAD) \ + --build-arg USE_SLIM=true \ + -t ghcr.io/ai-friend-coming/open-webui-next:slim \ + --load \ + . +``` + +**特点**: +- 镜像较小 (~7.8GB) +- 首次运行时自动下载 AI 模型 +- 适合生产环境 + +### 标准版本 (预装模型) + +```bash +docker buildx build \ + --platform linux/amd64 \ + --build-arg BUILD_HASH=$(git rev-parse HEAD) \ + --build-arg USE_SLIM=false \ + -t ghcr.io/ai-friend-coming/open-webui-next:latest \ + --load \ + . +``` + +**特点**: +- 镜像较大 (~10GB) +- 预装 AI 模型,启动更快 +- 适合离线环境 + +### CUDA 版本 (GPU 加速) + +```bash +docker buildx build \ + --platform linux/amd64 \ + --build-arg BUILD_HASH=$(git rev-parse HEAD) \ + --build-arg USE_CUDA=true \ + --build-arg USE_CUDA_VER=cu128 \ + -t ghcr.io/ai-friend-coming/open-webui-next:cuda \ + --load \ + . +``` + +**特点**: +- 支持 NVIDIA GPU 加速 +- 需要宿主机安装 NVIDIA Docker runtime +- 镜像更大 (~15GB) + +--- + +## 高级功能 + +### 1. 多架构构建 (amd64 + arm64) + +```bash +# 创建 multiarch builder +docker buildx create --name multiarch --use +docker buildx inspect --bootstrap + +# 构建并推送多架构镜像 +docker buildx build \ + --platform linux/amd64,linux/arm64 \ + --build-arg BUILD_HASH=$(git rev-parse HEAD) \ + --build-arg USE_SLIM=true \ + -t ghcr.io/ai-friend-coming/open-webui-next:slim \ + --push \ + . +``` + +**注意**: +- 多架构构建会自动推送 (不支持 `--load`) +- ARM64 构建可能需要 1-2 小时 + +### 2. 使用缓存加速构建 + +```bash +# 第一次构建: 导出缓存 +docker buildx build \ + --platform linux/amd64 \ + --build-arg BUILD_HASH=$(git rev-parse HEAD) \ + --build-arg USE_SLIM=true \ + -t ghcr.io/ai-friend-coming/open-webui-next:slim \ + --cache-to type=registry,ref=ghcr.io/ai-friend-coming/open-webui-next:cache-slim-amd64 \ + --load \ + . + +# 后续构建: 使用缓存 +docker buildx build \ + --platform linux/amd64 \ + --build-arg BUILD_HASH=$(git rev-parse HEAD) \ + --build-arg USE_SLIM=true \ + -t ghcr.io/ai-friend-coming/open-webui-next:slim \ + --cache-from type=registry,ref=ghcr.io/ai-friend-coming/open-webui-next:cache-slim-amd64 \ + --load \ + . +``` + +**效果**: 构建时间从 5 分钟降低到 1-2 分钟 + +### 3. 本地缓存 (更快) + +```bash +# 使用本地缓存 +docker buildx build \ + --platform linux/amd64 \ + --build-arg BUILD_HASH=$(git rev-parse HEAD) \ + --build-arg USE_SLIM=true \ + -t ghcr.io/ai-friend-coming/open-webui-next:slim \ + --cache-to type=local,dest=/tmp/docker-cache \ + --cache-from type=local,src=/tmp/docker-cache \ + --load \ + . +``` + +--- + +## 故障排查 + +### 1. 构建内存不足 + +**错误**: +``` +FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory +``` + +**解决方案**: +- ✅ 确认 Dockerfile 第 30 行已取消注释: `ENV NODE_OPTIONS="--max-old-space-size=4096"` +- ✅ 增加 Docker 内存限制: Docker Desktop → Settings → Resources → Memory (建议 8GB+) + +### 2. 推送权限被拒绝 + +**错误**: +``` +denied: permission_denied: write_package +``` + +**解决方案**: +```bash +# 检查登录状态 +docker info | grep Username + +# 重新登录 +docker logout ghcr.io +echo $CR_PAT | docker login ghcr.io -u ai-friend-coming --password-stdin + +# 确认 PAT 有 write:packages 权限 +``` + +### 3. 镜像推送超时 + +**错误**: +``` +error: timeout exceeded +``` + +**解决方案**: +```bash +# 增加 Docker 推送超时 +export DOCKER_CLIENT_TIMEOUT=300 +export COMPOSE_HTTP_TIMEOUT=300 + +# 或分别推送每个标签 +docker push ghcr.io/ai-friend-coming/open-webui-next:slim +``` + +### 4. Buildx 不可用 + +**错误**: +``` +ERROR: buildx: command not found +``` + +**解决方案**: +```bash +# 更新 Docker Desktop 到最新版本 +# 或手动安装 Buildx 插件 +mkdir -p ~/.docker/cli-plugins +curl -Lo ~/.docker/cli-plugins/docker-buildx https://github.com/docker/buildx/releases/download/v0.11.2/buildx-v0.11.2.linux-amd64 +chmod +x ~/.docker/cli-plugins/docker-buildx +``` + +--- + +## 清理与维护 + +### 清理本地镜像 + +```bash +# 删除 dangling 镜像 +docker image prune -f + +# 删除所有未使用的镜像 +docker image prune -a -f + +# 删除特定镜像 +docker rmi ghcr.io/ai-friend-coming/open-webui-next:slim +``` + +### 清理构建缓存 + +```bash +# 清理 Buildx 缓存 +docker buildx prune -f + +# 清理所有 Docker 缓存 (谨慎使用) +docker system prune -a --volumes -f +``` + +### 查看镜像层信息 + +```bash +# 使用 dive 工具分析镜像 +docker run --rm -it \ + -v /var/run/docker.sock:/var/run/docker.sock \ + wagoodman/dive:latest \ + ghcr.io/ai-friend-coming/open-webui-next:slim +``` + +--- + +## 自动化脚本示例 + +### 完整的 CI/CD 本地模拟脚本 + +保存为 `scripts/local-build.sh`: + +```bash +#!/bin/bash +# scripts/local-build.sh +# 完整的本地构建、测试、推送流程 + +set -e + +# 颜色输出 +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo_info() { echo -e "${GREEN}[INFO]${NC} $1"; } +echo_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +echo_error() { echo -e "${RED}[ERROR]${NC} $1"; } + +# 配置 +REGISTRY="ghcr.io" +IMAGE_NAME="ai-friend-coming/open-webui-next" +FULL_IMAGE_NAME="${REGISTRY}/${IMAGE_NAME}" +VARIANT="slim" + +# Git 信息 +BUILD_HASH=$(git rev-parse HEAD) +SHORT_HASH=$(git rev-parse --short HEAD) +BRANCH=$(git branch --show-current) + +# 检查工作目录 +if [ ! -f "Dockerfile" ]; then + echo_error "请在项目根目录运行此脚本" + exit 1 +fi + +# 检查未提交的更改 +if [ -n "$(git status --porcelain)" ]; then + echo_warn "存在未提交的更改:" + git status --short + read -p "继续构建? (y/n): " -n 1 -r + echo + [[ ! $REPLY =~ ^[Yy]$ ]] && exit 1 +fi + +echo_info "=========================================" +echo_info "构建配置:" +echo_info " 仓库: ${IMAGE_NAME}" +echo_info " 分支: ${BRANCH}" +echo_info " Commit: ${SHORT_HASH}" +echo_info " 变体: ${VARIANT}" +echo_info "=========================================" + +# 1. 构建镜像 +echo_info "步骤 1/5: 构建镜像..." +docker buildx build \ + --platform linux/amd64 \ + --build-arg BUILD_HASH="${BUILD_HASH}" \ + --build-arg USE_SLIM=true \ + -t ${FULL_IMAGE_NAME}:${VARIANT} \ + -t ${FULL_IMAGE_NAME}:git-${SHORT_HASH}-${VARIANT} \ + --load \ + . + +# 2. 验证镜像大小 +echo_info "步骤 2/5: 验证镜像..." +IMAGE_SIZE=$(docker images ${FULL_IMAGE_NAME}:${VARIANT} --format "{{.Size}}") +echo_info " 镜像大小: ${IMAGE_SIZE}" + +# 3. 测试镜像 +echo_info "步骤 3/5: 测试镜像..." +CONTAINER_ID=$(docker run -d -p 8081:8080 ${FULL_IMAGE_NAME}:${VARIANT}) +echo_info " 测试容器 ID: ${CONTAINER_ID}" + +# 等待健康检查 +echo_info " 等待服务启动 (最多 60 秒)..." +for i in {1..60}; do + if curl -sf http://localhost:8081/health > /dev/null 2>&1; then + echo_info " ✅ 健康检查通过" + break + fi + sleep 1 + [ $i -eq 60 ] && echo_error "健康检查超时" && docker logs ${CONTAINER_ID} && exit 1 +done + +# 清理测试容器 +docker stop ${CONTAINER_ID} > /dev/null +docker rm ${CONTAINER_ID} > /dev/null +echo_info " 测试容器已清理" + +# 4. 推送镜像 +echo_info "步骤 4/5: 推送镜像到 GHCR..." +read -p "确认推送? (y/n): " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]]; then + # 检查登录状态 + if ! docker info | grep -q "Username: ai-friend-coming"; then + echo_error "未登录 GHCR,请先登录:" + echo " echo \$CR_PAT | docker login ghcr.io -u ai-friend-coming --password-stdin" + exit 1 + fi + + docker push ${FULL_IMAGE_NAME}:${VARIANT} + docker push ${FULL_IMAGE_NAME}:git-${SHORT_HASH}-${VARIANT} + echo_info " ✅ 推送成功" +else + echo_warn "跳过推送" +fi + +# 5. 清理 +echo_info "步骤 5/5: 清理..." +docker builder prune -f > /dev/null +echo_info " 构建缓存已清理" + +echo_info "=========================================" +echo_info "✅ 构建流程完成!" +echo_info "" +echo_info "拉取命令:" +echo_info " docker pull ${FULL_IMAGE_NAME}:${VARIANT}" +echo_info "" +echo_info "查看镜像:" +echo_info " https://github.com/${IMAGE_NAME}/pkgs/container/open-webui-next" +echo_info "=========================================" +``` + +**使用方法**: + +```bash +chmod +x scripts/local-build.sh +./scripts/local-build.sh +``` + +--- + +## 最佳实践 + +### 1. 构建前检查清单 + +- [ ] 代码已提交到 Git +- [ ] Docker 有足够内存 (8GB+) +- [ ] 已登录 GHCR +- [ ] 磁盘空间充足 (20GB+) + +### 2. 标签命名规范 + +```bash +# 生产环境 +ghcr.io/ai-friend-coming/open-webui-next:v1.2.3-slim + +# 测试环境 +ghcr.io/ai-friend-coming/open-webui-next:dev-slim + +# 特性分支 +ghcr.io/ai-friend-coming/open-webui-next:feature-auth-slim +``` + +### 3. 安全建议 + +- 不要在脚本中硬编码 PAT +- 使用环境变量: `export CR_PAT=xxx` +- 定期轮换 PAT (90 天) +- 使用 `.gitignore` 排除敏感文件 + +### 4. 性能优化 + +- 使用 `--cache-from` 复用缓存 +- 本地缓存构建结果到 `/tmp` +- 使用 SSD 存储 Docker 数据 +- 增大 Docker 内存限制 + +--- + +## 常用命令速查 + +```bash +# 构建 +docker buildx build -t IMAGE:TAG --load . + +# 推送 +docker push IMAGE:TAG + +# 拉取 +docker pull IMAGE:TAG + +# 登录 +echo $CR_PAT | docker login ghcr.io -u USERNAME --password-stdin + +# 清理 +docker system prune -a -f + +# 查看镜像 +docker images | grep open-webui-next + +# 测试镜像 +docker run --rm -p 8080:8080 IMAGE:TAG +``` + +--- + +**最后更新**: 2024-11-14 diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000000..df4196c1fa --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,322 @@ +# 构建脚本使用指南 + +本目录包含用于本地构建和推送 Docker 镜像的自动化脚本。 + +## 脚本列表 + +### 1. `build-and-push.sh` - 完整构建和推送流程 (推荐) + +**功能**: 完整的构建、测试、推送流程,包含健康检查 + +**使用方法**: +```bash +# 1. 登录 GHCR (如需推送) +export CR_PAT=YOUR_PERSONAL_ACCESS_TOKEN +echo $CR_PAT | docker login ghcr.io -u ai-friend-coming --password-stdin + +# 2. 运行脚本 +./scripts/build-and-push.sh +``` + +**流程**: +1. 检查 Git 状态和未提交更改 +2. 构建 Docker 镜像 (slim 版本) +3. 验证镜像大小和 ID +4. 运行健康检查测试 (可选) +5. 推送镜像到 GHCR (需确认) +6. 清理构建缓存 (可选) + +**适用场景**: 正式发布前的完整测试和推送 + +--- + +### 2. `quick-build.sh` - 快速本地构建 + +**功能**: 快速构建镜像用于本地测试,不推送 + +**使用方法**: +```bash +./scripts/quick-build.sh +``` + +**特点**: +- 无交互式确认 +- 仅构建,不推送 +- 构建速度快 (如有缓存) + +**适用场景**: 本地开发和快速测试 + +--- + +### 3. `simulate-workflow.sh` - 模拟 GitHub Actions + +**功能**: 完整模拟 `.github/workflows/docker-build.yaml` 的执行流程 + +**使用方法**: +```bash +# 设置 CR_PAT (可选) +export CR_PAT=YOUR_PERSONAL_ACCESS_TOKEN + +# 运行模拟 +./scripts/simulate-workflow.sh +``` + +**特点**: +- 模拟 GitHub Actions 环境变量 +- 创建独立的 Buildx builder +- 使用 registry 缓存 +- 输出格式与 GitHub Actions 一致 + +**适用场景**: +- 测试 workflow 配置 +- 在推送代码前验证构建流程 +- 排查 CI/CD 问题 + +--- + +## 使用示例 + +### 场景 1: 本地快速测试 + +```bash +# 1. 快速构建 +./scripts/quick-build.sh + +# 2. 运行测试 +docker run -d -p 8080:8080 ghcr.io/ai-friend-coming/open-webui-next:slim + +# 3. 验证 +curl http://localhost:8080/health +``` + +### 场景 2: 发布新版本 + +```bash +# 1. 确保代码已提交 +git add . +git commit -m "feat: add new feature" +git push + +# 2. 登录 GHCR +export CR_PAT=ghp_xxxxxxxxxxxx +echo $CR_PAT | docker login ghcr.io -u ai-friend-coming --password-stdin + +# 3. 构建和推送 +./scripts/build-and-push.sh +# 按提示操作: 运行健康检查 → 确认推送 → 清理缓存 + +# 4. 验证推送成功 +docker pull ghcr.io/ai-friend-coming/open-webui-next:slim +``` + +### 场景 3: 测试 GitHub Actions workflow + +```bash +# 1. 模拟 workflow 执行 +export CR_PAT=ghp_xxxxxxxxxxxx +./scripts/simulate-workflow.sh + +# 2. 查看构建结果 +docker images | grep open-webui-next + +# 3. 如果成功,推送代码触发真实 workflow +git push origin main +``` + +--- + +## 环境要求 + +### 必需软件 + +- **Docker**: 24.0.0+ +- **Docker Buildx**: v0.11.0+ +- **Git**: 任意版本 +- **Bash**: 4.0+ + +### 系统要求 + +- **内存**: 建议 8GB+ +- **磁盘空间**: 至少 20GB 可用空间 +- **网络**: 需要访问 ghcr.io + +### Docker 配置 + +确保 Docker 有足够的资源: + +```bash +# Docker Desktop 配置 (推荐): +# - Memory: 8GB +# - Swap: 2GB +# - Disk image size: 64GB +``` + +--- + +## 常见问题 + +### 1. 构建失败 - 内存不足 + +**错误信息**: +``` +FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory +``` + +**解决方案**: +- 确认 `Dockerfile` 第 30 行已启用: `ENV NODE_OPTIONS="--max-old-space-size=4096"` +- 增加 Docker Desktop 内存限制到 8GB+ + +### 2. 推送失败 - 未登录 + +**错误信息**: +``` +unauthorized: authentication required +``` + +**解决方案**: +```bash +# 设置 PAT +export CR_PAT=ghp_your_token_here + +# 登录 GHCR +echo $CR_PAT | docker login ghcr.io -u ai-friend-coming --password-stdin +``` + +### 3. 脚本无执行权限 + +**错误信息**: +``` +Permission denied +``` + +**解决方案**: +```bash +chmod +x scripts/*.sh +``` + +### 4. Buildx 不可用 + +**错误信息**: +``` +ERROR: buildx: command not found +``` + +**解决方案**: +```bash +# 安装 Buildx 插件 +docker buildx install + +# 或更新 Docker Desktop 到最新版本 +``` + +--- + +## 标签规则 + +所有脚本都会生成以下标签: + +| 标签格式 | 示例 | 说明 | +|---------|------|------| +| `slim` | `ghcr.io/ai-friend-coming/open-webui-next:slim` | 主标签 (main 分支) | +| `latest-slim` | `ghcr.io/ai-friend-coming/open-webui-next:latest-slim` | 最新版本 (仅 main 分支) | +| `{branch}-slim` | `ghcr.io/ai-friend-coming/open-webui-next:main-slim` | 分支标签 | +| `git-{sha}-slim` | `ghcr.io/ai-friend-coming/open-webui-next:git-88396a1-slim` | Git commit 标签 | + +--- + +## 安全最佳实践 + +### PAT (Personal Access Token) 管理 + +1. **权限设置**: 仅授予 `read:packages` 和 `write:packages` +2. **存储位置**: 使用环境变量,不要硬编码到脚本 +3. **轮换周期**: 建议每 90 天轮换一次 +4. **作用域**: 为不同用途创建不同的 PAT + +### 环境变量设置 + +```bash +# 临时设置 (当前会话) +export CR_PAT=ghp_xxxxxxxxxxxx + +# 永久设置 (添加到 ~/.bashrc 或 ~/.zshrc) +echo 'export CR_PAT=ghp_xxxxxxxxxxxx' >> ~/.bashrc +source ~/.bashrc +``` + +**注意**: 不要提交 PAT 到 Git 仓库 + +--- + +## 性能优化 + +### 使用构建缓存 + +```bash +# 第一次构建会较慢 (5-10 分钟) +./scripts/build-and-push.sh + +# 后续构建会使用缓存 (1-2 分钟) +# 前提: 没有清理缓存 +``` + +### 并行构建 (高级) + +如果需要同时构建多个变体: + +```bash +# 构建 slim 和 cuda 版本 +docker buildx build --build-arg USE_SLIM=true -t IMAGE:slim . & +docker buildx build --build-arg USE_CUDA=true -t IMAGE:cuda . & +wait +``` + +--- + +## 清理命令 + +### 清理所有本地镜像 + +```bash +# 删除所有 open-webui-next 镜像 +docker images | grep open-webui-next | awk '{print $3}' | xargs docker rmi -f + +# 清理悬空镜像 +docker image prune -f + +# 清理所有未使用的镜像 +docker image prune -a -f +``` + +### 清理构建缓存 + +```bash +# 清理 Buildx 缓存 +docker buildx prune -f + +# 清理所有 Docker 数据 (谨慎使用) +docker system prune -a --volumes -f +``` + +--- + +## 相关文档 + +- [本地推送完整指南](../docs/LOCAL-PUSH-GUIDE.md) +- [Docker 部署指南](../docs/DOCKER-DEPLOYMENT.md) +- [GitHub Actions Workflow](../.github/workflows/docker-build.yaml) + +--- + +## 获取帮助 + +如果遇到问题: + +1. 查看脚本输出的错误信息 +2. 参考本文档的"常见问题"部分 +3. 查看详细文档: `docs/LOCAL-PUSH-GUIDE.md` +4. 提交 Issue: https://github.com/ai-friend-coming/open-webui-next/issues + +--- + +**最后更新**: 2024-11-14 diff --git a/scripts/build-and-push.sh b/scripts/build-and-push.sh new file mode 100755 index 0000000000..f766545856 --- /dev/null +++ b/scripts/build-and-push.sh @@ -0,0 +1,215 @@ +#!/bin/bash +# build-and-push.sh - 本地构建并推送镜像到 GHCR +# 使用方法: ./scripts/build-and-push.sh + +set -e # 遇到错误立即退出 + +# ============ 颜色输出 ============ +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo_info() { echo -e "${GREEN}[INFO]${NC} $1"; } +echo_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } +echo_error() { echo -e "${RED}[ERROR]${NC} $1"; } +echo_step() { echo -e "${BLUE}[STEP]${NC} $1"; } + +# ============ 配置变量 ============ +REGISTRY="ghcr.io" +IMAGE_NAME="ai-friend-coming/open-webui-next" +FULL_IMAGE_NAME="${REGISTRY}/${IMAGE_NAME}" + +# 获取 Git 信息 +BUILD_HASH=$(git rev-parse HEAD) +SHORT_HASH=$(git rev-parse --short HEAD) +BRANCH=$(git branch --show-current) +TIMESTAMP=$(date +%Y%m%d-%H%M%S) + +# 检查工作目录 +if [ ! -f "Dockerfile" ]; then + echo_error "请在项目根目录运行此脚本" + exit 1 +fi + +echo_info "=========================================" +echo_info "构建信息:" +echo_info " 仓库: ${IMAGE_NAME}" +echo_info " 分支: ${BRANCH}" +echo_info " Commit: ${SHORT_HASH} (${BUILD_HASH:0:40}...)" +echo_info " 时间: ${TIMESTAMP}" +echo_info "=========================================" +echo "" + +# 检查未提交的更改 +if [ -n "$(git status --porcelain)" ]; then + echo_warn "存在未提交的更改:" + git status --short + echo "" + read -p "继续构建? (y/n): " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo_info "已取消" + exit 0 + fi +fi + +# ============ 镜像标签生成 ============ +TAGS=( + "${FULL_IMAGE_NAME}:slim" # 主标签 + "${FULL_IMAGE_NAME}:${BRANCH}-slim" # 分支标签 + "${FULL_IMAGE_NAME}:git-${SHORT_HASH}-slim" # Git commit 标签 +) + +# 如果在 main 分支,添加 latest-slim 标签 +if [ "$BRANCH" = "main" ]; then + TAGS+=("${FULL_IMAGE_NAME}:latest-slim") +fi + +# 构建标签参数 +TAG_ARGS="" +for tag in "${TAGS[@]}"; do + TAG_ARGS="${TAG_ARGS} -t ${tag}" +done + +echo_info "将构建以下标签:" +for tag in "${TAGS[@]}"; do + echo " - ${tag}" +done +echo "" + +# ============ 构建镜像 ============ +echo_step "步骤 1/4: 构建 Docker 镜像" +echo_info "开始构建镜像 (这可能需要 5-10 分钟)..." +echo "" + +docker buildx build \ + --platform linux/amd64 \ + --build-arg BUILD_HASH="${BUILD_HASH}" \ + --build-arg USE_SLIM=true \ + ${TAG_ARGS} \ + --load \ + . + +echo "" +echo_info "✅ 镜像构建成功!" +echo "" + +# ============ 验证镜像 ============ +echo_step "步骤 2/4: 验证镜像" + +IMAGE_SIZE=$(docker images ${FULL_IMAGE_NAME}:slim --format "{{.Size}}") +IMAGE_ID=$(docker images ${FULL_IMAGE_NAME}:slim --format "{{.ID}}") + +echo_info "镜像信息:" +echo " - ID: ${IMAGE_ID}" +echo " - 大小: ${IMAGE_SIZE}" +echo "" + +# ============ 测试镜像 ============ +echo_step "步骤 3/4: 测试镜像" +read -p "是否运行健康检查测试? (y/n): " -n 1 -r +echo + +if [[ $REPLY =~ ^[Yy]$ ]]; then + echo_info "启动测试容器 (端口 8081)..." + + CONTAINER_ID=$(docker run -d -p 8081:8080 ${FULL_IMAGE_NAME}:slim) + echo_info "容器 ID: ${CONTAINER_ID}" + echo "" + + echo_info "等待服务启动 (最多 120 秒)..." + HEALTH_CHECK_PASSED=false + + for i in {1..120}; do + if curl -sf http://localhost:8081/health > /dev/null 2>&1; then + echo_info "✅ 健康检查通过 (耗时 ${i} 秒)" + HEALTH_CHECK_PASSED=true + break + fi + + # 每 10 秒显示一次进度 + if [ $((i % 10)) -eq 0 ]; then + echo_info " 等待中... (${i}/120 秒)" + fi + + sleep 1 + done + + if [ "$HEALTH_CHECK_PASSED" = false ]; then + echo_error "健康检查超时!" + echo_error "容器日志:" + docker logs ${CONTAINER_ID} | tail -50 + docker stop ${CONTAINER_ID} > /dev/null + docker rm ${CONTAINER_ID} > /dev/null + exit 1 + fi + + # 清理测试容器 + echo_info "清理测试容器..." + docker stop ${CONTAINER_ID} > /dev/null + docker rm ${CONTAINER_ID} > /dev/null + echo "" +else + echo_warn "跳过健康检查" + echo "" +fi + +# ============ 推送镜像 ============ +echo_step "步骤 4/4: 推送镜像到 GHCR" +echo_info "将推送 ${#TAGS[@]} 个标签到 ${REGISTRY}" +echo "" + +read -p "确认推送? (y/n): " -n 1 -r +echo + +if [[ $REPLY =~ ^[Yy]$ ]]; then + # 检查登录状态 + if ! docker info 2>/dev/null | grep -q "Username"; then + echo_error "未登录 GHCR!" + echo_error "请先执行:" + echo " export CR_PAT=YOUR_PERSONAL_ACCESS_TOKEN" + echo " echo \$CR_PAT | docker login ghcr.io -u ai-friend-coming --password-stdin" + exit 1 + fi + + echo_info "开始推送镜像..." + echo "" + + for tag in "${TAGS[@]}"; do + echo_info "推送: ${tag}" + docker push "${tag}" + done + + echo "" + echo_info "✅ 所有镜像推送成功!" + echo "" + echo_info "拉取命令:" + echo " docker pull ${FULL_IMAGE_NAME}:slim" + echo "" + echo_info "查看镜像:" + echo " https://github.com/${IMAGE_NAME}/pkgs/container/open-webui-next" + echo "" +else + echo_warn "跳过推送" + echo "" +fi + +# ============ 清理 ============ +echo_info "清理选项:" +read -p "是否清理本地构建缓存? (y/n): " -n 1 -r +echo + +if [[ $REPLY =~ ^[Yy]$ ]]; then + echo_info "清理构建缓存..." + docker builder prune -f + echo_info "✅ 缓存清理完成" +else + echo_warn "保留构建缓存 (下次构建会更快)" +fi + +echo "" +echo_info "=========================================" +echo_info "✅ 构建流程完成!" +echo_info "=========================================" diff --git a/scripts/quick-build.sh b/scripts/quick-build.sh new file mode 100755 index 0000000000..f492ba3bca --- /dev/null +++ b/scripts/quick-build.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# quick-build.sh - 快速本地构建 (不推送) +# 使用方法: ./scripts/quick-build.sh + +set -e + +# 获取 Git 信息 +BUILD_HASH=$(git rev-parse HEAD) +SHORT_HASH=$(git rev-parse --short HEAD) + +echo "🏗️ 开始快速构建..." +echo "📋 Commit: ${SHORT_HASH}" +echo "" + +# 构建镜像 +docker buildx build \ + --platform linux/amd64 \ + --build-arg BUILD_HASH="${BUILD_HASH}" \ + --build-arg USE_SLIM=true \ + -t ghcr.io/ai-friend-coming/open-webui-next:slim \ + -t ghcr.io/ai-friend-coming/open-webui-next:dev \ + --load \ + . + +echo "" +echo "✅ 构建完成!" +echo "" +echo "🚀 运行命令:" +echo " docker run -d -p 8080:8080 ghcr.io/ai-friend-coming/open-webui-next:slim" diff --git a/scripts/simulate-workflow.sh b/scripts/simulate-workflow.sh new file mode 100755 index 0000000000..51627f9c7d --- /dev/null +++ b/scripts/simulate-workflow.sh @@ -0,0 +1,181 @@ +#!/bin/bash +# simulate-workflow.sh - 模拟 GitHub Actions workflow 完整流程 +# 使用方法: ./scripts/simulate-workflow.sh + +set -e + +# 颜色 +GREEN='\033[0;32m' +BLUE='\033[0;34m' +YELLOW='\033[1;33m' +NC='\033[0m' + +echo_job() { echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"; echo -e "${BLUE}[JOB]${NC} $1"; } +echo_step() { echo -e "${GREEN}[STEP]${NC} $1"; } +echo_info() { echo -e " $1"; } + +# ============ 模拟 GitHub Actions 环境变量 ============ +export GITHUB_REPOSITORY="ai-friend-coming/open-webui-next" +export GITHUB_SHA=$(git rev-parse HEAD) +export GITHUB_REF=$(git symbolic-ref HEAD) +export GITHUB_REF_NAME=$(git branch --show-current) +export IMAGE_NAME="${GITHUB_REPOSITORY,,}" # 转小写 +export FULL_IMAGE_NAME="ghcr.io/${IMAGE_NAME}" + +echo_job "Build Slim Image (amd64)" +echo "" + +# ============ Step 1: Set repository and image name ============ +echo_step "Set repository and image name to lowercase" +echo_info "IMAGE_NAME=${IMAGE_NAME}" +echo_info "FULL_IMAGE_NAME=${FULL_IMAGE_NAME}" +echo "" + +# ============ Step 2: Checkout repository ============ +echo_step "Checkout repository" +echo_info "Repository: ${GITHUB_REPOSITORY}" +echo_info "Branch: ${GITHUB_REF_NAME}" +echo_info "Commit: ${GITHUB_SHA:0:7}" +echo "" + +# ============ Step 3: Set up Docker Buildx ============ +echo_step "Set up Docker Buildx" +if ! docker buildx inspect github-actions > /dev/null 2>&1; then + docker buildx create --name github-actions --use + echo_info "Created new builder: github-actions" +else + docker buildx use github-actions + echo_info "Using existing builder: github-actions" +fi +docker buildx inspect --bootstrap +echo "" + +# ============ Step 4: Log in to GitHub Container Registry ============ +echo_step "Log in to GitHub Container Registry" +echo_info "Registry: ghcr.io" +echo_info "Username: $(whoami)" + +if [ -z "$CR_PAT" ]; then + echo -e "${YELLOW}[WARN]${NC} CR_PAT 环境变量未设置,跳过登录" + echo_info "如需推送,请设置: export CR_PAT=YOUR_PERSONAL_ACCESS_TOKEN" + SKIP_PUSH=true +else + echo $CR_PAT | docker login ghcr.io -u ai-friend-coming --password-stdin > /dev/null 2>&1 + echo_info "✅ Login Succeeded" + SKIP_PUSH=false +fi +echo "" + +# ============ Step 5: Extract metadata for Docker images ============ +echo_step "Extract metadata for Docker images" + +# 生成标签 +if [ "$GITHUB_REF_NAME" = "main" ]; then + TAGS=( + "${FULL_IMAGE_NAME}:slim" + "${FULL_IMAGE_NAME}:latest-slim" + "${FULL_IMAGE_NAME}:${GITHUB_REF_NAME}-slim" + "${FULL_IMAGE_NAME}:git-${GITHUB_SHA:0:7}-slim" + ) +else + TAGS=( + "${FULL_IMAGE_NAME}:${GITHUB_REF_NAME}-slim" + "${FULL_IMAGE_NAME}:git-${GITHUB_SHA:0:7}-slim" + ) +fi + +echo_info "Tags:" +for tag in "${TAGS[@]}"; do + echo_info " - ${tag}" +done +echo "" + +# 构建标签参数 +TAG_ARGS="" +for tag in "${TAGS[@]}"; do + TAG_ARGS="${TAG_ARGS} --tag ${tag}" +done + +# ============ Step 6: Extract metadata for Docker cache ============ +echo_step "Extract metadata for Docker cache" +CACHE_TAG="${FULL_IMAGE_NAME}:cache-slim-linux-amd64-${GITHUB_REF_NAME}" +echo_info "Cache: ${CACHE_TAG}" +echo "" + +# ============ Step 7: Build and push Docker image ============ +echo_step "Build and push Docker image (slim)" +echo_info "Platform: linux/amd64" +echo_info "Build args: BUILD_HASH=${GITHUB_SHA}, USE_SLIM=true" +echo "" + +if [ "$SKIP_PUSH" = true ]; then + # 仅构建,不推送 + docker buildx build \ + --platform linux/amd64 \ + --build-arg BUILD_HASH="${GITHUB_SHA}" \ + --build-arg USE_SLIM=true \ + ${TAG_ARGS} \ + --cache-from type=registry,ref=${CACHE_TAG} \ + --load \ + . + echo "" + echo -e "${YELLOW}[WARN]${NC} 跳过推送 (CR_PAT 未设置)" +else + # 构建并推送 + read -p "确认推送到 GHCR? (y/n): " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + docker buildx build \ + --platform linux/amd64 \ + --build-arg BUILD_HASH="${GITHUB_SHA}" \ + --build-arg USE_SLIM=true \ + ${TAG_ARGS} \ + --cache-from type=registry,ref=${CACHE_TAG} \ + --cache-to type=registry,ref=${CACHE_TAG},mode=max \ + --push \ + . + echo "" + echo_info "✅ Image pushed successfully" + else + docker buildx build \ + --platform linux/amd64 \ + --build-arg BUILD_HASH="${GITHUB_SHA}" \ + --build-arg USE_SLIM=true \ + ${TAG_ARGS} \ + --cache-from type=registry,ref=${CACHE_TAG} \ + --load \ + . + echo "" + echo -e "${YELLOW}[WARN]${NC} 用户取消推送" + fi +fi +echo "" + +# ============ Step 8: Inspect image ============ +echo_step "Inspect image" +if [ "$SKIP_PUSH" = false ] && [[ $REPLY =~ ^[Yy]$ ]]; then + docker buildx imagetools inspect ${FULL_IMAGE_NAME}:slim +else + docker images | grep open-webui-next | head -5 +fi +echo "" + +# ============ Step 9: Output image tags ============ +echo_step "Output image tags" +echo "" +echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${GREEN}🐳 Docker 镜像构建成功${NC}" +echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo "" +echo "📦 镜像标签:" +for tag in "${TAGS[@]}"; do + echo " ${tag}" +done +echo "" +echo "🚀 拉取命令:" +echo " docker pull ${FULL_IMAGE_NAME}:slim" +echo "" +echo "🌐 查看镜像:" +echo " https://github.com/${GITHUB_REPOSITORY}/pkgs/container/open-webui-next" +echo "" +echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"