# DSH Web Manager 面向本机或受信任团队的DSH容器管理面板,使用Go、React和SQLite。目标见 [DESIGN.md](DESIGN.md),接口见 [API.md](API.md),部署和恢复见 [OPERATIONS.md](OPERATIONS.md)。不适合直接作为公网不可信多租户平台。 本项目定位为本机或可信团队试运行,可由管理员通过 HTTPS 反代提供公网入口;不支持不可信租户。选型差异及真实验证边界见 [ACCEPTANCE.md](ACCEPTANCE.md)。 ## DSH 工作台与移动端 登录后默认进入内嵌 DSH 工作台。在“设置”中选择当前账号的实例;选择仅保存在当前浏览器并按账号区分,不保存登录凭据。没有实例时先到“实例”创建,停止或异常的实例需先启动或处理。切换实例会重新加载内嵌页面,但不会停止原容器。 移动端使用底部导航:DSH、实例、文件、设置;桌面保留侧栏。镜像管理和管理员功能从设置进入。切换面板栏目会保留内嵌 DSH 页面,提供重新加载及独立窗口入口。此为同源 iframe,不是安全隔离边界,仍仅适合本机或受信任团队。 ## 当前实现 - 内置账号、管理员配额、短JWT与refresh轮换、失败锁定及审计。 - 精确版本查询、本地镜像异步构建/验证/清理、持久创建预约与事务名额(每用户最多两个实例)、启动对账。预约不再仅保存在当前页面。 - 实例启停删除、独立home(模板7挂载为 `/workspace`)、每实例bridge、归属鉴权及路径反代;DSH认证cookie由服务端保管。 - 文件上传/下载/重命名/删除及目录ZIP;软配额写前检查、写入串行化与周期巡检。上传最大32 MiB且不覆盖已有文件,删除目录须先清空;删除实例保留数据。 - 密码加密配置备份、资源目录与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。 ```sh 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`): ```dotenv 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` 内。证书需提前申请并配置续期;示例不负责签发证书。 ```nginx 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`。 保存后先检查,再由管理员重载: ```sh sudo nginx -t # 仅在检查成功后执行 sudo systemctl reload nginx ``` ### Caddy 将以下内容加入 `/etc/caddy/Caddyfile`。域名 DNS 应指向该主机,80/443 需按证书签发方式可达;Caddy 默认自动申请/续期 HTTPS 证书并将 HTTP 重定向到 HTTPS。 ```caddyfile 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,客户端需重连。 ```sh 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](ACCEPTANCE.md) 与 [OPERATIONS.md](OPERATIONS.md)。 ## 用户密码重设 所有已登录用户(包括管理员)可在「设置 → 账号 → 修改密码」输入当前密码、新密码及确认新密码。当前密码必须验证通过;成功后撤销该账号全部旧会话并返回登录页。忘记当前密码时仍需联系管理员,不提供匿名找回入口。 管理员可在「设置 → 用户与审计 → 用户列表 → 重设密码」为用户设置新密码(12–72 UTF-8字节,二次输入及确认)。重设会撤销目标账号全部登录会话和refresh令牌、清除登录失败锁定;旧密码不能再登录,审计仅记录操作者和目标,不记录密码。重设管理员自身会返回登录页。已建立的DSH WebSocket/SSE连接不保证在重设瞬间断开,后续HTTP请求、连接重建与会话续期会被拒绝。 ## 本地runtime构建 当前模板为修订7,同时内置 `dsh-web-mobile@3.0.5` 与 `dsh-free-search@0.8.5`(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默认内置 `dsh-web-mobile@3.0.5`(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及禁用状态。 需要Docker、Bash、npm及基础镜像/npm网络访问;不推送镜像。 注意:dsh-web-mobile还包含响应压缩、移动会话删除入口和自定义模型推理档位补全,并非纯CSS。内置版本随镜像固定,不跟踪GitHub main或npm latest。 模板4目前限定DSH 0.2.0-rc.1/rc.2,并校验已安装宿主的Cordis/DSH peer版本;其他版本需先更新兼容验证,不会自动安装第二套宿主依赖。插件没有独立参数配置页;它修改DSH原有移动界面与设置页。 ```sh bash scripts/build-image.sh # 查询latest并锁定精确版本 bash scripts/build-image.sh 0.2.0-rc.2 # 或指定精确版本 bash scripts/smoke-image.sh dsh-web:0.2.0-rc.2-7 ``` 当前tag为 `dsh-web:-7`(模板修订7)。runtime以UID/GID 1000运行,home和workspace需允许该身份读写。DSH监听loopback:3080,Caddy监听容器内8080;不要发布runtime端口或挂载socket。原生模块需要可执行的 `/tmp`,保留nosuid/nodev和容量限制。存在插件清单时先协调插件,失败不启动应用;真实验证范围见 ACCEPTANCE.md。 ## 设计替代与边界 合理替代方案为:stdlib HTTP Docker client而非官方SDK;每实例独立bridge而非共享bridge;服务端保管DSH cookie而非下发浏览器;普通React组件而非shadcn/ui(保留Monaco)。这些是选型差异,不降低功能、安全或验收标准,详见验收表。 同源路径代理仅适用于可信用户/插件,尚无独立origin及宿主/局域网出站防护。默认软配额不能阻止agent直接写满磁盘;硬quota是依赖宿主的可选增强,不是默认路线必做项。升级/恢复快照需额外容量,home快照上限1 GiB,模板7的 `/workspace` 就是实例home,会随home快照回滚;旧共享目录不随回滚恢复;中断及回滚失败仍可能需要管理员恢复。