211 lines
17 KiB
Markdown
211 lines
17 KiB
Markdown
# 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,不是安全隔离边界,仍仅适合本机或受信任团队。
|
||
|
||
## 当前实现
|
||
|
||
DSH 运行镜像支持从本仓库 `dockerfile` 分支获取独立发布的构建配置,无需 CI 预构建镜像。发布包含 `release.json` 和完整的 `image/` 目录;面板锁定 Git commit,在本地构建并验证。管理员在镜像页检查并确认构建,实例升级仍需另行确认,不会因分支更新自动停机。发布约定和首次启用步骤见 [OPERATIONS.md](OPERATIONS.md)。
|
||
|
||
实例所有者可管理完整 home,包括读取自己的配置和凭据;不承诺密钥 write-only,跨用户隔离保持不变。通用文件操作不受专用 Profile 编辑器的 YAML 校验和停机要求约束。下载、打包与敏感修改提示用于防误操作,不是权限限制;文件下载和目录 ZIP 不会自动加密,修改配置前建议先停止实例并备份。
|
||
|
||
- 内置账号、管理员配额、短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构建
|
||
|
||
### 兼容清单与实验构建
|
||
|
||
镜像的构建验证与使用准入分开管理:推荐/兼容镜像还必须通过精确身份与实际验证检查才可选择;仅存量镜像允许原实例继续运行和启停,不可新建或作为升级目标;禁止使用镜像还拒绝新的启动/重启,但不会自动停止现有容器或断开工作台。
|
||
|
||
未知版本仅管理员可在镜像页显式提交实验构建。实验保留镜像模板的完整性、依赖和启动检查,可能因不兼容而失败;不会为让新版通过而跳过检查。实验成功后仍不可创建实例,管理员需完成独立兼容验收,再在“准入管理”填写可追溯依据并确认。准入绑定精确镜像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及禁用状态。
|
||
|
||
需要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:<version>-7`(模板修订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而非下发浏览器;普通React组件而非shadcn/ui(保留Monaco)。这些是选型差异,不降低功能、安全或验收标准,详见验收表。
|
||
|
||
同源路径代理仅适用于可信用户/插件,尚无独立origin及宿主/局域网出站防护。默认软配额不能阻止agent直接写满磁盘;硬quota是依赖宿主的可选增强,不是默认路线必做项。升级/恢复快照需额外容量,home快照上限1 GiB,模板7的 `/workspace` 就是实例home,会随home快照回滚;旧共享目录不随回滚恢复;中断及回滚失败仍可能需要管理员恢复。
|