From e7fde9fad76ebe2f2b313ed7847de4c5a61354ce Mon Sep 17 00:00:00 2001 From: okxlin <61420215+okxlin@users.noreply.github.com> Date: Sun, 16 Aug 2026 01:21:13 +0800 Subject: [PATCH] Tag DeepSeek Harness images by DSH version --- .../build-deepseek-harness-workstation.yml | 43 +++++- .github/workflows/build-deepseek-harness.yml | 43 +++++- README.md | 10 +- README_EN.md | 10 +- deepseek-harness-builder/README.md | 28 ++-- deepseek-harness-builder/image/Dockerfile | 16 ++- .../image/scripts/entrypoint.sh | 11 ++ .../scripts/build-local.sh | 126 ++++++++++++++++++ .../scripts/prepare-dsh-version.sh | 102 ++++++++++++++ .../scripts/smoke-test.sh | 18 ++- 10 files changed, 367 insertions(+), 40 deletions(-) create mode 100755 deepseek-harness-builder/scripts/build-local.sh create mode 100755 deepseek-harness-builder/scripts/prepare-dsh-version.sh diff --git a/.github/workflows/build-deepseek-harness-workstation.yml b/.github/workflows/build-deepseek-harness-workstation.yml index 66a62a4..af9a9a6 100644 --- a/.github/workflows/build-deepseek-harness-workstation.yml +++ b/.github/workflows/build-deepseek-harness-workstation.yml @@ -4,7 +4,11 @@ on: workflow_dispatch: inputs: image_tag: - description: "Workstation tag or tag base; -workstation is appended when absent" + description: "Workstation tag or tag base; leave empty to use the resolved @deepseek-ai/dsh version" + required: false + default: "" + dsh_version: + description: "DeepSeek Harness npm version or dist-tag; leave empty to resolve latest" required: false default: "" platforms: @@ -58,6 +62,25 @@ jobs: - name: Checkout repository uses: actions/checkout@v7 + - name: Set up Node.js + uses: actions/setup-node@v7 + with: + node-version: "24.18.0" + + - name: Prepare DeepSeek Harness version + id: dsh + env: + INPUT_DSH_VERSION: ${{ inputs.dsh_version }} + run: | + set -euo pipefail + args=() + if [[ -n "${INPUT_DSH_VERSION}" ]]; then + args+=(--version "${INPUT_DSH_VERSION}") + fi + deepseek-harness-builder/scripts/prepare-dsh-version.sh \ + "${args[@]}" \ + --github-output "$GITHUB_OUTPUT" + - name: Validate Docker Hub credentials env: DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} @@ -81,17 +104,17 @@ jobs: id: release env: EVENT_NAME: ${{ github.event_name }} + DSH_VERSION: ${{ steps.dsh.outputs.dsh_version }} INPUT_IMAGE_TAG: ${{ inputs.image_tag }} INPUT_PUSH_LATEST: ${{ inputs.push_latest }} run: | set -euo pipefail - date_tag="$(date -u +%Y%m%d)" if [[ "${EVENT_NAME}" == "schedule" ]]; then - image_tag="${date_tag}-workstation" + image_tag="${DSH_VERSION}-workstation" push_latest="true" else - image_tag="${INPUT_IMAGE_TAG:-${date_tag}-workstation}" + image_tag="${INPUT_IMAGE_TAG:-${DSH_VERSION}-workstation}" if [[ "${image_tag}" != *-workstation ]]; then image_tag="${image_tag}-workstation" fi @@ -99,7 +122,7 @@ jobs: fi { - echo "date_tag=${date_tag}" + echo "dsh_version=${DSH_VERSION}" echo "image_tag=${image_tag}" echo "push_latest=${push_latest}" } >> "$GITHUB_OUTPUT" @@ -163,6 +186,8 @@ jobs: platforms: linux/amd64 load: true tags: ${{ env.AUDIT_IMAGE }} + build-args: | + DSH_VERSION=${{ steps.release.outputs.dsh_version }} cache-from: type=gha,scope=deepseek-harness-workstation cache-to: type=gha,mode=min,scope=deepseek-harness-workstation @@ -182,6 +207,8 @@ jobs: load: true tags: ${{ env.TEST_IMAGE_AMD64 }} labels: ${{ steps.meta.outputs.labels }} + build-args: | + DSH_VERSION=${{ steps.release.outputs.dsh_version }} cache-from: type=gha,scope=deepseek-harness-workstation cache-to: type=gha,mode=min,scope=deepseek-harness-workstation @@ -225,6 +252,8 @@ jobs: load: true tags: ${{ env.TEST_IMAGE_ARM64 }} labels: ${{ steps.meta.outputs.labels }} + build-args: | + DSH_VERSION=${{ steps.release.outputs.dsh_version }} cache-from: type=gha,scope=deepseek-harness-workstation cache-to: type=gha,mode=min,scope=deepseek-harness-workstation @@ -281,6 +310,8 @@ jobs: ${{ steps.release.outputs.push_latest == 'true' && format('ghcr.io/{0}/{1}:{2}', steps.owner.outputs.lower_owner, steps.params.outputs.image_repo, steps.params.outputs.latest_tag) || '' }} ${{ steps.release.outputs.push_latest == 'true' && format('docker.io/{0}/{1}:{2}', env.DOCKERHUB_NAMESPACE, steps.params.outputs.image_repo, steps.params.outputs.latest_tag) || '' }} labels: ${{ steps.meta.outputs.labels }} + build-args: | + DSH_VERSION=${{ steps.release.outputs.dsh_version }} cache-from: type=gha,scope=deepseek-harness-workstation cache-to: type=gha,mode=min,scope=deepseek-harness-workstation @@ -288,6 +319,7 @@ jobs: env: IMAGE_NAME: ghcr.io/${{ steps.owner.outputs.lower_owner }}/${{ steps.params.outputs.image_repo }} IMAGE_TAG: ${{ steps.params.outputs.image_tag }} + DSH_VERSION: ${{ steps.release.outputs.dsh_version }} LATEST_TAG: ${{ steps.params.outputs.latest_tag }} PARAM_PLATFORMS: ${{ steps.params.outputs.platforms }} PUSH_LATEST: ${{ steps.release.outputs.push_latest }} @@ -296,6 +328,7 @@ jobs: echo "## DeepSeek Harness Workstation image build" echo "" echo "- Image: ${IMAGE_NAME}" + echo "- DeepSeek Harness: ${DSH_VERSION}" echo "- Published image tag: ${IMAGE_TAG}" echo "- Floating tag: ${LATEST_TAG} (published: ${PUSH_LATEST})" echo "- Registries: GitHub Container Registry and Docker Hub" diff --git a/.github/workflows/build-deepseek-harness.yml b/.github/workflows/build-deepseek-harness.yml index 5942d08..b653a04 100644 --- a/.github/workflows/build-deepseek-harness.yml +++ b/.github/workflows/build-deepseek-harness.yml @@ -4,7 +4,11 @@ on: workflow_dispatch: inputs: image_tag: - description: "Published image tag; leave empty to use UTC date tag YYYYMMDD" + description: "Published image tag; leave empty to use the resolved @deepseek-ai/dsh version" + required: false + default: "" + dsh_version: + description: "DeepSeek Harness npm version or dist-tag; leave empty to resolve latest" required: false default: "" platforms: @@ -58,6 +62,25 @@ jobs: - name: Checkout repository uses: actions/checkout@v7 + - name: Set up Node.js + uses: actions/setup-node@v7 + with: + node-version: "24.18.0" + + - name: Prepare DeepSeek Harness version + id: dsh + env: + INPUT_DSH_VERSION: ${{ inputs.dsh_version }} + run: | + set -euo pipefail + args=() + if [[ -n "${INPUT_DSH_VERSION}" ]]; then + args+=(--version "${INPUT_DSH_VERSION}") + fi + deepseek-harness-builder/scripts/prepare-dsh-version.sh \ + "${args[@]}" \ + --github-output "$GITHUB_OUTPUT" + - name: Validate Docker Hub credentials env: DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} @@ -81,22 +104,22 @@ jobs: id: release env: EVENT_NAME: ${{ github.event_name }} + DSH_VERSION: ${{ steps.dsh.outputs.dsh_version }} INPUT_IMAGE_TAG: ${{ inputs.image_tag }} INPUT_PUSH_LATEST: ${{ inputs.push_latest }} run: | set -euo pipefail - date_tag="$(date -u +%Y%m%d)" if [[ "${EVENT_NAME}" == "schedule" ]]; then - image_tag="${date_tag}" + image_tag="${DSH_VERSION}" push_latest="true" else - image_tag="${INPUT_IMAGE_TAG:-${date_tag}}" + image_tag="${INPUT_IMAGE_TAG:-${DSH_VERSION}}" push_latest="${INPUT_PUSH_LATEST:-true}" fi { - echo "date_tag=${date_tag}" + echo "dsh_version=${DSH_VERSION}" echo "image_tag=${image_tag}" echo "push_latest=${push_latest}" } >> "$GITHUB_OUTPUT" @@ -157,6 +180,8 @@ jobs: platforms: linux/amd64 load: true tags: ${{ env.AUDIT_IMAGE }} + build-args: | + DSH_VERSION=${{ steps.release.outputs.dsh_version }} cache-from: type=gha,scope=deepseek-harness cache-to: type=gha,mode=min,scope=deepseek-harness @@ -176,6 +201,8 @@ jobs: load: true tags: ${{ env.TEST_IMAGE_AMD64 }} labels: ${{ steps.meta.outputs.labels }} + build-args: | + DSH_VERSION=${{ steps.release.outputs.dsh_version }} cache-from: type=gha,scope=deepseek-harness cache-to: type=gha,mode=min,scope=deepseek-harness @@ -213,6 +240,8 @@ jobs: load: true tags: ${{ env.TEST_IMAGE_ARM64 }} labels: ${{ steps.meta.outputs.labels }} + build-args: | + DSH_VERSION=${{ steps.release.outputs.dsh_version }} cache-from: type=gha,scope=deepseek-harness cache-to: type=gha,mode=min,scope=deepseek-harness @@ -262,6 +291,8 @@ jobs: ${{ steps.release.outputs.push_latest == 'true' && format('ghcr.io/{0}/{1}:{2}', steps.owner.outputs.lower_owner, steps.params.outputs.image_repo, steps.params.outputs.latest_tag) || '' }} ${{ steps.release.outputs.push_latest == 'true' && format('docker.io/{0}/{1}:{2}', env.DOCKERHUB_NAMESPACE, steps.params.outputs.image_repo, steps.params.outputs.latest_tag) || '' }} labels: ${{ steps.meta.outputs.labels }} + build-args: | + DSH_VERSION=${{ steps.release.outputs.dsh_version }} cache-from: type=gha,scope=deepseek-harness cache-to: type=gha,mode=min,scope=deepseek-harness @@ -269,6 +300,7 @@ jobs: env: IMAGE_NAME: ghcr.io/${{ steps.owner.outputs.lower_owner }}/${{ steps.params.outputs.image_repo }} IMAGE_TAG: ${{ steps.params.outputs.image_tag }} + DSH_VERSION: ${{ steps.release.outputs.dsh_version }} LATEST_TAG: ${{ steps.params.outputs.latest_tag }} PARAM_PLATFORMS: ${{ steps.params.outputs.platforms }} PUSH_LATEST: ${{ steps.release.outputs.push_latest }} @@ -277,6 +309,7 @@ jobs: echo "## DeepSeek Harness image build" echo "" echo "- Image: ${IMAGE_NAME}" + echo "- DeepSeek Harness: ${DSH_VERSION}" echo "- Published image tag: ${IMAGE_TAG}" echo "- Floating tag: ${LATEST_TAG} (published: ${PUSH_LATEST})" echo "- Registries: GitHub Container Registry and Docker Hub" diff --git a/README.md b/README.md index ed00102..dae14c4 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ docker pull ghcr.io/okxlin/openclaw-sandbox:latest gh workflow run build-deepseek-harness.yml \ --repo okxlin/release-factory \ --ref main \ - -f image_tag=20260815 \ + -f dsh_version=0.1.0-rc.6 \ -f platforms=linux/amd64,linux/arm64 \ -f push_latest=true @@ -52,16 +52,18 @@ gh workflow run build-1panel.yml \ -f version=v2.1.3 ``` +DeepSeek Harness 的 `image_tag` 默认跟随 `dsh_version`;留空时会使用当前解析到的 npm 版本。 + DeepSeek Harness 同时发布到 GHCR 和 Docker Hub。运行这两个工作流前,需要配置仓库变量或 Secret `DOCKERHUB_USERNAME`,以及 Secret `DOCKERHUB_TOKEN`。Token 只用于 Registry 登录,不会传入镜像构建上下文。 ## 标签与发布策略 -| 项目 | 主要版本/日期标签 | 可选浮动标签 | +| 项目 | 主要版本标签 | 可选浮动标签 | | --- | --- | --- | | 1Panel | Release:`1panel--`;文件:`1panel---.tar.gz` | 不适用 | | Codex Claude Workstation | UTC 日期 `YYYYMMDD` | `latest`,手动触发默认启用,定时任务始终更新 | -| DeepSeek Harness Runtime | UTC 日期 `YYYYMMDD` | `latest`,手动触发默认启用,定时任务始终更新 | -| DeepSeek Harness Workstation | `YYYYMMDD-workstation`;手动标签缺少后缀时自动补全 | `workstation`,手动触发默认启用,定时任务始终更新 | +| DeepSeek Harness Runtime | 解析到的 `@deepseek-ai/dsh` 版本 `` | `latest`,手动触发默认启用,定时任务始终更新 | +| DeepSeek Harness Workstation | `-workstation`;手动标签缺少后缀时自动补全 | `workstation`,手动触发默认启用,定时任务始终更新 | | Gemini Skill Browser(Kasm) | `-kasm` | `latest-kasm`,仅显式启用时发布 | | Gemini Skill Browser(LinuxServer) | `-linuxserver` | `latest-linuxserver`,仅显式启用时发布 | | OpenCode Workstation | `latest` 或手动指定标签 | 可显式附带 `latest` | diff --git a/README_EN.md b/README_EN.md index d999b9a..6ed8cdf 100644 --- a/README_EN.md +++ b/README_EN.md @@ -42,7 +42,7 @@ Choose a workflow on the [Actions](https://github.com/okxlin/release-factory/act gh workflow run build-deepseek-harness.yml \ --repo okxlin/release-factory \ --ref main \ - -f image_tag=20260815 \ + -f dsh_version=0.1.0-rc.6 \ -f platforms=linux/amd64,linux/arm64 \ -f push_latest=true @@ -52,16 +52,18 @@ gh workflow run build-1panel.yml \ -f version=v2.1.3 ``` +DeepSeek Harness `image_tag` follows `dsh_version` by default; leave it empty to use the currently resolved npm version. + DeepSeek Harness publishes to both GHCR and Docker Hub. Before running either DeepSeek workflow, configure `DOCKERHUB_USERNAME` as a repository variable or secret and `DOCKERHUB_TOKEN` as a repository secret. The token is used only for registry login and is not passed to the image build context. ## Tags and Publication Policy -| Project | Primary version/date tag | Optional floating tag | +| Project | Primary version tag | Optional floating tag | | --- | --- | --- | | 1Panel | Release: `1panel--`; file: `1panel---.tar.gz` | Not applicable | | Codex Claude Workstation | UTC date `YYYYMMDD` | `latest`; enabled by default for manual runs and always updated by scheduled runs | -| DeepSeek Harness Runtime | UTC date `YYYYMMDD` | `latest`; enabled by default for manual runs and always updated by scheduled runs | -| DeepSeek Harness Workstation | `YYYYMMDD-workstation`; the suffix is appended when a manual tag omits it | `workstation`; enabled by default for manual runs and always updated by scheduled runs | +| DeepSeek Harness Runtime | Resolved `@deepseek-ai/dsh` version (``) | `latest`; enabled by default for manual runs and always updated by scheduled runs | +| DeepSeek Harness Workstation | `-workstation`; the suffix is appended when a manual tag omits it | `workstation`; enabled by default for manual runs and always updated by scheduled runs | | Gemini Skill Browser (Kasm) | `-kasm` | `latest-kasm`, published only when explicitly enabled | | Gemini Skill Browser (LinuxServer) | `-linuxserver` | `latest-linuxserver`, published only when explicitly enabled | | OpenCode Workstation | `latest` or a manually supplied tag | Can explicitly add the `latest` alias | diff --git a/deepseek-harness-builder/README.md b/deepseek-harness-builder/README.md index 9d4f5f4..a148a1c 100644 --- a/deepseek-harness-builder/README.md +++ b/deepseek-harness-builder/README.md @@ -6,14 +6,14 @@ One Dockerfile produces two independently tested variants under one image reposi | Floating tag | Fixed tag pattern | Docker target | Intended use | | --- | --- | --- | --- | -| `latest` | `YYYYMMDD` or `` | `runtime` | Lightweight 1Panel service with essential shell and repository tools. This remains the default final target. | -| `workstation` | `YYYYMMDD-workstation` or `-workstation` | `workstation` | Full interactive development environment with compiler and language toolchains. | +| `latest` | `` | `runtime` | Lightweight 1Panel service with essential shell and repository tools. This remains the default final target. | +| `workstation` | `-workstation` | `workstation` | Full interactive development environment with compiler and language toolchains. | Both workflows publish the same tags to `ghcr.io/okxlin/deepseek-harness` and to `docker.io/$DOCKERHUB_USERNAME/deepseek-harness`. Configure `DOCKERHUB_USERNAME` as a GitHub Actions repository variable or secret and configure `DOCKERHUB_TOKEN` as a repository secret. The Docker Hub token is used only by the registry login action and is never passed to the image build. -Scheduled runs publish UTC date tags. Manual workflow runs can publish the pinned DeepSeek Harness version instead, for example `0.1.0-rc.6-workstation`. Use a floating tag for an AppStore `latest` channel and the matching version tag for a numbered AppStore version. +Scheduled runs resolve the current npm `@deepseek-ai/dsh` version, update the image build context, and publish matching tags such as `0.1.0-rc.6` and `0.1.0-rc.6-workstation`. Manual workflow runs can still override the DSH version or the published tag. Use a floating tag for an AppStore `latest` channel and the matching version tag for a numbered AppStore version. -Pinned runtime versions: +Checked-in baseline versions: - DeepSeek Harness `0.1.0-rc.6` - Node.js `24.18.0` @@ -49,18 +49,20 @@ Only Caddy listens on the container interface. The DSH port is not exposed to si ## Build ```bash -# Default/lightweight image. --target runtime is explicit for release parity. -docker build --target runtime \ - -t deepseek-harness:local \ - deepseek-harness-builder/image +# Default/lightweight image. Resolves npm @deepseek-ai/dsh@latest by default. +deepseek-harness-builder/scripts/build-local.sh \ + --target runtime \ + --tag deepseek-harness:local # Full development workstation. -docker build --target workstation \ - -t deepseek-harness-workstation:local \ - deepseek-harness-builder/image +deepseek-harness-builder/scripts/build-local.sh \ + --target workstation \ + --tag deepseek-harness-workstation:local ``` -The production dependency closure is pinned by `pnpm-lock.yaml`. pnpm lifecycle scripts are fail-closed and limited to the reviewed packages in `pnpm-workspace.yaml`. +The local build helper resolves the requested `@deepseek-ai/dsh` npm version, updates `package.json` and `pnpm-lock.yaml` in a temporary build context, and passes the resolved version as Docker `DSH_VERSION`. Use `--version 0.1.0-rc.6` or another npm dist-tag to build a specific DSH release. Direct `docker build` remains supported for the checked-in baseline context, and derives `DSH_VERSION` from `package.json` when no build argument is supplied. + +The production dependency closure is pinned by `pnpm-lock.yaml` in the active build context. pnpm lifecycle scripts are fail-closed and limited to the reviewed packages in `pnpm-workspace.yaml`. The custom Caddy build verifies the go-authcrunch source archive checksum, removes its unused GPG public-key parser, and runs the upstream identity-package tests before linking. SSH public-key support remains available to caddy-security, while the generated `CADDY_GO_PACKAGES.txt` manifest must contain no `golang.org/x/crypto/openpgp` package. The build also raises `grpc`, `klauspost/compress`, and `x/text` to their fixed versions. @@ -393,4 +395,4 @@ Both arm64 CI lanes use QEMU to prove the native `node-pty` build, Caddy plugin ## Upgrade behavior -Caddy and caddy-security are compiled together and pinned. Updating Caddy alone is not assumed safe. Scheduled workflows publish the `runtime` target as `latest` plus a UTC date tag and the `workstation` target as `workstation` plus a `YYYYMMDD-workstation` tag. Manual runs may replace the date component with the pinned DSH version, yielding fixed tags such as `` and `-workstation`. Each workflow pushes its verified multi-platform manifest to both GHCR and Docker Hub only after auditing the frozen pnpm production tree, rebuilding and validating the plugin, running the Caddy dependency-graph/govulncheck gate, executing its amd64 and arm64 smoke contracts, and applying zero-fixable HIGH/CRITICAL Trivy gates to both architectures. If either registry login or publication fails, the workflow fails instead of reporting a complete release. +Caddy and caddy-security are compiled together and pinned. Updating Caddy alone is not assumed safe. Scheduled workflows resolve npm `@deepseek-ai/dsh@latest`, temporarily update `package.json` and `pnpm-lock.yaml` in the build workspace, pass the resolved version as the Docker `DSH_VERSION` build argument, and publish the `runtime` target as `latest` plus `` and the `workstation` target as `workstation` plus `-workstation`. Manual runs may override either the DSH package version or the final image tag while retaining the same validation and optional floating tag behavior. Each workflow pushes its verified multi-platform manifest to both GHCR and Docker Hub only after auditing the frozen pnpm production tree, rebuilding and validating the plugin, running the Caddy dependency-graph/govulncheck gate, executing its amd64 and arm64 smoke contracts, and applying zero-fixable HIGH/CRITICAL Trivy gates to both architectures. If either registry login or publication fails, the workflow fails instead of reporting a complete release. diff --git a/deepseek-harness-builder/image/Dockerfile b/deepseek-harness-builder/image/Dockerfile index a132dfc..ab492b5 100644 --- a/deepseek-harness-builder/image/Dockerfile +++ b/deepseek-harness-builder/image/Dockerfile @@ -69,7 +69,7 @@ RUN mkdir -p /licenses \ FROM node:24.18.0-bookworm-slim@sha256:6f7b03f7c2c8e2e784dcf9295400527b9b1270fd37b7e9a7285cf83b6951452d AS dsh-deps -ARG DSH_VERSION=0.1.0-rc.6 +ARG DSH_VERSION= ARG PNPM_VERSION=11.21.0 RUN apt-get update \ @@ -84,9 +84,10 @@ COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ RUN corepack enable \ && corepack prepare "pnpm@${PNPM_VERSION}" --activate \ - && test "$(node -p "require('./package.json').dependencies['@deepseek-ai/dsh']")" = "${DSH_VERSION}" \ + && resolved_dsh_version="${DSH_VERSION:-$(node -p "require('./package.json').dependencies['@deepseek-ai/dsh']")}" \ + && test "$(node -p "require('./package.json').dependencies['@deepseek-ai/dsh']")" = "${resolved_dsh_version}" \ && pnpm install --prod --frozen-lockfile --trust-lockfile \ - && pnpm exec dsh --version | grep -Fx "${DSH_VERSION}" \ + && pnpm exec dsh --version | grep -Fx "${resolved_dsh_version}" \ && find node_modules/.pnpm \ -path '*/node_modules/node-pty/build/Release/pty.node' \ -type f -print -quit | grep -q . @@ -217,7 +218,7 @@ FROM node:24.18.0-bookworm-slim@sha256:6f7b03f7c2c8e2e784dcf9295400527b9b1270fd3 ARG CADDY_VERSION=2.11.4 ARG CADDY_SECURITY_VERSION=1.1.64 -ARG DSH_VERSION=0.1.0-rc.6 +ARG DSH_VERSION= ARG PNPM_VERSION=11.21.0 ENV AUTH_MODE=caddy-security \ @@ -229,7 +230,6 @@ ENV AUTH_MODE=caddy-security \ DSH_HOME=/data/dsh \ DSH_INTERNAL_PORT=3080 \ DSH_TELEMETRY_DISABLED=1 \ - DSH_VERSION=${DSH_VERSION} \ DSH_WORKSPACE=/workspace \ GOMEMLIMIT=128MiB \ GOMAXPROCS=2 \ @@ -274,12 +274,16 @@ RUN install -d -m 0750 -o node -g node \ && chown -R node:node /home/node \ && ln -s /opt/pnpm/bin/pnpm.mjs /usr/local/bin/pnpm \ && ln -s /opt/pnpm/bin/pnpx.mjs /usr/local/bin/pnpx \ + && resolved_dsh_version="${DSH_VERSION:-$(node -p "require('/opt/dsh/package.json').dependencies['@deepseek-ai/dsh']")}" \ + && printf 'DSH_VERSION=%s\n' "${resolved_dsh_version}" > /etc/deepseek-harness-version \ && caddy version | grep -F "v${CADDY_VERSION}" \ && caddy list-modules | grep -Fx 'security' \ - && dsh --version | grep -Fx "${DSH_VERSION}" \ + && dsh --version | grep -Fx "${resolved_dsh_version}" \ && node --version | grep -Fx 'v24.18.0' \ && pnpm --version | grep -Fx "${PNPM_VERSION}" +ENV DSH_VERSION_FILE=/etc/deepseek-harness-version + WORKDIR /workspace EXPOSE 8080 diff --git a/deepseek-harness-builder/image/scripts/entrypoint.sh b/deepseek-harness-builder/image/scripts/entrypoint.sh index acc5202..c9dcbf8 100755 --- a/deepseek-harness-builder/image/scripts/entrypoint.sh +++ b/deepseek-harness-builder/image/scripts/entrypoint.sh @@ -9,10 +9,21 @@ AUTH_JWT_SECRET_PATH="${AUTH_STATE_DIR}/jwt-secret" CADDY_CONFIG_HOME="${CADDY_CONFIG_HOME:-/data/caddy/config}" CADDY_DATA_HOME="${CADDY_DATA_HOME:-/data/caddy/data}" DSH_HOME="${DSH_HOME:-/data/dsh}" +DSH_VERSION_FILE="${DSH_VERSION_FILE:-/etc/deepseek-harness-version}" DSH_WORKSPACE="${DSH_WORKSPACE:-/workspace}" CADDY_AUTH_CONFIG="/etc/caddy/Caddyfile" CADDY_PASSTHROUGH_CONFIG="/etc/caddy/Caddyfile.passthrough" +if [[ -z "${DSH_VERSION:-}" && -r "${DSH_VERSION_FILE}" ]]; then + while IFS='=' read -r key value; do + if [[ "${key}" == "DSH_VERSION" ]]; then + DSH_VERSION="${value}" + break + fi + done < "${DSH_VERSION_FILE}" +fi +export DSH_VERSION + log() { printf '[entrypoint] %s\n' "$*" } diff --git a/deepseek-harness-builder/scripts/build-local.sh b/deepseek-harness-builder/scripts/build-local.sh new file mode 100755 index 0000000..5e06a9d --- /dev/null +++ b/deepseek-harness-builder/scripts/build-local.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +builder_dir="$(cd -- "${script_dir}/.." && pwd)" +image_dir="${builder_dir}/image" +target="runtime" +tag="" +dsh_version="" +platform="" + +usage() { + cat <<'EOF' +Usage: build-local.sh [options] [-- DOCKER_BUILD_ARGS...] + +Options: + --target TARGET Docker target: runtime or workstation (default: runtime) + --tag TAG Local image tag; defaults by target + --version VERSION DeepSeek Harness npm version or dist-tag; defaults to latest + --platform PLATFORM Optional Docker build platform + -h, --help Show this help +EOF +} + +require_value() { + local option="$1" + local count="$2" + (( count >= 2 )) || { + printf 'ERROR: %s requires a value\n' "${option}" >&2 + exit 2 + } +} + +docker_args=() +while [[ $# -gt 0 ]]; do + case "$1" in + --target) + require_value "$1" "$#" + target="$2" + shift 2 + ;; + --tag) + require_value "$1" "$#" + tag="$2" + shift 2 + ;; + --version) + require_value "$1" "$#" + dsh_version="$2" + shift 2 + ;; + --platform) + require_value "$1" "$#" + platform="$2" + shift 2 + ;; + --) + shift + docker_args+=("$@") + break + ;; + -h|--help) + usage + exit 0 + ;; + *) + printf 'ERROR: unsupported argument: %s\n' "$1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +case "${target}" in + runtime|workstation) ;; + *) + printf 'ERROR: target must be runtime or workstation, got: %s\n' "${target}" >&2 + exit 2 + ;; +esac + +if [[ -z "${tag}" ]]; then + if [[ "${target}" == "workstation" ]]; then + tag="deepseek-harness-workstation:local" + else + tag="deepseek-harness:local" + fi +fi + +tmp_dir="$(mktemp -d /tmp/deepseek-harness-local-build.XXXXXX)" +cleanup() { + rm -rf -- "${tmp_dir}" +} +trap cleanup EXIT + +tmp_image_dir="${tmp_dir}/image" +mkdir -p -- "${tmp_image_dir}" +cp -a "${image_dir}/." "${tmp_image_dir}/" + +prepare_args=(--image-dir "${tmp_image_dir}") +if [[ -n "${dsh_version}" ]]; then + prepare_args+=(--version "${dsh_version}") +fi +version_output="${tmp_dir}/version-output" +"${script_dir}/prepare-dsh-version.sh" "${prepare_args[@]}" --github-output "${version_output}" +resolved_dsh_version="$(sed -n 's/^dsh_version=//p' "${version_output}" | tail -n 1)" +[[ -n "${resolved_dsh_version}" ]] || { + printf 'ERROR: failed to resolve DeepSeek Harness version\n' >&2 + exit 1 +} + +build_cmd=( + docker build + --target "${target}" + --build-arg "DSH_VERSION=${resolved_dsh_version}" + -t "${tag}" +) +if [[ -n "${platform}" ]]; then + build_cmd+=(--platform "${platform}") +fi +build_cmd+=("${docker_args[@]}" "${tmp_image_dir}") + +printf '[build-local] DeepSeek Harness: %s\n' "${resolved_dsh_version}" +printf '[build-local] Target: %s\n' "${target}" +printf '[build-local] Tag: %s\n' "${tag}" +"${build_cmd[@]}" diff --git a/deepseek-harness-builder/scripts/prepare-dsh-version.sh b/deepseek-harness-builder/scripts/prepare-dsh-version.sh new file mode 100755 index 0000000..354c05a --- /dev/null +++ b/deepseek-harness-builder/scripts/prepare-dsh-version.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +image_dir="$(cd -- "${script_dir}/../image" && pwd)" +dsh_version="" +github_output_path="${GITHUB_OUTPUT:-/dev/null}" + +usage() { + cat <<'EOF' +Usage: prepare-dsh-version.sh [options] + +Options: + --image-dir DIR Image build context directory + --version VERSION DeepSeek Harness version; defaults to npm latest + --github-output FILE GitHub Actions output file + -h, --help Show this help +EOF +} + +require_value() { + local option="$1" + local count="$2" + (( count >= 2 )) || { + printf 'ERROR: %s requires a value\n' "${option}" >&2 + exit 2 + } +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --image-dir) + require_value "$1" "$#" + image_dir="$(cd -- "$2" && pwd)" + shift 2 + ;; + --version) + require_value "$1" "$#" + dsh_version="$2" + shift 2 + ;; + --github-output) + require_value "$1" "$#" + github_output_path="$2" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + printf 'ERROR: unsupported argument: %s\n' "$1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +if [[ -z "${dsh_version}" ]]; then + dsh_version="$(npm view @deepseek-ai/dsh version)" +else + dsh_version="$(npm view "@deepseek-ai/dsh@${dsh_version}" version)" +fi + +if [[ ! "${dsh_version}" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$ ]]; then + printf 'ERROR: invalid DeepSeek Harness version for an OCI tag: %s\n' "${dsh_version}" >&2 + exit 1 +fi + +node - "${image_dir}/package.json" "${dsh_version}" <<'NODE' +const fs = require('fs'); + +const packagePath = process.argv[2]; +const dshVersion = process.argv[3]; +const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8')); + +pkg.version = dshVersion; +pkg.dependencies = pkg.dependencies || {}; +pkg.dependencies['@deepseek-ai/dsh'] = dshVersion; + +fs.writeFileSync(packagePath, `${JSON.stringify(pkg, null, 2)}\n`); +NODE + +( + cd -- "${image_dir}" + corepack enable + package_manager="$(node -p "require('./package.json').packageManager")" + corepack prepare "${package_manager}" --activate + pnpm install --lockfile-only --ignore-scripts +) + +resolved_version="$(cd -- "${image_dir}" && node -p "require('./package.json').dependencies['@deepseek-ai/dsh']")" +if [[ "${resolved_version}" != "${dsh_version}" ]]; then + printf 'ERROR: package.json resolved %s, expected %s\n' "${resolved_version}" "${dsh_version}" >&2 + exit 1 +fi + +if [[ -n "${github_output_path}" && "${github_output_path}" != "/dev/null" ]]; then + printf 'dsh_version=%s\n' "${dsh_version}" >> "${github_output_path}" +fi + +printf 'dsh_version=%s\n' "${dsh_version}" diff --git a/deepseek-harness-builder/scripts/smoke-test.sh b/deepseek-harness-builder/scripts/smoke-test.sh index e56e840..d3aeb7f 100755 --- a/deepseek-harness-builder/scripts/smoke-test.sh +++ b/deepseek-harness-builder/scripts/smoke-test.sh @@ -8,6 +8,7 @@ VARIANT="${SMOKE_VARIANT:-runtime}" MAX_IDLE_MEMORY_MIB="${SMOKE_MAX_IDLE_MEMORY_MIB:-256}" MAX_IDLE_PIDS="${SMOKE_MAX_IDLE_PIDS:-64}" TOKEN_LIFETIME="${SMOKE_TOKEN_LIFETIME:-2592000}" +EXPECTED_DSH_VERSION="${SMOKE_EXPECTED_DSH_VERSION:-}" usage() { cat <<'EOF' @@ -24,6 +25,7 @@ Environment overrides: SMOKE_MAX_IDLE_MEMORY_MIB Full-profile idle memory ceiling (default: 256) SMOKE_MAX_IDLE_PIDS Full-profile idle PID ceiling (default: 64) SMOKE_TOKEN_LIFETIME Login lifetime exercised by the smoke test (default: 2592000) + SMOKE_EXPECTED_DSH_VERSION Expected DSH version; defaults to image DSH_VERSION EOF } @@ -581,9 +583,19 @@ check_runtime_versions() { || fail "runtime architecture ${runtime_arch} does not match image architecture ${image_arch}" pass "image architecture ${image_arch} runs as Node.js ${runtime_arch}" - [[ "$(docker exec "${container_name}" dsh --version)" == "0.1.0-rc.6" ]] \ - || fail "DeepSeek Harness version is not pinned to 0.1.0-rc.6" - pass "DeepSeek Harness version is pinned" + if [[ -z "${EXPECTED_DSH_VERSION}" ]]; then + EXPECTED_DSH_VERSION="$(docker exec "${container_name}" sh -c ' + if [ -n "${DSH_VERSION:-}" ]; then + printf %s "${DSH_VERSION}" + elif [ -r "${DSH_VERSION_FILE:-/etc/deepseek-harness-version}" ]; then + sed -n "s/^DSH_VERSION=//p" "${DSH_VERSION_FILE:-/etc/deepseek-harness-version}" | head -n 1 + fi + ')" + fi + [[ -n "${EXPECTED_DSH_VERSION}" ]] || fail "DeepSeek Harness expected version is empty" + [[ "$(docker exec "${container_name}" dsh --version)" == "${EXPECTED_DSH_VERSION}" ]] \ + || fail "DeepSeek Harness version is not ${EXPECTED_DSH_VERSION}" + pass "DeepSeek Harness version matches ${EXPECTED_DSH_VERSION}" [[ "$(docker exec "${container_name}" node --version)" == "v24.18.0" ]] \ || fail "Node.js version is not pinned to v24.18.0"