From 356b3df81e0f784287cb50a6a01b1f814f66e256 Mon Sep 17 00:00:00 2001 From: ryan Date: Mon, 8 Jun 2026 21:12:55 +0800 Subject: [PATCH] =?UTF-8?q?=E8=9E=8D=E5=90=88=E6=A8=A1=E5=BC=8F=E5=90=AF?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-image.yml | 181 ++++++++++-------------------- internal/cmd/all.go | 68 +++++++++++ internal/cmd/root.go | 6 +- 3 files changed, 134 insertions(+), 121 deletions(-) create mode 100644 internal/cmd/all.go diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 1b8c2727..cb5b1ac9 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -1,4 +1,4 @@ -name: Docker Image +name: Docker image build (Server) on: workflow_dispatch: @@ -7,23 +7,9 @@ on: description: "Image version/tag to publish, for example v1.0.0-beta" required: false type: string - image_name: - description: "Image name without registry. Defaults to owner/repo." - required: false - type: string push: tags: ["v*"] -env: - REGISTRY: ghcr.io - DEFAULT_IMAGE_NAME: ${{ github.repository }} - DOCKERFILE: ./docker/Dockerfile - BUILD_CONTEXT: . - CACHE_SCOPE: docker-image - STABLE_FLOATING_TAG: latest - PRERELEASE_FLOATING_TAG: beta - PRERELEASE_PATTERN: (alpha|beta|rc) - permissions: contents: read packages: write @@ -33,116 +19,88 @@ permissions: jobs: build: name: Build (${{ matrix.arch }}) - runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - arch: amd64 platform: linux/amd64 + runner: ubuntu-24.04 - arch: arm64 platform: linux/arm64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} steps: - - name: Checkout + - name: Checkout code uses: actions/checkout@v4 with: - fetch-depth: 0 fetch-tags: true + fetch-depth: 0 persist-credentials: false - - name: Set build metadata + - name: Set image metadata shell: bash env: INPUT_VERSION: ${{ github.event.inputs.version }} - INPUT_IMAGE_NAME: ${{ github.event.inputs.image_name }} run: | - set -euo pipefail + POINTED_TAG="$(git tag --points-at HEAD --list 'v*' | sort -V | tail -n1)" + INPUT_VERSION="${INPUT_VERSION//[[:space:]]/}" - pointed_tag="$(git tag --points-at HEAD --list 'v*' | sort -V | tail -n1)" - input_version="${INPUT_VERSION//[[:space:]]/}" - image_name="${INPUT_IMAGE_NAME:-$DEFAULT_IMAGE_NAME}" - - if [[ "$GITHUB_REF" == refs/tags/* ]]; then - version="$GITHUB_REF_NAME" - elif [[ -n "$input_version" ]]; then - version="$input_version" - elif [[ -n "$pointed_tag" ]]; then - version="$pointed_tag" + echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV" + if [[ "${GITHUB_REF}" == refs/tags/* ]]; then + VERSION="${GITHUB_REF_NAME}" + elif [[ -n "$INPUT_VERSION" ]]; then + VERSION="$INPUT_VERSION" + elif [[ -n "$POINTED_TAG" ]]; then + VERSION="$POINTED_TAG" else - echo "workflow_dispatch requires a version input when HEAD is not tagged" >&2 + echo "workflow_dispatch requires an explicit version input when HEAD is not tagged" >&2 exit 1 fi - if [[ ! -f "$DOCKERFILE" ]]; then - echo "Dockerfile not found: $DOCKERFILE" >&2 - exit 1 - fi - - if [[ ! -d "$BUILD_CONTEXT" ]]; then - echo "Docker context not found: $BUILD_CONTEXT" >&2 - exit 1 - fi - - { - echo "IMAGE=${REGISTRY}/${image_name,,}" - echo "VERSION=$version" - echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" - echo "VCS_REF=$GITHUB_SHA" - } >> "$GITHUB_ENV" - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + echo "VERSION=$VERSION" >> "$GITHUB_ENV" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - - name: Log in to registry + - name: Log into registry uses: docker/login-action@v3 with: - registry: ${{ env.REGISTRY }} + registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push digest + - name: Build and push id: build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: - context: ${{ env.BUILD_CONTEXT }} - file: ${{ env.DOCKERFILE }} + context: . + file: ./docker/Dockerfile platforms: ${{ matrix.platform }} outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true build-args: | VERSION=${{ env.VERSION }} - BUILD_DATE=${{ env.BUILD_DATE }} - VCS_REF=${{ env.VCS_REF }} - labels: | - org.opencontainers.image.title=${{ github.event.repository.name }} - org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} - org.opencontainers.image.revision=${{ github.sha }} - org.opencontainers.image.version=${{ env.VERSION }} - org.opencontainers.image.created=${{ env.BUILD_DATE }} - cache-from: type=gha,scope=${{ env.CACHE_SCOPE }}-${{ matrix.arch }} - cache-to: type=gha,mode=max,ignore-error=true,timeout=20m,scope=${{ env.CACHE_SCOPE }}-${{ matrix.arch }} + cache-from: type=gha,scope=docker-server-${{ matrix.arch }} + cache-to: type=gha,mode=max,ignore-error=true,timeout=20m,scope=docker-server-${{ matrix.arch }} - name: Export digest shell: bash + run: | + mkdir -p /tmp/server-digests + touch "/tmp/server-digests/${DIGEST#sha256:}" env: DIGEST: ${{ steps.build.outputs.digest }} - run: | - set -euo pipefail - mkdir -p /tmp/image-digests - touch "/tmp/image-digests/${DIGEST#sha256:}" - name: Upload digest uses: actions/upload-artifact@v4 with: - name: image-digests-${{ matrix.arch }} - path: /tmp/image-digests/* + name: server-digests-${{ matrix.arch }} + path: /tmp/server-digests/* if-no-files-found: error retention-days: 1 - name: Generate artifact attestation - uses: actions/attest-build-provenance@v2 + uses: actions/attest-build-provenance@v3 with: subject-name: ${{ env.IMAGE }} subject-digest: ${{ steps.build.outputs.digest }} @@ -150,96 +108,79 @@ jobs: merge: name: Merge multi-arch manifest - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 needs: build steps: - - name: Checkout + - name: Checkout code uses: actions/checkout@v4 with: - fetch-depth: 0 fetch-tags: true + fetch-depth: 0 persist-credentials: false - - name: Set build metadata + - name: Set image metadata shell: bash env: INPUT_VERSION: ${{ github.event.inputs.version }} - INPUT_IMAGE_NAME: ${{ github.event.inputs.image_name }} run: | - set -euo pipefail + POINTED_TAG="$(git tag --points-at HEAD --list 'v*' | sort -V | tail -n1)" + INPUT_VERSION="${INPUT_VERSION//[[:space:]]/}" - pointed_tag="$(git tag --points-at HEAD --list 'v*' | sort -V | tail -n1)" - input_version="${INPUT_VERSION//[[:space:]]/}" - image_name="${INPUT_IMAGE_NAME:-$DEFAULT_IMAGE_NAME}" - - if [[ "$GITHUB_REF" == refs/tags/* ]]; then - version="$GITHUB_REF_NAME" - elif [[ -n "$input_version" ]]; then - version="$input_version" - elif [[ -n "$pointed_tag" ]]; then - version="$pointed_tag" + echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV" + if [[ "${GITHUB_REF}" == refs/tags/* ]]; then + VERSION="${GITHUB_REF_NAME}" + elif [[ -n "$INPUT_VERSION" ]]; then + VERSION="$INPUT_VERSION" + elif [[ -n "$POINTED_TAG" ]]; then + VERSION="$POINTED_TAG" else - echo "workflow_dispatch requires a version input when HEAD is not tagged" >&2 + echo "workflow_dispatch requires an explicit version input when HEAD is not tagged" >&2 exit 1 fi - if [[ ! -f "$DOCKERFILE" ]]; then - echo "Dockerfile not found: $DOCKERFILE" >&2 - exit 1 - fi - - if [[ ! -d "$BUILD_CONTEXT" ]]; then - echo "Docker context not found: $BUILD_CONTEXT" >&2 - exit 1 - fi - - { - echo "IMAGE=${REGISTRY}/${image_name,,}" - echo "VERSION=$version" - } >> "$GITHUB_ENV" + echo "VERSION=$VERSION" >> "$GITHUB_ENV" - name: Download digests uses: actions/download-artifact@v4 with: - path: /tmp/image-digests - pattern: image-digests-* + path: /tmp/server-digests + pattern: server-digests-* merge-multiple: true - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - - name: Log in to registry + - name: Log into registry uses: docker/login-action@v3 with: - registry: ${{ env.REGISTRY }} + registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Create and push manifest list - working-directory: /tmp/image-digests + working-directory: /tmp/server-digests shell: bash run: | - set -euo pipefail shopt -s nullglob - references=() for digest in *; do references+=("${IMAGE}@sha256:${digest}") done - if [[ ${#references[@]} -eq 0 ]]; then - echo "No digests found in /tmp/image-digests" >&2 + if [ ${#references[@]} -eq 0 ]; then + echo "No digests found in /tmp/server-digests" >&2 exit 1 fi - floating_tag="$STABLE_FLOATING_TAG" - if [[ "$VERSION" =~ $PRERELEASE_PATTERN ]]; then - floating_tag="$PRERELEASE_FLOATING_TAG" + if [[ "${VERSION}" =~ (alpha|beta|rc) ]]; then + FLOATING_TAG="beta" + else + FLOATING_TAG="latest" fi docker buildx imagetools create \ -t "${IMAGE}:${VERSION}" \ - -t "${IMAGE}:${floating_tag}" \ + -t "${IMAGE}:${FLOATING_TAG}" \ "${references[@]}" - name: Inspect image diff --git a/internal/cmd/all.go b/internal/cmd/all.go new file mode 100644 index 00000000..dcabf51d --- /dev/null +++ b/internal/cmd/all.go @@ -0,0 +1,68 @@ +/* +Copyright 2025 linux.do +Modified by Arctel.net, 2026 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "log" + "sync" + + "github.com/Rain-kl/Wavelet/internal/router" + "github.com/Rain-kl/Wavelet/internal/task/scheduler" + "github.com/Rain-kl/Wavelet/internal/task/worker" + "github.com/spf13/cobra" +) + +var allCmd = &cobra.Command{ + Use: "all", + Short: "以融合模式同时启动 API、Worker 和 Scheduler", + Run: func(cmd *cobra.Command, args []string) { + log.Println("[All] 融合模式启动") + + var wg sync.WaitGroup + + // 启动 API HTTP 服务 + wg.Add(1) + go func() { + defer wg.Done() + log.Println("[All] 启动 API 服务") + router.Serve() + }() + + // 启动 Asynq Worker 任务处理服务 + wg.Add(1) + go func() { + defer wg.Done() + log.Println("[All] 启动 Worker 服务") + if err := worker.StartWorker(); err != nil { + log.Printf("[All] Worker 启动失败: %v\n", err) + } + }() + + // 启动 Asynq 定时任务调度器 + wg.Add(1) + go func() { + defer wg.Done() + log.Println("[All] 启动 Scheduler 服务") + if err := scheduler.StartScheduler(); err != nil { + log.Printf("[All] Scheduler 启动失败: %v\n", err) + } + }() + + wg.Wait() + }, +} diff --git a/internal/cmd/root.go b/internal/cmd/root.go index 5908872c..69586a80 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -30,8 +30,10 @@ var rootCmd = &cobra.Command{ migrator.Migrate() }, Run: func(cmd *cobra.Command, args []string) { + // 无参数时默认以融合模式启动所有服务 if len(args) == 0 { - log.Fatalf("[CMD] please provide a command\n") + allCmd.Run(allCmd, args) + return } appMode := args[0] switch appMode { @@ -41,6 +43,8 @@ var rootCmd = &cobra.Command{ schedulerCmd.Run(schedulerCmd, args) case "worker": workerCmd.Run(workerCmd, args) + case "all": + allCmd.Run(allCmd, args) default: log.Fatal("[CMD] unknown app mode\n") }