Upload 4 files
Browse files- Dockerfile +63 -0
- README.md +112 -0
- entrypoint.sh +55 -0
- nginx.conf +41 -0
Dockerfile
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# syntax=docker/dockerfile:1
|
| 2 |
+
#
|
| 3 |
+
# DeepSeek Harness (dsh) on a Hugging Face Docker Space.
|
| 4 |
+
#
|
| 5 |
+
# Persistent user data:
|
| 6 |
+
# Hugging Face Storage Bucket -> /home/user
|
| 7 |
+
#
|
| 8 |
+
# DSH itself and nginx live in the image; DSH's user data, settings, credentials,
|
| 9 |
+
# sessions, plugins, auth state, and user-level caches live under /home/user.
|
| 10 |
+
|
| 11 |
+
FROM node:24-bookworm-slim
|
| 12 |
+
|
| 13 |
+
# Always build the current DSH release.
|
| 14 |
+
ARG DSH_VERSION=latest
|
| 15 |
+
ARG MOBILE_CSS_PLUGIN="dsh-mobile-css@latest"
|
| 16 |
+
ARG REMOTE_AUTH_PLUGIN="dsh-web-startup-auth@latest"
|
| 17 |
+
|
| 18 |
+
ENV HOME=/home/user
|
| 19 |
+
ENV DSH_HOME=/home/user/.dsh
|
| 20 |
+
|
| 21 |
+
# Keep user-level configuration/cache/package-manager state inside the persistent bucket.
|
| 22 |
+
ENV XDG_CONFIG_HOME=/home/user/.config
|
| 23 |
+
ENV XDG_DATA_HOME=/home/user/.local/share
|
| 24 |
+
ENV XDG_STATE_HOME=/home/user/.local/state
|
| 25 |
+
ENV XDG_CACHE_HOME=/home/user/.cache
|
| 26 |
+
ENV NPM_CONFIG_CACHE=/home/user/.cache/npm
|
| 27 |
+
ENV PNPM_HOME=/home/user/.local/share/pnpm
|
| 28 |
+
|
| 29 |
+
ENV MOBILE_CSS_PLUGIN=${MOBILE_CSS_PLUGIN}
|
| 30 |
+
ENV REMOTE_AUTH_PLUGIN=${REMOTE_AUTH_PLUGIN}
|
| 31 |
+
|
| 32 |
+
# DSH requires Node >=22.19 or >=24; Node 24 is the current LTS line.
|
| 33 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 34 |
+
nginx-light \
|
| 35 |
+
curl \
|
| 36 |
+
ca-certificates \
|
| 37 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 38 |
+
|
| 39 |
+
# Install DSH globally outside /home/user so the image contains the runtime.
|
| 40 |
+
ENV NPM_CONFIG_PREFIX=/opt/npm-global
|
| 41 |
+
ENV PATH=/opt/npm-global/bin:/home/user/.local/share/pnpm:$PATH
|
| 42 |
+
# DSH's plugin manager is a thin wrapper around pnpm, so pnpm must be
|
| 43 |
+
# available on PATH at runtime. Install both globally in the image; the
|
| 44 |
+
# actual DSH profile/plugin state remains under the persistent /home/user.
|
| 45 |
+
RUN npm install -g pnpm@latest @deepseek-ai/dsh@${DSH_VERSION}
|
| 46 |
+
|
| 47 |
+
# Hugging Face Docker Spaces convention: uid 1000, home = /home/user.
|
| 48 |
+
RUN if getent passwd 1000 >/dev/null; then userdel -r "$(getent passwd 1000 | cut -d: -f1)"; fi \
|
| 49 |
+
&& useradd -m -u 1000 user
|
| 50 |
+
|
| 51 |
+
WORKDIR /app
|
| 52 |
+
|
| 53 |
+
COPY --chown=user:user nginx.conf /etc/nginx/nginx.conf
|
| 54 |
+
RUN mkdir -p /var/lib/nginx /var/log/nginx \
|
| 55 |
+
&& chown -R user:user /var/lib/nginx /var/log/nginx /etc/nginx
|
| 56 |
+
|
| 57 |
+
COPY --chown=user:user entrypoint.sh /app/entrypoint.sh
|
| 58 |
+
RUN chmod +x /app/entrypoint.sh
|
| 59 |
+
|
| 60 |
+
USER user
|
| 61 |
+
|
| 62 |
+
EXPOSE 7860
|
| 63 |
+
CMD ["/app/entrypoint.sh"]
|
README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: DeepSeek Harness
|
| 3 |
+
emoji: 🐳
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: gray
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# DeepSeek Harness (dsh) on a Hugging Face Space
|
| 11 |
+
|
| 12 |
+
This Space runs the latest DeepSeek Harness behind nginx with persistent Hugging
|
| 13 |
+
Face Storage and the `dsh-web-startup-auth` remote-access/auth plugin.
|
| 14 |
+
|
| 15 |
+
## Storage: persist the whole user home
|
| 16 |
+
|
| 17 |
+
Attach a Hugging Face Storage Bucket to this Space and mount it at:
|
| 18 |
+
|
| 19 |
+
`/home/user`
|
| 20 |
+
|
| 21 |
+
Use read-write access.
|
| 22 |
+
|
| 23 |
+
This persists the entire user home, including:
|
| 24 |
+
|
| 25 |
+
- `$DSH_HOME` (`/home/user/.dsh`)
|
| 26 |
+
- DSH settings and provider configuration
|
| 27 |
+
- DSH credentials stored by the app
|
| 28 |
+
- sessions and workspace/user data
|
| 29 |
+
- installed DSH profile plugins
|
| 30 |
+
- web-auth state (`web-auth.json`)
|
| 31 |
+
- user-level `.config`, `.local`, and `.cache` data
|
| 32 |
+
|
| 33 |
+
System software installed into the Docker image (`/usr`, `/opt/npm-global`,
|
| 34 |
+
nginx, Node, and the DSH runtime itself) remains part of the image and is
|
| 35 |
+
recreated from the Dockerfile on rebuild. That is intentional.
|
| 36 |
+
|
| 37 |
+
## Remote settings / Models fix
|
| 38 |
+
|
| 39 |
+
The stock DSH web startup keeps remote privileged configuration APIs loopback-only,
|
| 40 |
+
and the browser also disables settings on a non-loopback hostname. The current
|
| 41 |
+
deployment therefore installs `dsh-web-startup-auth@latest` into the web profile.
|
| 42 |
+
|
| 43 |
+
That plugin provides authenticated remote browser access, fixes the remote
|
| 44 |
+
browser settings gate, handles privileged loopback-authority APIs after
|
| 45 |
+
authentication, and supplies the browser crypto compatibility needed by remote
|
| 46 |
+
HTTP contexts.
|
| 47 |
+
|
| 48 |
+
The plugin is installed/updated from the npm `latest` tag at every container
|
| 49 |
+
start. If the registry is temporarily unavailable, an already-installed copy is
|
| 50 |
+
kept; a first boot fails rather than silently starting without the security and
|
| 51 |
+
settings layer.
|
| 52 |
+
|
| 53 |
+
See the plugin's current documentation for the authentication flow. On first
|
| 54 |
+
visit, use its registration/login page to create the administrator account.
|
| 55 |
+
The authentication state is stored under `/home/user/.dsh`, so it survives
|
| 56 |
+
restarts when the bucket is attached.
|
| 57 |
+
|
| 58 |
+
## Latest packages
|
| 59 |
+
|
| 60 |
+
The Docker build installs:
|
| 61 |
+
|
| 62 |
+
- `@deepseek-ai/dsh@latest`
|
| 63 |
+
- `dsh-mobile-css@latest`
|
| 64 |
+
- `dsh-web-startup-auth@latest`
|
| 65 |
+
|
| 66 |
+
The two web plugins are also refreshed on every boot so a persistent profile
|
| 67 |
+
doesn't permanently pin an old plugin version.
|
| 68 |
+
|
| 69 |
+
## Hugging Face visibility
|
| 70 |
+
|
| 71 |
+
Keep this Space **Private**. DSH has powerful filesystem/agent capabilities.
|
| 72 |
+
The authentication plugin is an additional application-level security layer,
|
| 73 |
+
not a reason to make a powerful agent public.
|
| 74 |
+
|
| 75 |
+
## Secrets
|
| 76 |
+
|
| 77 |
+
Put API keys in Hugging Face Space **Settings → Secrets**, not in this repo.
|
| 78 |
+
|
| 79 |
+
## Port
|
| 80 |
+
|
| 81 |
+
Hugging Face routes the Space to port `7860`. nginx listens on `7860` and
|
| 82 |
+
proxies to DSH on `0.0.0.0:3080` inside the container.
|
| 83 |
+
|
| 84 |
+
## Why nginx no longer rewrites Host/Origin
|
| 85 |
+
|
| 86 |
+
The earlier configuration rewrote `Host`/`Origin` to localhost. That could make
|
| 87 |
+
the server-side loopback check pass, but it could not change the browser's
|
| 88 |
+
actual `location.hostname`, which is what caused:
|
| 89 |
+
|
| 90 |
+
`settings are unavailable in this browser`
|
| 91 |
+
|
| 92 |
+
The remote-auth plugin is designed to solve both sides: the browser-side remote
|
| 93 |
+
settings gate and the authenticated server-side privileged API boundary. nginx
|
| 94 |
+
therefore preserves the HF authority and forwards the real request metadata.
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
## Plugin package manager
|
| 98 |
+
|
| 99 |
+
`dsh plugin --profile web ...` forwards plugin operations to pnpm. The image
|
| 100 |
+
therefore installs **pnpm@latest** globally as well as **@deepseek-ai/dsh@latest**.
|
| 101 |
+
The pnpm executable is outside `/home/user`; the profile, lockfile, installed
|
| 102 |
+
plugins, settings, auth state, credentials, sessions, and user-level caches are
|
| 103 |
+
inside `/home/user` and are persisted by the HF Storage Bucket.
|
| 104 |
+
|
| 105 |
+
On every container start the entrypoint runs:
|
| 106 |
+
|
| 107 |
+
- `dsh plugin --profile web add dsh-mobile-css@latest`
|
| 108 |
+
- `dsh plugin --profile web add dsh-web-startup-auth@latest`
|
| 109 |
+
|
| 110 |
+
A successful run updates the persistent profile to the current npm `latest`
|
| 111 |
+
versions. If npm is temporarily unreachable, an already-installed plugin is
|
| 112 |
+
kept so the Space can still start.
|
entrypoint.sh
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
|
| 4 |
+
# The HF Storage Bucket is mounted at /home/user. Keep all user-level state
|
| 5 |
+
# underneath it so DSH settings, credentials, sessions, plugins, auth state,
|
| 6 |
+
# and package-manager caches survive container restarts/rebuilds.
|
| 7 |
+
mkdir -p \
|
| 8 |
+
"$HOME/.dsh" \
|
| 9 |
+
"$HOME/.config" \
|
| 10 |
+
"$HOME/.local/share" \
|
| 11 |
+
"$HOME/.local/state" \
|
| 12 |
+
"$HOME/.cache/npm" \
|
| 13 |
+
"$HOME/.cache"
|
| 14 |
+
|
| 15 |
+
# dsh plugin is a pnpm forwarder. pnpm is installed globally in the image.
|
| 16 |
+
# Install/update both runtime UI plugins from the npm latest tag on every boot.
|
| 17 |
+
# If the registry is temporarily unavailable but an existing copy is present,
|
| 18 |
+
# keep the existing copy; otherwise fail rather than silently starting without
|
| 19 |
+
# the remote-settings/auth capability.
|
| 20 |
+
ensure_latest_plugin() {
|
| 21 |
+
local spec="$1"
|
| 22 |
+
local package_name="$2"
|
| 23 |
+
|
| 24 |
+
echo "[entrypoint] installing/updating ${spec}"
|
| 25 |
+
if dsh plugin --profile web add "${spec}"; then
|
| 26 |
+
echo "[entrypoint] ${package_name}: latest install/update succeeded"
|
| 27 |
+
return 0
|
| 28 |
+
fi
|
| 29 |
+
|
| 30 |
+
if [ -d "$DSH_HOME/profiles/web/node_modules/$package_name" ]; then
|
| 31 |
+
echo "[entrypoint] ${package_name}: registry update failed; keeping existing installed copy"
|
| 32 |
+
return 0
|
| 33 |
+
fi
|
| 34 |
+
|
| 35 |
+
echo "[entrypoint] ERROR: ${package_name} is not installed and could not be fetched"
|
| 36 |
+
exit 1
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
ensure_latest_plugin "${MOBILE_CSS_PLUGIN}" "dsh-mobile-css"
|
| 40 |
+
ensure_latest_plugin "${REMOTE_AUTH_PLUGIN}" "dsh-web-startup-auth"
|
| 41 |
+
|
| 42 |
+
# nginx is the HF-facing listener. DSH remains behind it.
|
| 43 |
+
nginx -g 'daemon off;' &
|
| 44 |
+
NGINX_PID=$!
|
| 45 |
+
trap 'kill "$NGINX_PID" 2>/dev/null || true' EXIT
|
| 46 |
+
|
| 47 |
+
# dsh-web-startup-auth replaces the stock startup guard and provides:
|
| 48 |
+
# - authenticated remote browser access
|
| 49 |
+
# - remote settings/provider/credentials support
|
| 50 |
+
# - crypto.randomUUID compatibility for non-loopback browser contexts
|
| 51 |
+
# - loopback-authority handling for privileged APIs after authentication
|
| 52 |
+
#
|
| 53 |
+
# Bind DSH to all interfaces inside the container; nginx is the only public
|
| 54 |
+
# HF-facing entry point. The auth plugin protects remote access.
|
| 55 |
+
exec dsh --profile web --host 0.0.0.0 --port 3080 --no-open
|
nginx.conf
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
worker_processes 1;
|
| 2 |
+
pid /tmp/nginx.pid;
|
| 3 |
+
|
| 4 |
+
events {
|
| 5 |
+
worker_connections 1024;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
http {
|
| 9 |
+
include /etc/nginx/mime.types;
|
| 10 |
+
default_type application/octet-stream;
|
| 11 |
+
|
| 12 |
+
access_log /var/log/nginx/access.log;
|
| 13 |
+
error_log /var/log/nginx/error.log;
|
| 14 |
+
|
| 15 |
+
server {
|
| 16 |
+
listen 0.0.0.0:7860;
|
| 17 |
+
|
| 18 |
+
# DSH supports large file/workspace uploads.
|
| 19 |
+
client_max_body_size 160M;
|
| 20 |
+
|
| 21 |
+
location / {
|
| 22 |
+
proxy_pass http://127.0.0.1:3080;
|
| 23 |
+
|
| 24 |
+
proxy_http_version 1.1;
|
| 25 |
+
proxy_set_header Upgrade $http_upgrade;
|
| 26 |
+
proxy_set_header Connection "upgrade";
|
| 27 |
+
|
| 28 |
+
# Preserve the real HF authority. dsh-web-startup-auth performs
|
| 29 |
+
# the authenticated loopback-authority rewrite itself after login.
|
| 30 |
+
proxy_set_header Host $host;
|
| 31 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 32 |
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
| 33 |
+
proxy_set_header X-Forwarded-Proto $scheme;
|
| 34 |
+
|
| 35 |
+
# Required for live token/event streaming and terminal/WebSocket traffic.
|
| 36 |
+
proxy_read_timeout 3600s;
|
| 37 |
+
proxy_send_timeout 3600s;
|
| 38 |
+
proxy_buffering off;
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
}
|