diff --git a/Dockerfile b/Dockerfile index 8af365b65c..803a144b05 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,10 +8,11 @@ # # No upstream code is patched. The bind address comes from the `--patch` # launcher layer (/opt/deepseek-harness/web.cordis.patch.yml), which the -# entrypoint renders onto the container's own IPv4 address at startup: dsh -# 0.2.1-alpha.2+ rejects the unspecified address in the webserver schema, so a -# static 0.0.0.0 would no longer boot. The UI is reachable only through ports -# you explicitly publish on the host. +# entrypoint renders at startup: dsh 0.2.1-alpha.2+ rejects the unspecified +# address in the webserver schema and gets the container's own IPv4 instead, +# while releases whose schema still only accepts 127.0.0.1 or 0.0.0.0 keep +# 0.0.0.0. The UI is reachable only through ports you explicitly publish on the +# host. FROM node:22-bookworm-slim diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 9589d243bf..9c33117246 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -38,22 +38,39 @@ else -*) set -- web --patch "$PATCH" --no-open "$@" ;; esac fi -# dsh 0.2.1-alpha.2 validates the webserver bind address in a config transform -# and rejects the unspecified address outright — "host … is an unspecified -# (wildcard) address, which is not supported … bind one concrete IPv4 or IPv6 -# address of a local interface instead" — and no launcher flag can opt out of a -# schema-level check. The shipped patch is therefore a template; render it onto -# this container's own address and hand dsh the rendered copy, so a port -# published on the host still reaches the UI while the socket binds one -# concrete interface instead of every interface. DSH_WEB_HOST overrides the -# discovered address. +# 0.2.1-alpha.2 replaced the webserver's old `host: "127.0.0.1" | "0.0.0.0"` +# union with a config transform that rejects the unspecified address outright — +# "host … is an unspecified (wildcard) address, which is not supported … bind +# one concrete IPv4 or IPv6 address of a local interface instead" — and a +# schema-level check cannot be opted out of from the launcher. The shipped patch +# is therefore a template: render it onto this container's own address and hand +# dsh the rendered copy, so a port published on the host still reaches the UI +# while the socket binds one concrete interface instead of every interface. +# Releases whose schema still accepts the wildcard keep 0.0.0.0, so old images +# rebuild exactly as before. DSH_WEB_HOST overrides the address. if [ -r "$PATCH" ]; then BIND_HOST="${DSH_WEB_HOST:-}" if [ -z "$BIND_HOST" ]; then - # Same discovery dsh's own boot smoke test uses: first non-internal IPv4. - BIND_HOST="$(node -e 'const os=require("os");const n=Object.values(os.networkInterfaces()).flat().find(i=>i&&i.family==="IPv4"&&!i.internal);process.stdout.write(n?n.address:"")' 2>/dev/null || true)" + # Detect the installed webserver schema rather than guessing from a + # version string. + dsh_root="${DSH_INSTALL_ROOT:-/usr/local/lib/node_modules/@deepseek-ai/dsh}" + ws="$dsh_root/node_modules/@deepseek-ai/dsh-host-webserver/lib/index.js" + if [ -f "$ws" ] && grep -qs 'unspecified (wildcard) address' "$ws"; then + wildcard_rejected=1 + elif [ ! -f "$ws" ] && grep -rqs 'unspecified (wildcard) address' "$dsh_root" 2>/dev/null; then + wildcard_rejected=1 + else + wildcard_rejected=0 + fi + + if [ "$wildcard_rejected" = 1 ]; then + # Same discovery dsh's own boot smoke test uses: first non-internal IPv4. + BIND_HOST="$(node -e 'const os=require("os");const n=Object.values(os.networkInterfaces()).flat().find(i=>i&&i.family==="IPv4"&&!i.internal);process.stdout.write(n?n.address:"")' 2>/dev/null || true)" + [ -n "$BIND_HOST" ] || BIND_HOST=127.0.0.1 + else + BIND_HOST=0.0.0.0 + fi fi - [ -n "$BIND_HOST" ] || BIND_HOST=127.0.0.1 render_patch() { sed -e "s|__DSH_BIND_HOST__|${BIND_HOST}|g" \ @@ -67,7 +84,7 @@ if [ -r "$PATCH" ]; then render_patch > "$RUNTIME_PATCH" fi chmod 0644 "$RUNTIME_PATCH" 2>/dev/null || true - log "webserver binds ${BIND_HOST} (dsh 0.2.1+ rejects the unspecified address); patch: ${RUNTIME_PATCH}" + log "webserver binds ${BIND_HOST}; runtime patch: ${RUNTIME_PATCH}" # Point every --patch argument that names the shipped template at the # rendered copy. Rebuilt with a rotating `set --`, so quoting survives. diff --git a/web.cordis.patch.yml b/web.cordis.patch.yml index 1bb525a0ef..46e8a64a91 100644 --- a/web.cordis.patch.yml +++ b/web.cordis.patch.yml @@ -1,16 +1,17 @@ # Launcher patch layer for containerized `dsh web` (passed via --patch). # -# The host is a placeholder, not a value: dsh 0.2.1-alpha.2 rejects the -# unspecified address in the webserver schema ("host … is an unspecified +# The host is a placeholder, not a value. From 0.2.1-alpha.2 the webserver +# schema rejects the unspecified address outright ("host … is an unspecified # (wildcard) address, which is not supported … bind one concrete IPv4 or IPv6 -# address of a local interface instead"), and that check is a config transform, -# so no launcher flag can opt out of it. A static 0.0.0.0 — which the earlier -# 0.1.x schema accepted — now fails the boot smoke test. +# address of a local interface instead") and that check is a config transform, +# so no launcher flag can opt out of it; a static 0.0.0.0 — which the earlier +# `"127.0.0.1" | "0.0.0.0"` schema accepted — now fails the boot smoke test. # -# The image entrypoint renders this file onto the container's own IPv4 address -# at startup and passes the rendered copy to dsh. A port published on the -# Docker host still reaches the UI, because the published port is forwarded to -# exactly that address. Set DSH_WEB_HOST to pin a different address. +# The image entrypoint renders this file at startup: onto the container's own +# IPv4 address when the installed schema rejects the wildcard, otherwise back +# to 0.0.0.0 so older images build unchanged. A port published on the Docker +# host still reaches the UI either way, because the published port is forwarded +# to exactly that address. Set DSH_WEB_HOST to pin a different address. - id: webserver config: host: __DSH_BIND_HOST__