Hemifield commited on
Commit
5d82fdf
·
verified ·
1 Parent(s): 7d4831b

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +65 -0
  2. README.md +123 -0
  3. entrypoint.sh +63 -0
  4. nginx.conf +41 -0
Dockerfile ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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. Community plugins below are fetched from
14
+ # their current GitHub default branches because they are not npm registry packages.
15
+ ARG DSH_VERSION=latest
16
+ ARG MOBILE_CSS_PLUGIN="github:ook826092-cloud/dsh-mobile-css"
17
+ ARG REMOTE_AUTH_PLUGIN="github:GDWhisper/dsh-web-startup-auth"
18
+
19
+ ENV HOME=/home/user
20
+ ENV DSH_HOME=/home/user/.dsh
21
+
22
+ # Keep user-level configuration/cache/package-manager state inside the persistent bucket.
23
+ ENV XDG_CONFIG_HOME=/home/user/.config
24
+ ENV XDG_DATA_HOME=/home/user/.local/share
25
+ ENV XDG_STATE_HOME=/home/user/.local/state
26
+ ENV XDG_CACHE_HOME=/home/user/.cache
27
+ ENV NPM_CONFIG_CACHE=/home/user/.cache/npm
28
+ ENV PNPM_HOME=/home/user/.local/share/pnpm
29
+
30
+ ENV MOBILE_CSS_PLUGIN=${MOBILE_CSS_PLUGIN}
31
+ ENV REMOTE_AUTH_PLUGIN=${REMOTE_AUTH_PLUGIN}
32
+
33
+ # DSH requires Node >=22.19 or >=24; Node 24 is the current LTS line.
34
+ RUN apt-get update && apt-get install -y --no-install-recommends \
35
+ nginx-light \
36
+ curl \
37
+ ca-certificates \
38
+ && rm -rf /var/lib/apt/lists/*
39
+
40
+ # Install DSH globally outside /home/user so the image contains the runtime.
41
+ ENV NPM_CONFIG_PREFIX=/opt/npm-global
42
+ ENV PATH=/opt/npm-global/bin:/home/user/.local/share/pnpm:$PATH
43
+ # dsh plugin is a thin forwarder to pnpm, so pnpm must be installed before
44
+ # the entrypoint can install/update profile plugins.
45
+ RUN npm install -g pnpm@latest @deepseek-ai/dsh@${DSH_VERSION} \
46
+ && pnpm --version \
47
+ && dsh --version
48
+
49
+ # Hugging Face Docker Spaces convention: uid 1000, home = /home/user.
50
+ RUN if getent passwd 1000 >/dev/null; then userdel -r "$(getent passwd 1000 | cut -d: -f1)"; fi \
51
+ && useradd -m -u 1000 user
52
+
53
+ WORKDIR /app
54
+
55
+ COPY --chown=user:user nginx.conf /etc/nginx/nginx.conf
56
+ RUN mkdir -p /var/lib/nginx /var/log/nginx \
57
+ && chown -R user:user /var/lib/nginx /var/log/nginx /etc/nginx
58
+
59
+ COPY --chown=user:user entrypoint.sh /app/entrypoint.sh
60
+ RUN chmod +x /app/entrypoint.sh
61
+
62
+ USER user
63
+
64
+ EXPOSE 7860
65
+ CMD ["/app/entrypoint.sh"]
README.md ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ - `pnpm@latest` (required by `dsh plugin`)
64
+ - `dsh-mobile-css` from `github:ook826092-cloud/dsh-mobile-css`
65
+ - `dsh-web-startup-auth` from `github:GDWhisper/dsh-web-startup-auth`
66
+
67
+ The two web plugins are also refreshed on every boot so a persistent profile
68
+ doesn't permanently pin an old plugin version.
69
+
70
+ ## Hugging Face visibility
71
+
72
+ Keep this Space **Private**. DSH has powerful filesystem/agent capabilities.
73
+ The authentication plugin is an additional application-level security layer,
74
+ not a reason to make a powerful agent public.
75
+
76
+ ## Secrets
77
+
78
+ Put API keys in Hugging Face Space **Settings → Secrets**, not in this repo.
79
+
80
+ ## Port
81
+
82
+ Hugging Face routes the Space to port `7860`. nginx listens on `7860` and
83
+ proxies to DSH on `0.0.0.0:3080` inside the container.
84
+
85
+ ## Why nginx no longer rewrites Host/Origin
86
+
87
+ The earlier configuration rewrote `Host`/`Origin` to localhost. That could make
88
+ the server-side loopback check pass, but it could not change the browser's
89
+ actual `location.hostname`, which is what caused:
90
+
91
+ `settings are unavailable in this browser`
92
+
93
+ The remote-auth plugin is designed to solve both sides: the browser-side remote
94
+ settings gate and the authenticated server-side privileged API boundary. nginx
95
+ therefore preserves the HF authority and forwards the real request metadata.
96
+
97
+
98
+ ## Plugin sources
99
+
100
+ The mobile CSS and remote-auth plugins are community GitHub repositories, not npm
101
+ registry packages. The startup script therefore installs them from their current
102
+ GitHub default branches:
103
+
104
+ - `github:ook826092-cloud/dsh-mobile-css`
105
+ - `github:GDWhisper/dsh-web-startup-auth`
106
+
107
+ This is intentional: asking pnpm for `dsh-mobile-css@latest` causes npm registry
108
+ HTTP 404 because there is no package published under that npm name.
109
+
110
+ ## Plugin installation note
111
+
112
+ `dsh plugin` is a thin pnpm forwarder. The image therefore installs pnpm before
113
+ startup; without it, plugin installation cannot run.
114
+
115
+ The mobile CSS repository is correctly installed from GitHub. It is **not**
116
+ `dsh-mobile-css@latest` from the npm registry: that registry name returns HTTP
117
+ 404. The project's current installation docs recommend
118
+ `github:ook826092-cloud/dsh-mobile-css`.
119
+
120
+ GitHub plugins may also require pnpm build-script approval depending on the
121
+ pnpm version and the plugin's package metadata. If the runtime log reports an
122
+ `allowBuilds`/approval key, that exact package must be allowlisted in the
123
+ persistent web profile before the plugin can build.
entrypoint.sh ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 forwards plugin operations to pnpm; fail with a useful diagnostic
16
+ # before touching the persistent profile if the executable is missing.
17
+ if ! command -v pnpm >/dev/null 2>&1; then
18
+ echo "[entrypoint] ERROR: pnpm is missing from PATH"
19
+ echo "[entrypoint] PATH=$PATH"
20
+ exit 1
21
+ fi
22
+ echo "[entrypoint] pnpm $(pnpm --version)"
23
+
24
+ # Install/update both runtime UI plugins from their current GitHub default branches on every boot.
25
+ # If the registry is temporarily unavailable but an existing copy is present,
26
+ # keep the existing copy; otherwise fail rather than silently starting without
27
+ # the remote-settings/auth capability.
28
+ ensure_latest_plugin() {
29
+ local spec="$1"
30
+ local package_name="$2"
31
+
32
+ echo "[entrypoint] installing/updating ${spec}"
33
+ if dsh plugin --profile web add "${spec}"; then
34
+ echo "[entrypoint] ${package_name}: latest install/update succeeded"
35
+ return 0
36
+ fi
37
+
38
+ if [ -d "$DSH_HOME/profiles/web/node_modules/$package_name" ]; then
39
+ echo "[entrypoint] ${package_name}: registry update failed; keeping existing installed copy"
40
+ return 0
41
+ fi
42
+
43
+ echo "[entrypoint] ERROR: ${package_name} is not installed and could not be fetched"
44
+ exit 1
45
+ }
46
+
47
+ ensure_latest_plugin "${MOBILE_CSS_PLUGIN}" "dsh-mobile-css"
48
+ ensure_latest_plugin "${REMOTE_AUTH_PLUGIN}" "dsh-web-startup-auth"
49
+
50
+ # nginx is the HF-facing listener. DSH remains behind it.
51
+ nginx -g 'daemon off;' &
52
+ NGINX_PID=$!
53
+ trap 'kill "$NGINX_PID" 2>/dev/null || true' EXIT
54
+
55
+ # dsh-web-startup-auth replaces the stock startup guard and provides:
56
+ # - authenticated remote browser access
57
+ # - remote settings/provider/credentials support
58
+ # - crypto.randomUUID compatibility for non-loopback browser contexts
59
+ # - loopback-authority handling for privileged APIs after authentication
60
+ #
61
+ # Bind DSH to all interfaces inside the container; nginx is the only public
62
+ # HF-facing entry point. The auth plugin protects remote access.
63
+ 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
+ }