name: Publish Container Images on: workflow_run: workflows: - Publish Supported Platform Target types: - completed workflow_dispatch: inputs: source_commit: description: Source commit to build required: true default: main type: string channel_commit: description: release-channel ref to search for the matching signed target required: false default: release-channel type: string concurrency: group: production-container-images cancel-in-progress: false env: DOCKERHUB_IMAGE: szcq/deepseek-harness GHCR_IMAGE: ghcr.io/${{ github.repository }} jobs: build: name: Verify, test, and publish images if: >- (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main') || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest timeout-minutes: 90 environment: production-image permissions: contents: read packages: write steps: - name: Checkout reviewed source uses: actions/checkout@v6 with: ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || inputs.source_commit }} path: source - name: Checkout signed channel history uses: actions/checkout@v6 with: ref: ${{ github.event_name == 'workflow_dispatch' && inputs.channel_commit || 'release-channel' }} fetch-depth: 0 path: channel - name: Resolve immutable signed channel state id: channel run: | source_commit="$(git -C source rev-parse HEAD)" channel_commit= while IFS= read -r candidate; do candidate_source="$(git -C channel show "${candidate}:channel.json" | jq -er .sourceCommit)" if test "$candidate_source" = "$source_commit"; then channel_commit="$candidate" break fi done < <(git -C channel log --format=%H -- channel.json) if test -z "$channel_commit"; then echo "No signed release-channel target matches source commit $source_commit" >&2 exit 1 fi git -C channel checkout --detach "$channel_commit" { printf 'commit=%s\n' "$channel_commit" printf 'source_commit=%s\n' "$source_commit" } >> "$GITHUB_OUTPUT" # shellcheck disable=SC2016 printf 'Using signed channel commit `%s` for source commit `%s`.\n' \ "$channel_commit" "$source_commit" >> "$GITHUB_STEP_SUMMARY" - name: Set up Node.js uses: actions/setup-node@v6 with: node-version: 24 cache: npm cache-dependency-path: | source/container/control-plane/services/management/package-lock.json source/container/environment/resources/plugins/platform-management/package/package-lock.json - name: Install Management runtime dependencies working-directory: source run: | npm ci --omit=dev --ignore-scripts --no-audit --no-fund --prefix container/control-plane/services/management npm ci --omit=dev --ignore-scripts --no-audit --no-fund --legacy-peer-deps --prefix container/environment/resources/plugins/platform-management/package - name: Verify gateway and Compose configuration working-directory: source run: | npm test --prefix container/control-plane/services/gateway npm test --prefix container/platform node container/test/compose-config.mjs sh -n container/test/container-smoke.sh sh -n container/test/devtools-smoke.sh - name: Verify signed Supported Target id: signed-target working-directory: source env: RECOVERY_ROOT_PUBLIC_KEY: ${{ secrets.DSH_RECOVERY_ROOT_PUBLIC_KEY }} run: | test -n "$RECOVERY_ROOT_PUBLIC_KEY" source_commit="$(git rev-parse HEAD)" test "$(jq -er .sourceCommit ../channel/channel.json)" = "$source_commit" sequence="$(jq -er .targetSequence ../channel/stable.json)" test "$(jq -er .targetSequence ../channel/channel.json)" = "$sequence" release_dir="../channel/targets/$sequence" test -d "$release_dir" for name in stable.json stable.sig.json keyring.json keyring.sig.json; do cmp "../channel/$name" "$release_dir/$name" done image_input=container/platform/image-input mkdir -p "$image_input/release" cp -a "$release_dir/." "$image_input/release/" printf '%s\n' "$RECOVERY_ROOT_PUBLIC_KEY" > "$image_input/recovery-root.spki.base64" cp release/supported-target.json "$image_input/supported-target.json" cp container/environment/definition.json "$image_input/environment-definition.json" version="$(jq -er '.latestSupportedDsh' release/supported-target.json)" npm_input="$RUNNER_TEMP/image-npm-input" mkdir -p "$npm_input" npm pack "@deepseek-ai/dsh@${version}" --pack-destination "$npm_input" >/dev/null tarball="$(find "$npm_input" -maxdepth 1 -type f -name '*.tgz' -print -quit)" test -n "$tarball" cp "$tarball" "$image_input/dsh.tgz" verified="$(node container/platform/tools/verify-image-release.mjs \ "$image_input/release" "$image_input/recovery-root.spki.base64" "$image_input/dsh.tgz" \ "$image_input/supported-target.json" "$image_input/environment-definition.json")" test "$(jq -er .dshVersion <<<"$verified")" = "$version" test "$(jq -er .targetSequence <<<"$verified")" = "$sequence" environment_version="$(jq -er .environmentVersion <<<"$verified")" test "$environment_version" = "$(jq -er .version container/environment/definition.json)" { printf 'version=%s\n' "$version" printf 'environment_version=%s\n' "$environment_version" printf 'sequence=%s\n' "$sequence" } >> "$GITHUB_OUTPUT" cp "$image_input/recovery-root.spki.base64" container/platform/seed/trust/recovery-root.spki.base64 cp "$release_dir/keyring.json" container/platform/seed/trust/keyring.json cp "$release_dir/keyring.sig.json" container/platform/seed/trust/keyring.sig.json rm container/platform/seed/trust/DEVELOPMENT_FIXTURE test ! -e container/platform/seed/trust/DEVELOPMENT_FIXTURE printf 'Building signed DSH %s with Environment %s (sequence %s).\n' \ "$version" "$environment_version" "$sequence" >> "$GITHUB_STEP_SUMMARY" - name: Generate registry tags id: tags env: DSH_VERSION: ${{ steps.signed-target.outputs.version }} ENVIRONMENT_VERSION: ${{ steps.signed-target.outputs.environment_version }} run: | [[ "$ENVIRONMENT_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]] major="${BASH_REMATCH[1]}" minor="${BASH_REMATCH[2]}" { echo 'standard<> "$GITHUB_OUTPUT" - name: Set up QEMU uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 - name: Build standard smoke-test image uses: docker/build-push-action@v7 with: context: ./source file: ./source/Dockerfile platforms: linux/amd64 load: true provenance: false build-args: | SIGNED_IMAGE_INPUT=true DSH_VERSION=${{ steps.signed-target.outputs.version }} ENVIRONMENT_VERSION=${{ steps.signed-target.outputs.environment_version }} TARGET_SEQUENCE=${{ steps.signed-target.outputs.sequence }} PLATFORM_REVISION=${{ steps.channel.outputs.source_commit }} tags: dsh-gateway-smoke:${{ github.run_id }} cache-from: type=gha,scope=standard cache-to: type=gha,mode=max,scope=standard - name: Run standard container smoke checks working-directory: source run: container/test/container-smoke.sh "dsh-gateway-smoke:${{ github.run_id }}" - name: Build devtools smoke-test image uses: docker/build-push-action@v7 with: context: ./source file: ./source/Dockerfile platforms: linux/amd64 load: true provenance: false build-args: | SIGNED_IMAGE_INPUT=true DSH_VERSION=${{ steps.signed-target.outputs.version }} ENVIRONMENT_VERSION=${{ steps.signed-target.outputs.environment_version }} TARGET_SEQUENCE=${{ steps.signed-target.outputs.sequence }} PLATFORM_REVISION=${{ steps.channel.outputs.source_commit }} INSTALL_DEVTOOLS=true tags: dsh-gateway-devtools-smoke:${{ github.run_id }} cache-from: type=gha,scope=devtools cache-to: type=gha,mode=max,scope=devtools - name: Run devtools smoke checks working-directory: source run: container/test/devtools-smoke.sh "dsh-gateway-devtools-smoke:${{ github.run_id }}" - name: Log in to Docker Hub uses: docker/login-action@v4 with: username: szcq password: ${{ secrets.DOCKER_TOKEN }} - name: Log in to GitHub Container Registry uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ github.token }} - name: Push versioned devtools image to Docker Hub uses: docker/build-push-action@v7 with: context: ./source file: ./source/Dockerfile platforms: linux/amd64,linux/arm64 push: true provenance: false build-args: | SIGNED_IMAGE_INPUT=true DSH_VERSION=${{ steps.signed-target.outputs.version }} ENVIRONMENT_VERSION=${{ steps.signed-target.outputs.environment_version }} TARGET_SEQUENCE=${{ steps.signed-target.outputs.sequence }} PLATFORM_REVISION=${{ steps.channel.outputs.source_commit }} INSTALL_DEVTOOLS=true tags: ${{ steps.tags.outputs.devtools }} cache-from: type=gha,scope=devtools cache-to: type=gha,mode=max,scope=devtools - name: Push versioned standard image to Docker Hub and GHCR uses: docker/build-push-action@v7 with: context: ./source file: ./source/Dockerfile platforms: linux/amd64,linux/arm64 push: true provenance: false build-args: | SIGNED_IMAGE_INPUT=true DSH_VERSION=${{ steps.signed-target.outputs.version }} ENVIRONMENT_VERSION=${{ steps.signed-target.outputs.environment_version }} TARGET_SEQUENCE=${{ steps.signed-target.outputs.sequence }} PLATFORM_REVISION=${{ steps.channel.outputs.source_commit }} tags: ${{ steps.tags.outputs.standard }} cache-from: type=gha,scope=standard cache-to: type=gha,mode=max,scope=standard - name: Promote Docker Hub rolling tags env: DSH_VERSION: ${{ steps.signed-target.outputs.version }} run: | docker buildx imagetools create \ --tag "${DOCKERHUB_IMAGE}:latest-devtools" \ "${DOCKERHUB_IMAGE}:${DSH_VERSION}-devtools" docker buildx imagetools create \ --tag "${DOCKERHUB_IMAGE}:latest" \ "${DOCKERHUB_IMAGE}:${DSH_VERSION}" - name: Verify published registry manifests env: DSH_VERSION: ${{ steps.signed-target.outputs.version }} ENVIRONMENT_VERSION: ${{ steps.signed-target.outputs.environment_version }} run: | docker buildx imagetools inspect "${DOCKERHUB_IMAGE}:${DSH_VERSION}" docker buildx imagetools inspect "${DOCKERHUB_IMAGE}:${DSH_VERSION}-devtools" docker buildx imagetools inspect "${DOCKERHUB_IMAGE}:latest" docker buildx imagetools inspect "${DOCKERHUB_IMAGE}:latest-devtools" docker buildx imagetools inspect "${GHCR_IMAGE}:${ENVIRONMENT_VERSION}" docker buildx imagetools inspect "${GHCR_IMAGE}:dsh-${DSH_VERSION}" outputs: dsh_version: ${{ steps.signed-target.outputs.version }} environment_version: ${{ steps.signed-target.outputs.environment_version }} sequence: ${{ steps.signed-target.outputs.sequence }} notify-result: name: Notify container image publication result needs: build if: >- always() && (needs.build.result == 'success' || needs.build.result == 'failure') uses: yjrszcq/github-workflows/.github/workflows/gotify-notify.yml@c4c7fe9e17854cd4962913ac2792513eab0be988 with: title: >- ${{ needs.build.result == 'success' && format('DSH {0} 镜像已发布', needs.build.outputs.dsh_version) || 'DSH 镜像发布失败' }} message: >- ${{ needs.build.result == 'success' && format('DSH {0} 与 Environment {1} 的标准镜像已同步到 Docker Hub 和 GHCR,Devtools 镜像已发布到 Docker Hub;签名目标序列为 {2}。', needs.build.outputs.dsh_version, needs.build.outputs.environment_version, needs.build.outputs.sequence) || format('Docker Hub 或 GHCR 镜像的构建、测试、推送或验证失败。请查看 GitHub Actions:{0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id) }} priority: ${{ needs.build.result == 'failure' && 8 || 7 }} secrets: gotify_url: ${{ secrets.GOTIFY_URL }} gotify_token: ${{ secrets.GOTIFY_TOKEN }}