commit f3d43120892d5e98981a49102d1041131288cb4c Author: Nicolás Georger Date: Wed Oct 21 00:39:54 2026 -0300 Initial commit Signed-off-by: Nicolás Georger diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1301e01 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,149 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.* +!.env.example + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist +.output + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp directory +.temp + +# Sveltekit cache directory +.svelte-kit/ + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# Firebase cache directory +.firebase/ + +# TernJS port file +.tern-port + +# Stores Visual Studio Code versions used for testing Visual Studio Code extensions +.vscode-test + +# pnpm +.pnpm-store + +# yarn v3 +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Vite files +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +.vite/ + +# Astro generated types +.astro/ + +# Turbo Repo +.turbo diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..136846d --- /dev/null +++ b/.env.sample @@ -0,0 +1,3 @@ +DEEPSEEK_API_KEY= +DSH_VERSION=dsh-v0.2.1-rc.2 +PNPM_VERSION=11.28.5 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bb6f512 --- /dev/null +++ b/.gitignore @@ -0,0 +1,151 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.* +!.env.example + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist +.output + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp directory +.temp + +# Sveltekit cache directory +.svelte-kit/ + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# Firebase cache directory +.firebase/ + +# TernJS port file +.tern-port + +# Stores Visual Studio Code versions used for testing Visual Studio Code extensions +.vscode-test + +# pnpm +.pnpm-store + +# yarn v3 +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Vite files +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +.vite/ + +# Astro generated types +.astro/ + +# Turbo Repo +.turbo + +!.env.sample diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0fdba00 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,107 @@ +# syntax=docker/dockerfile:1.7 + +ARG NODE_BUILD_IMAGE=docker.io/node:24-trixie +ARG NODE_RUNTIME_IMAGE=docker.io/node:24-trixie-slim + +# --------------------------------------------------------------------------- +# Build stage +# --------------------------------------------------------------------------- +FROM ${NODE_BUILD_IMAGE} AS build + +ARG DSH_REPO=https://github.com/deepseek-ai/deepseek-harness.git +ARG DSH_VERSION=dsh-v0.2.1-rc.2 +ARG PNPM_VERSION=11.28.5 + +ENV CI=true \ + NODE_OPTIONS=--max-old-space-size=4096 + +RUN corepack enable \ + && corepack prepare "pnpm@${PNPM_VERSION}" --activate + +WORKDIR /src + +RUN git clone --depth 1 --branch "${DSH_VERSION}" "${DSH_REPO}" . + +RUN pnpm install --frozen-lockfile + +RUN pnpm run build + +# ── Package the CLI with pnpm pack ─────────────────────────────────────── +RUN mkdir -p /opt/dsh \ + && pnpm --filter @deepseek-ai/dsh pack \ + --pack-destination /opt \ + --config.ignore-scripts=true \ + && mv /opt/deepseek-ai-dsh-*.tgz /opt/dsh.tgz \ + && tar -xzf /opt/dsh.tgz -C /opt/dsh --strip-components=1 \ + && rm /opt/dsh.tgz + +# ── Production install with npm ────────────────────────────────────────── +RUN cd /opt/dsh \ + && npm install --omit=dev --no-audit --no-fund \ + && test -f lib/bin.js \ + && node lib/bin.js web --help >/dev/null + +# --------------------------------------------------------------------------- +# Runtime stage +# --------------------------------------------------------------------------- +FROM ${NODE_RUNTIME_IMAGE} AS runtime + +ARG DSH_VERSION + +LABEL org.opencontainers.image.title="DeepSeek Harness Web" \ + org.opencontainers.image.version="${DSH_VERSION}" \ + org.opencontainers.image.source="https://github.com/deepseek-ai/deepseek-harness" + +ENV NODE_ENV=production \ + HOME=/home/node \ + DSH_HOME=/home/node/.dsh \ + DSH_PORT=3080 \ + PATH="/app/node_modules/.bin:${PATH}" + +# git/ripgrep are useful for DSH agent tools; tini handles signals. +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + git \ + ripgrep \ + tini \ + && rm -rf /var/lib/apt/lists/* \ + && install -d -o node -g node -m 0755 /workspace "${DSH_HOME}" + +COPY --from=build --chown=node:node /opt/dsh /app + +COPY <<'EOF' /usr/local/bin/dsh-entrypoint +#!/bin/sh +set -eu + +: "${DSH_PORT:=3080}" +: "${DSH_HOST:=}" +DSH_PUBLIC_URL="${DSH_PUBLIC_URL-http://localhost:${DSH_PORT}}" + +# DSH rejects wildcard hosts. Pick the container/pod's first non-loopback IPv4. +if [ -z "$DSH_HOST" ]; then + DSH_HOST="$(node -e 'const os=require("os"); const ifaces=Object.values(os.networkInterfaces()).flat(); const addr=ifaces.find(v=>v&&v.family==="IPv4"&&!v.internal); if(!addr){process.exit(1)} process.stdout.write(addr.address)')" +fi + +if [ -n "$DSH_PUBLIC_URL" ]; then + set -- --public-url "$DSH_PUBLIC_URL" "$@" +fi + +exec node /app/lib/bin.js web \ + --no-open \ + --host "$DSH_HOST" \ + --port "$DSH_PORT" \ + --trusted-host localhost \ + --trusted-host 127.0.0.1 \ + "$@" +EOF + +RUN chmod 0755 /usr/local/bin/dsh-entrypoint + +WORKDIR /workspace +USER node + +EXPOSE 3080 +VOLUME ["/home/node/.dsh"] +STOPSIGNAL SIGTERM +ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/dsh-entrypoint"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9cde3c2 --- /dev/null +++ b/README.md @@ -0,0 +1,151 @@ +Here is the complete updated Dockerfile incorporating the hoisted deploy fix and the dependency patch. The key changes versus the last version are in the deploy block: `--legacy` is dropped, `node-linker=hoisted` and `inject-workspace-packages` are enabled, and the missing workspace peer is added via the `workspace:*` protocol. + +```dockerfile +# syntax=docker/dockerfile:1.7 + +ARG NODE_BUILD_IMAGE=docker.io/node:24-trixie +ARG NODE_RUNTIME_IMAGE=docker.io/node:trixie-slim + +# --------------------------------------------------------------------------- +# Build stage +# --------------------------------------------------------------------------- +FROM ${NODE_BUILD_IMAGE} AS build + +ARG DSH_REPO=https://github.com/deepseek-ai/deepseek-harness.git +ARG DSH_VERSION=dsh-v0.2.1-rc.2 +ARG PNPM_VERSION=11.28.5 + +# CI=true skips the repository's lefthook postinstall in the container. +ENV CI=true \ + NODE_OPTIONS=--max-old-space-size=4096 + +RUN corepack enable \ + && corepack prepare "pnpm@${PNPM_VERSION}" --activate + +WORKDIR /src + +# Exact upstream tag, e.g. dsh-v0.2.1-rc.2. +RUN git clone --depth 1 --branch "${DSH_VERSION}" "${DSH_REPO}" . + +RUN pnpm install --frozen-lockfile + +# ── Workaround for upstream packaging defect ────────────────────────────── +# dsh-app-boot statically imports @deepseek-ai/cordis-plugin-group, which is +# only a peer/workspace dependency and is omitted by `pnpm deploy --prod`. +# Promote it to a real dependency of @deepseek-ai/dsh so it is always part of +# the deploy closure. +RUN pnpm --filter @deepseek-ai/dsh add @deepseek-ai/cordis-plugin-group@workspace:* +# ────────────────────────────────────────────────────────────────────────── + +# Builds native addon, Host/Client bundles, and the Web frontend. +RUN pnpm run build + +# ── Production deploy closure ──────────────────────────────────────────── +# A hoisted (npm-style) node_modules is required: the default isolated pnpm +# layout drops peer dependencies such as @deepseek-ai/cosmokit from the +# deployed tree, causing ERR_MODULE_NOT_FOUND at runtime. `--legacy` is +# intentionally NOT used, so pnpm's modern deploy binds peers correctly. +RUN pnpm --config.node-linker=hoisted \ + --config.inject-workspace-packages=true \ + deploy --prod /opt/dsh \ + && test -f /opt/dsh/lib/bin.js \ + && test -d /opt/dsh/node_modules/@deepseek-ai/cosmokit \ + && test -d /opt/dsh/node_modules/@deepseek-ai/cordis-plugin-group \ + && node /opt/dsh/lib/bin.js web --help >/dev/null +# ────────────────────────────────────────────────────────────────────────── + +# --------------------------------------------------------------------------- +# Runtime stage +# --------------------------------------------------------------------------- +FROM ${NODE_RUNTIME_IMAGE} AS runtime + +ARG DSH_VERSION + +LABEL org.opencontainers.image.title="DeepSeek Harness Web" \ + org.opencontainers.image.version="${DSH_VERSION}" \ + org.opencontainers.image.source="https://github.com/deepseek-ai/deepseek-harness" + +ENV NODE_ENV=production \ + HOME=/home/node \ + DSH_HOME=/home/node/.dsh \ + DSH_PORT=3080 \ + PATH="/app/node_modules/.bin:${PATH}" + +# git/ripgrep are useful for DSH agent tools; tini handles signals. +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + ca-certificates \ + git \ + ripgrep \ + tini \ + && rm -rf /var/lib/apt/lists/* \ + && install -d -o node -g node -m 0755 /workspace "${DSH_HOME}" + +COPY --from=build --chown=node:node /opt/dsh /app + +COPY <<'EOF' /usr/local/bin/dsh-entrypoint +#!/bin/sh +set -eu + +: "${DSH_PORT:=3080}" +: "${DSH_HOST:=}" +DSH_PUBLIC_URL="${DSH_PUBLIC_URL-http://localhost:${DSH_PORT}}" + +# DSH rejects wildcard hosts. Pick the container/pod's first non-loopback IPv4. +if [ -z "$DSH_HOST" ]; then + DSH_HOST="$(node -e 'const os=require("os"); const ifaces=Object.values(os.networkInterfaces()).flat(); const addr=ifaces.find(v=>v&&v.family==="IPv4"&&!v.internal); if(!addr){process.exit(1)} process.stdout.write(addr.address)')" +fi + +if [ -n "$DSH_PUBLIC_URL" ]; then + set -- --public-url "$DSH_PUBLIC_URL" "$@" +fi + +exec node /app/lib/bin.js web \ + --no-open \ + --host "$DSH_HOST" \ + --port "$DSH_PORT" \ + --trusted-host localhost \ + --trusted-host 127.0.0.1 \ + "$@" +EOF + +RUN chmod 0755 /usr/local/bin/dsh-entrypoint + +WORKDIR /workspace +USER node + +EXPOSE 3080 +VOLUME ["/home/node/.dsh"] +STOPSIGNAL SIGTERM +ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/dsh-entrypoint"] +``` + +### What changed and why + +| Area | Before | Now | Reason | +| ------ | -------- | ----- | -------- | +| Deploy linker | default isolated `.pnpm` store | `--config.node-linker=hoisted` | Produces a flat npm-style tree so peer deps like `@deepseek-ai/cosmokit` are physically present. | +| Deploy flag | `--legacy` | removed | Legacy deployer does not reliably bind peer dependencies; the modern deployer does. | +| Workspace binding | implicit | `--config.inject-workspace-packages=true` | Guarantees workspace packages and their peers are bound into the closure deterministically. | +| Missing peer patch | `@deepseek-ai/cordis-plugin-group@^1.0.1` | `@deepseek-ai/cordis-plugin-group@workspace:*` | Correct protocol for a monorepo-internal package; no dependency on a published npm version. | +| Verification | only `bin.js` exists + `web --help` | also asserts `cosmokit` and `cordis-plugin-group` directories exist | Fails the build early if the closure is still incomplete, instead of failing at container start. | + +### Build and run + +```bash +docker build \ + --build-arg DSH_VERSION=dsh-v0.2.1-rc.2 \ + -t dsh-web:0.2.1-rc.2 . + +docker run --rm -it \ + -p 3080:3080 \ + -v dsh-home:/home/node/.dsh \ + -e DEEPSEEK_API_KEY \ + dsh-web:0.2.1-rc.2 +``` + +Then open the `dsh web:` URL printed in the logs (it carries a per-process access token). + +### If the `pnpm add …@workspace:*` step ever fails + +That would mean `@deepseek-ai/cordis-plugin-group` is not a workspace package in the chosen tag. In that case remove the two `pnpm --filter … add` lines; the hoisted linker alone is usually sufficient to include peer dependencies. You can confirm by checking that the `test -d /opt/dsh/node_modules/@deepseek-ai/cordis-plugin-group` assertion still passes. diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..02678d4 --- /dev/null +++ b/build.sh @@ -0,0 +1,9 @@ +docker build \ + --build-arg DSH_VERSION=dsh-v0.2.1-rc.2 \ + -t dsh-web:0.2.1-rc.2 . + +docker run --rm -it \ + -p 3080:3080 \ + -v dsh-home:/home/node/.dsh \ + -e DEEPSEEK_API_KEY \ + dsh-web:0.2.1-rc.2 \ No newline at end of file