DSH Web Manager
面向本机或受信任团队的DSH容器管理面板,使用Go、React和SQLite。目标见 DESIGN.md,接口见 API.md,部署和恢复见 OPERATIONS.md。不适合直接作为公网不可信多租户平台。
本项目定位为本机或可信团队试运行,可由管理员通过 HTTPS 反代提供公网入口;不支持不可信租户。选型差异及真实验证边界见 ACCEPTANCE.md。
DSH 工作台与移动端
登录后默认进入内嵌 DSH 工作台。在“设置”中选择当前账号的实例;选择仅保存在当前浏览器并按账号区分,不保存登录凭据。没有实例时先到“实例”创建,停止或异常的实例需先启动或处理。切换实例会重新加载内嵌页面,但不会停止原容器。
移动端使用底部导航:DSH、实例、文件、设置;桌面保留侧栏,管理员可直接从侧栏进入镜像与用户管理,移动端这两个入口仍在“设置”中。切换面板栏目会保留内嵌 DSH 页面,重新加载与独立窗口入口收在工具栏的溢出菜单中。此为同源 iframe,不是安全隔离边界,仍仅适合本机或受信任团队。
当前实现
DSH 新运行镜像仅由管理员从本仓库 dockerfile 分支获取独立发布的构建配置,无需 CI 预构建镜像。release.json 和完整的 image/ 目录仅维护在发布分支,main 不再内置运行镜像构建资产;面板锁定 Git commit,在本地构建并验证。管理员在镜像页检查并确认构建,实例升级仍需另行确认,不会因分支更新自动停机。发布约定和首次启用步骤见 OPERATIONS.md。
实例所有者可管理完整 home,包括读取自己的配置和凭据;不承诺密钥 write-only,跨用户隔离保持不变。通用文件操作不受专用 Profile 编辑器的 YAML 校验和停机要求约束。下载、打包与敏感修改提示用于防误操作,不是权限限制;文件下载和目录 ZIP 不会自动加密,修改配置前建议先停止实例并备份。
- 内置账号、管理员配额、短JWT与refresh轮换、失败锁定及审计。
- 管理员 Git 发布检查、本地镜像异步构建/验证/清理、使用已验证且准入的本地镜像创建实例与事务名额(每用户最多两个实例)、启动对账。
- 实例启停删除、独立home(模板7挂载为
/workspace)、每实例bridge、归属鉴权及路径反代;DSH认证cookie由服务端保管。 - 文件上传/下载/重命名/删除及目录ZIP;软配额写前检查、写入串行化与周期巡检。上传最大32 MiB且不覆盖已有文件,删除目录须先清空;删除实例会永久删除其 home、快照及备份。
- 密码加密配置备份、资源目录与manifest、外部加密包导入、恢复健康检查及回滚。备份不再限于六个固定文件;不包含会话、工作区及node_modules。
- 备份/恢复/升级自动编排停启;升级保存密码配置备份和home快照,失败尝试回滚。Profile使用本地Monaco及脱敏预览;目前仍需先停止实例,页面“保存并启动”随后验证就绪。
新备份使用创建时的登录密码经Argon2id派生密钥、AES-GCM加密,不依赖或创建 backup.key。导入/恢复须使用原备份密码,修改登录密码不会重加密旧包。旧版密钥包仍兼容,保留这些包时必须保管原 backup.key。
启动面板
需要Linux 5.6+(openat2/renameat2)、Go 1.25+、Node/npm及本机Docker daemon。Docker socket权限等价宿主root。
npm --prefix web ci
npm --prefix web run build
go build -o bin/dsh-mgr ./cmd/server
# 首次启动设置ADMIN_USERNAME及12–72字节强密码ADMIN_PASSWORD
./bin/dsh-mgr
默认访问 http://127.0.0.1:8088,数据在 ./data。修改环境密码不会重置已有账号。容器部署使用 .env.example 和 compose.yaml,按运维文档准备UID/GID 1000数据目录、Docker socket附加组及Docker存储目录映射,再执行 docker compose up --build -d。Compose仅发布loopback。
公网 HTTPS 反向代理(Nginx / Caddy)
以下两种方案任选一种,假设反代运行在面板所在宿主机,域名为 dsh.example.com,后端为 http://127.0.0.1:8088。将示例域名、证书路径替换为实际值。仅支持独立域名根路径部署,不要挂在 /panel/ 等子路径下;所有路径(包括 /api/ 和 /dsh/)统一转发到面板,不直接代理或公开 runtime 的端口。公网入口仍仅供可信账号使用,不是多租户安全隔离方案。
面板环境变量
使用 Compose 时在项目 .env 中设置;直接运行二进制时需通过进程环境传入(二进制不会自动读取 .env):
PANEL_PUBLIC_URL=https://dsh.example.com
# 仅适用于反代和面板二进制均在宿主机、通过127.0.0.1连接的情况
TRUSTED_PROXY_CIDRS=127.0.0.1/32
PANEL_PUBLIC_URL 必须与浏览器访问的 origin 一致,不带路径;非标准 HTTPS 端口需包含端口号。它控制 Origin/CSRF 校验和 Secure Cookie,不依赖 X-Forwarded-Proto 推断。修改环境变量后需重启面板进程;Compose 部署应执行 docker compose up -d panel 使容器读取新环境,单纯 docker compose restart 不会更新环境。
注意 Docker 网络差异:宿主机反代访问 Compose 发布端口时,面板看到的来源可能是 Docker 网桥网关,而不是 127.0.0.1。此时应将 TRUSTED_PROXY_CIDRS 改为确认过的实际代理来源 IP /32(IPv6 用 /128),不要照抄上述值,也不要信任 0.0.0.0/0、::/0 或整个不受控内网。未配置信任时,转发头会被忽略,所有用户可能共享代理 IP 的登录额度。可通过后端连接观测确认来源,Docker 网络 inspect 只能提供候选网关信息,不能替代实际确认。
如果反代也运行在容器内,127.0.0.1 指向反代容器自身,不能使用下面的后端地址。应让反代与面板加入专用 Docker 网络,将后端改为 panel:8088(实际服务名),并按面板看到的代理容器地址设置可信来源;不要在同一受信网络放入不可信容器。后端8088和runtime端口不应直接暴露公网。
Nginx
示例可放入由 http {} 引入的 /etc/nginx/conf.d/dsh.conf;map 必须位于 http 上下文,不能放在 server 或 location 内。证书需提前申请并配置续期;示例不负责签发证书。
map $http_upgrade $dsh_connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name dsh.example.com;
return 308 https://dsh.example.com$request_uri;
}
server {
listen 443 ssl;
server_name dsh.example.com;
ssl_certificate /etc/letsencrypt/live/dsh.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dsh.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# 为multipart封装留余量;应用仍执行自己的上传/备份上限
client_max_body_size 64m;
client_body_timeout 60s;
location / {
proxy_pass http://127.0.0.1:8088;
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $dsh_connection_upgrade;
# 单层公网边缘代理:覆盖客户端提供的转发来源,不能原样信任
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_buffering off;
proxy_request_buffering off;
proxy_cache off;
proxy_connect_timeout 10s;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
send_timeout 3600s;
}
}
Upgrade/Connection 支持 WebSocket,关闭响应缓冲可及时传送 SSE;读写超时指相邻读写的空闲时间,不是保证连接永久存活。不要改写或删除 Origin、Cookie、X-CSRF-Token,不要额外添加阻止同源 iframe 的 X-Frame-Options: DENY。
保存后先检查,再由管理员重载:
sudo nginx -t
# 仅在检查成功后执行
sudo systemctl reload nginx
Caddy
将以下内容加入 /etc/caddy/Caddyfile。域名 DNS 应指向该主机,80/443 需按证书签发方式可达;Caddy 默认自动申请/续期 HTTPS 证书并将 HTTP 重定向到 HTTPS。
dsh.example.com {
request_body {
max_size 64MB
}
reverse_proxy 127.0.0.1:8088 {
# 单层公网边缘代理,覆盖外部传入的XFF
header_up X-Forwarded-For {http.request.remote.host}
flush_interval -1
transport http {
dial_timeout 10s
}
}
}
Caddy 自动支持 WebSocket,默认保留请求 Host,并设置转发协议信息;flush_interval -1 用于及时刷新流式响应。此处不设置较短的响应读取/流生命周期超时,避免中断长时间 SSE/终端连接。Caddy 重载可能关闭现有 WebSocket,客户端需重连。
sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
# 仅在检查成功后执行(使用systemd安装时)
sudo systemctl reload caddy
以上示例均假设客户端直接连接这层反代。若前方还有 CDN、负载均衡或第二层代理,不能直接依赖这里的 $remote_addr / remote.host 得到真实客户端 IP;需要在边缘代理单独配置严格的可信上游及来源解析,并相应调整转发策略,否则用户会共享上一层代理的限流额度。
部署后检查
- 确认 HTTP 跳转 HTTPS,证书有效,公网只能到达反代而不能直接访问面板8088或runtime端口。
- 通过实际域名完成登录、刷新、退出,确认无 Origin/CSRF 403,认证 Cookie 带 Secure;直接使用后端HTTP测试不能替代HTTPS验收。
- 打开内嵌 DSH、终端/WebSocket、SSE及插件设置;空闲后重连、登录续期均需验证。
- 验证文件上传/下载及备份导入;应用限制仍有效,反代64MB上限不代表单文件可上传64MB。
- 使用不同真实客户端确认登录限流来源隔离;测试应使用专用账号,避免锁定实际账号。
这些是配置模板,不代表已在你的公网域名部署或验收;应用侧测试和运维边界见 ACCEPTANCE.md 与 OPERATIONS.md。
用户密码重设
所有已登录用户(包括管理员)可在「设置 → 账号 → 修改密码」输入当前密码、新密码及确认新密码。当前密码必须验证通过;成功后撤销该账号全部旧会话并返回登录页。忘记当前密码时仍需联系管理员,不提供匿名找回入口。
管理员可在「设置 → 用户与审计 → 用户列表 → 重设密码」为用户设置新密码(12–72 UTF-8字节,二次输入及确认)。重设会撤销目标账号全部登录会话和refresh令牌、清除登录失败锁定;旧密码不能再登录,审计仅记录操作者和目标,不记录密码。重设管理员自身会返回登录页。已建立的DSH WebSocket/SSE连接不保证在重设瞬间断开,后续HTTP请求、连接重建与会话续期会被拒绝。
本地runtime构建
Git 发布与镜像准入
镜像的构建验证与使用准入分开管理:推荐/兼容镜像还必须通过精确身份与实际验证检查才可选择;仅存量镜像允许原实例继续运行和启停,不可新建或作为升级目标;禁止使用镜像还拒绝新的启动/重启,但不会自动停止现有容器或断开工作台。
管理员在镜像页检查 Git 发布并确认精确 commit 后构建。普通用户只能选择已验证且准入的本地镜像创建实例;没有可选镜像时需等待管理员发布与构建,不再提供 npm 版本、实验构建或构建后自动创建入口。这是 breaking change:移除 /api/latest、/api/compatibility、POST /api/builds 及 /api/intents 的全部查询、创建和取消接口,不提供 410 兼容入口。仅保留 GET /api/builds 和 GET /api/builds/{id} 构建历史读取。不迁移或恢复旧构建及旧创建意图,不在后台续跑或完成旧预约;这不意味着删除已有实例或数据。准入绑定精确镜像ID、版本、模板和平台,不自动信任同tag的新镜像。
历史模板按其自身验证记录与挂载布局判断,不因模板号不是当前版本就标为缺失。有成功验证记录、布局受支持的旧镜像可经管理员验收准入;无法识别的布局不会仅凭一段说明放行。界面展示实际准入原因,以后端校验为准。
当前模板为修订7,同时内置 [email protected] 与 [email protected](MIT,https://github.com/DDDMUC/dsh-free-search)。Free Search及其普通依赖固定版本、校验SHA-512并保留镜像内原始tarball;新profile默认正式安装并启用,已有profile不自动注入。模板4的mobile恢复路径继续保留。仅包含内置固定依赖的profile在启动前离线安装,不限制运行期插件管理器联网。
Free Search默认引擎为Bing,注册的统一provider ID为ddg;未设置或等于deepseek-official的搜索provider会被它接管(包括显式选择同名官方provider),默认http抓取也会被接管,其他自定义provider保留。名称含free不等于保证零费用:自动回退可能读取已有API凭据并调用计费服务。请在插件设置中按需禁用引擎;fallback顺序不是引擎白名单。内置集成不自动测试搜索、配置密钥或执行在线更新。
插件设置页的八个固定API路径由面板在实例归属、登录和同源检查后适配前缀及loopback Host,不开放匿名根路径接口。用户在插件内更新到其他版本可能改变接口或离线依赖,需重新验证兼容性。旧实例需显式安装/启用;升级镜像不会覆盖其已有插件选择。
模板修订4默认内置 [email protected](MIT,来源 https://github.com/mexiaosqwq/dsh-web-mobile)。构建校验固定SHA-512、不执行插件安装脚本;新建web profile使用镜像内tarball作为正式依赖并默认启用bundle,可在Plugins的Installed列表查看及启停。为兼容旧profile仍保留宿主副本;UI可能显示Uninstall,但本轮未验证卸载行为,不承诺移除镜像内置副本。已有profile不会被覆盖或强制启用;模板3旧实例须在升级后补正式依赖登记,仅升级镜像不会自动出现在Installed列表。该插件按触屏能力启用移动布局,仅缩窄桌面窗口不一定触发。
依赖固定引用 file:/usr/local/lib/dsh-mgr/dsh-web-mobile-3.0.5.tgz,首次安装与备份恢复无需下载该包。备份包含依赖和bundle清单,恢复目标必须提供相同tarball路径(模板4及兼容后续镜像);初始化会在此依赖存在且workspace缺失时补建官方no-auto-peers配置,保留已有workspace及禁用状态。
运行镜像的构建源与初始化单测在 dockerfile 分支维护;main 只构建面板,不再提供 scripts/build-image.sh。镜像构建仍需要基础镜像与依赖网络访问,不推送镜像。
注意:dsh-web-mobile还包含响应压缩、移动会话删除入口和自定义模型推理档位补全,并非纯CSS。内置版本随镜像固定,不跟踪GitHub main或npm latest。 模板4目前限定DSH 0.2.0-rc.1/rc.2,并校验已安装宿主的Cordis/DSH peer版本;其他版本需先更新兼容验证,不会自动安装第二套宿主依赖。插件没有独立参数配置页;它修改DSH原有移动界面与设置页。
bash scripts/smoke-image.sh "$RUNTIME_IMAGE_ID" # 已由管理员构建的本地镜像 ID
scripts/smoke-image.sh 只检查已有镜像运行,不读取源码、不构建镜像;需 Docker 和 Bash,会创建并清理临时容器。请从镜像页取得精确镜像 ID,不依赖旧版本 tag 推断 Git 发布身份。当前部署协议为7。runtime以UID/GID 1000运行,home和workspace需允许该身份读写。DSH监听loopback:3080,Caddy监听容器内8080;不要发布runtime端口或挂载socket。原生模块需要可执行的 /tmp,保留nosuid/nodev和容量限制。存在插件清单时先协调插件,失败不启动应用;真实验证范围见 ACCEPTANCE.md。
设计替代与边界
构建历史接口现默认分页(20条,最大100条),列表不再携带日志正文;日志通过 /api/builds/{id} 按需读取。升级面板后请刷新已打开的旧页面;外部脚本需按 API.md 更新,不能再依赖 /api/builds 返回全部历史或日志。
合理替代方案为:stdlib HTTP Docker client而非官方SDK;每实例独立bridge而非共享bridge;服务端保管DSH cookie而非下发浏览器。前端已改用 Tailwind CSS + Radix 原语的 shadcn 风格组件(保留本地Monaco),桌面与移动端共用一套响应式界面。界面跟随系统深浅色设置自动切换夜间模式(含 Monaco 编辑器),与内嵌 DSH 行为一致,无独立开关。这些是选型差异,不降低功能、安全或验收标准,详见验收表。
同源路径代理仅适用于可信用户/插件,尚无独立origin及宿主/局域网出站防护。默认软配额不能阻止agent直接写满磁盘;硬quota是依赖宿主的可选增强,不是默认路线必做项。升级/恢复快照需额外容量,home快照上限1 GiB,模板7的 /workspace 就是实例home,会随home快照回滚;旧共享目录不随回滚恢复;中断及回滚失败仍可能需要管理员恢复。