- 02-运维手册.md:mksess 命中处补「PG 直插 + 连接串来源」+ D1 勘误指针两行(档案 77 的旧形态说明,正文不改) - skills/dsh-change-workflow/SKILL.md(146/395/495 三行)、skills/dsh-env-bootstrap/references/常驻规则-快照.md(55 行):同口径校正 - skills/dsh-auto-handoff-chain/:新建文档库副本(SKILL.md v1.3.2 + scripts/chain_report.py),此前该技能三处同步从未建立 - README.md / INDEX.md:登记该技能(技能表 + 「什么时候查什么」+ 技能清单表) 判据:E1 = 0 行、E2 两副本 md5 全同、E3 diff -r = 0 行、E4 登记命中 README 1 / INDEX 2、镜像 47 同值
338 lines
21 KiB
Markdown
338 lines
21 KiB
Markdown
> ⚠️ **历史章节**(2026-09-08):本章「dsh-web 容器 / /opt/dsh/data」形态已废弃(容器 2026-09-09 删除,架构切到 dshs 多租户)。保留作历史参考;**当前运维见 `DEPLOY-本部署.md`**。
|
||
|
||
## 六、单实例迁移执行步骤(从当前 data/ 形态 → users/main 形态)
|
||
|
||
> ⚠️ 以下命令在服务器执行(root)。当前容器 `dsh-web` 以 `/opt/dsh/data` 为 `/root/.dsh`。
|
||
|
||
```bash
|
||
# ===== 1. 停容器 + 重组目录 =====
|
||
docker stop dsh-web
|
||
mkdir -p /opt/dsh/backups
|
||
mv /opt/dsh/backup-20260908 /opt/dsh/backups/20260908_initial # 旧备份归档保留
|
||
|
||
mkdir -p /opt/dsh/users/main
|
||
mv /opt/dsh/data /opt/dsh/users/main/.dsh # data 落入主目录
|
||
mkdir -p /opt/dsh/users/main/workspaces
|
||
|
||
# ===== 2. 固化密钥 + 重建容器(挂载主目录内两点) =====
|
||
DK=$(docker inspect dsh-web --format '{{range .Config.Env}}{{println .}}{{end}}' | grep '^DEEPSEEK_API_KEY=' | cut -d= -f2-)
|
||
printf 'DEEPSEEK_API_KEY=%s\n' "$DK" > /opt/dsh/.env
|
||
chmod 600 /opt/dsh/.env
|
||
|
||
docker rm dsh-web
|
||
docker run -d --name dsh-web --restart always --network host \
|
||
-v /opt/dsh/users/main/.dsh:/root/.dsh \
|
||
-v /opt/dsh/users/main/workspaces:/root/workspaces \
|
||
--env-file /opt/dsh/.env \
|
||
dsh-web:0.1.2 dsh web --host 127.0.0.1 --port 8900 --no-open --trusted-host 47.77.182.89
|
||
|
||
# ===== 3. 生成备份/恢复脚本 =====
|
||
cat > /opt/dsh/backup.sh <<'EOF'
|
||
#!/bin/bash
|
||
# 一键备份:打包 users/ 全部用户主目录 → backups/时间戳.tar.gz
|
||
set -euo pipefail
|
||
TS=$(date +%Y%m%d_%H%M%S)
|
||
OUT=/opt/dsh/backups/dsh-backup-${TS}.tar.gz
|
||
cd /opt/dsh
|
||
docker stop dsh-web 2>/dev/null || true
|
||
tar -czf "$OUT" users
|
||
docker start dsh-web 2>/dev/null || true
|
||
echo "备份完成: $OUT"
|
||
ls -lh "$OUT"
|
||
EOF
|
||
|
||
cat > /opt/dsh/restore.sh <<'EOF'
|
||
#!/bin/bash
|
||
# 一键恢复:./restore.sh <备份tar.gz>(当前数据先挪走可回滚)
|
||
set -euo pipefail
|
||
TAR=${1:?用法: ./restore.sh /opt/dsh/backups/dsh-backup-xxx.tar.gz}
|
||
test -f "$TAR" || { echo "备份文件不存在: $TAR"; exit 1; }
|
||
TS=$(date +%Y%m%d_%H%M%S)
|
||
cd /opt/dsh
|
||
docker stop dsh-web 2>/dev/null || true
|
||
[ -d users ] && mv users users.old-$TS
|
||
tar -xzf "$TAR"
|
||
docker rm -f dsh-web 2>/dev/null || true
|
||
docker run -d --name dsh-web --restart always --network host \
|
||
-v /opt/dsh/users/main/.dsh:/root/.dsh \
|
||
-v /opt/dsh/users/main/workspaces:/root/workspaces \
|
||
--env-file /opt/dsh/.env \
|
||
dsh-web:0.1.2 dsh web --host 127.0.0.1 --port 8900 --no-open --trusted-host 47.77.182.89
|
||
echo "恢复完成(旧数据在 users.old-$TS,确认无误后可删)"
|
||
EOF
|
||
|
||
chmod +x /opt/dsh/backup.sh /opt/dsh/restore.sh
|
||
|
||
# ===== 4. 验证 =====
|
||
docker ps | grep dsh-web
|
||
curl -s -X POST http://127.0.0.1:8900/api/llm/listProviders \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"type":"client-request","rpcId":"v1","method":"llm/listProviders","payload":{"args":{}}}'
|
||
# 期望 {"ok":true,"value":[{"id":"deepseek-official",...}]}
|
||
/opt/dsh/backup.sh
|
||
```
|
||
|
||
---
|
||
|
||
|
||
## 七、备份与恢复语义
|
||
|
||
- **备份 = 打包 `users/`**(全部用户主目录,一个 tar);`.env`(部署配置)与 `std/`(可再生成)不进备份;
|
||
- **恢复 = 解压 + 重建容器**(restore.sh 已固化容器参数);
|
||
- **复制/迁移用户**:拷 `/opt/dsh/users/<uid>` 到新机同路径即可;
|
||
- 多用户同构扩展:新用户增加 `users/<uid>/`,脚本无需改动(tar 整体 users/)。
|
||
|
||
---
|
||
|
||
|
||
## 八、多用户扩展(dshs 落地前的过渡方案)
|
||
|
||
目标:**每用户独立实例 + 严格隔离**。新增一个用户 = 一套同构目录 + 一个容器 + nginx 分流:
|
||
|
||
```bash
|
||
# 以新增 zhangsan 为例(示例脚本 add-user.sh)
|
||
UID_NEW=zhangsan
|
||
PORT=8901
|
||
mkdir -p /opt/dsh/users/${UID_NEW}/.dsh /opt/dsh/users/${UID_NEW}/workspaces
|
||
# 从 main 复制 profile 骨架(不含凭据/会话),或从 std/profile-skel 初始化
|
||
docker run -d --name dsh-web-${UID_NEW} --restart always --network host \
|
||
-v /opt/dsh/users/${UID_NEW}/.dsh:/root/.dsh \
|
||
-v /opt/dsh/users/${UID_NEW}/workspaces:/root/workspaces \
|
||
--env-file /opt/dsh/.env \
|
||
dsh-web:0.1.2 dsh web --host 127.0.0.1 --port ${PORT} --no-open --trusted-host 47.77.182.89
|
||
# nginx:新 server/server_name 分流 → 127.0.0.1:${PORT},沿用 Host/Origin 伪装头模板
|
||
```
|
||
|
||
> 说明:web profile 首次启动会自动初始化模板,新用户空 `.dsh` 直接起容器即可;系统标准技能层由 `std/` 挂载提供。
|
||
|
||
### 认证路线
|
||
|
||
| 阶段 | 方案 | 说明 |
|
||
|---|---|---|
|
||
| 当前 | dsh 自带 token(一次性) | 仅单人过渡 |
|
||
| 近期 | **dshs 密码制基座**(每用户独立 DSH 实例 + 注册审核) | 多租户严格隔离的正规托管方案 |
|
||
| 后期 | **飞书 OAuth 登录** | 替换/并存 dshs 密码认证,预留扩展点 |
|
||
|
||
> 备选认知:`@xgone/dsh-remote` / `dsh-passwords` 类认证网关是共享单租户,无法满足严格隔离,不采用。
|
||
|
||
---
|
||
|
||
|
||
## 十二、域名接入记录(2026-09-08)
|
||
|
||
**域名**:`dsh.alotbuy.com`(Cloudflare 代理,海外服务器免备案)
|
||
|
||
| 项 | 状态 |
|
||
|---|---|
|
||
| DNS | `dsh.alotbuy.com` → Cloudflare(172.67.194.206 / 104.21.44.42),CF 回源服务器 80 |
|
||
| **通配 `*.dsh.alotbuy.com`** | ⚠️ **未配置**——dshs 每用户子域路由的前提,落地前需在 CF 加 `*` A 记录 → 47.77.182.89 |
|
||
| HTTPS | Cloudflare 边缘证书(源站无 443,CF SSL 需为 Flexible 模式,实测已通) |
|
||
| 源站 nginx | 新建站点 `/www/server/panel/vhost/nginx/dsh.alotbuy.com.conf`(监听 80,反代 127.0.0.1:8900,Host 伪装 + 剥 Origin/Sec-Fetch,含 WebSocket 头) |
|
||
| 验证 | token 登录 303→cookie(host-only 绑 dsh.alotbuy.com)→ 首页 200(24KB)→ API `{"ok":true, deepseek-official}` ✅ |
|
||
|
||
**访问方式**:`https://dsh.alotbuy.com/?token=<token>`(浏览器打开一次即种 cookie,30 天有效)
|
||
|
||
**踩坑记录**:
|
||
- Windows 原生 curl 写 `/tmp/` 路径失败导致 cookie 不落盘 → 必须用 `C:/...` 路径(历史已知坑,本次复现)
|
||
- 对根路径发无 cookie 裸 WS Upgrade 返回 502 为 dsh 正常拒绝(IP 站点同样 502),非配置问题
|
||
- nginx 新站点需与 47.77.182.89 站同样伪装头(Host 127.0.0.1:8900 + 空 Origin/Sec-Fetch),否则特权 API 403
|
||
|
||
|
||
## 十四、dsh.alotbuy.com 注册为宝塔面板反代站点(2026-09-08 18:46)
|
||
|
||
**背景**:手写 conf(`/www/server/panel/vhost/nginx/dsh.alotbuy.com.conf`)在面板数据库无站点记录 → 面板重置/批量管理可能误伤。按用户偏好(能走 MCP 尽量走 MCP)用宝塔 MCP 完成注册。
|
||
|
||
**执行链(MCP 优先,全流程零中断)**:
|
||
|
||
1. SSH 备份 + 移走手写 conf:`cp` 到 `/opt/dsh/docs/nginx-dsh.alotbuy.com.conf.bak-20260908`,`mv` 出 include 目录(不 reload,服务无感)
|
||
2. MCP `ProxyProjectCreate`:domains=`dsh.alotbuy.com`,proxy_pass=`http://127.0.0.1:3080` → 站点 id=2 入面板 DB
|
||
3. MCP `ProxyWriteConfig`:把完整精调 conf(HTTP→HTTPS 跳转 ×2 + 443 主域 SSL + 443 通配子域 SSL)覆盖写回同路径
|
||
4. SSH `nginx -t` + reload + 本机 curl 验证
|
||
|
||
**⚠️ 关键坑**:面板反代项目 conf 写入路径与手写 conf 同名同路径(`/www/server/panel/vhost/nginx/<域名>.conf`)——直接 ProxyProjectCreate 会用面板模板覆盖,丢失 SSL/通配子域/301 等精调内容。**必须"先移走手写 conf → 创建 → ProxyWriteConfig 写回精调版"三步走**。
|
||
|
||
**验证结果**:
|
||
|
||
| 检查项 | 结果 |
|
||
|--------|------|
|
||
| SiteList 面板 DB | 2 站点:47.77.182.89(id=1)+ dsh.alotbuy.com(id=2)✅ |
|
||
| nginx -t + reload | OK ✅ |
|
||
| https://dsh.alotbuy.com(外网直连源站) | 200 ✅ |
|
||
| https://*.dsh.alotbuy.com(通配子域,未注册) | 404(dshs 正常拒绝,代理/SSL 层通)✅ |
|
||
| http://dsh.alotbuy.com | 301 → https ✅ |
|
||
| conf 保留 | 4 段精调 server 块完整(certbot 证书路径原样)✅ |
|
||
|
||
**遗留说明**:面板站点列表 SSL 状态显示 `enabled:false`——证书为 certbot 手动签发(面板不托管),面板只做站点/conf 管理;如需面板托管 SSL 需换面板 Let's Encrypt 流程(不建议,会与通配证书冲突)。
|
||
|
||
|
||
## 十五、启动 DSH 报"已有运行中"+ 无法对话 排障(2026-09-08 19:00)
|
||
|
||
**现象**:admin 登录桌面点"启动 DSH",提示"有运行中的 DSH 请先停止",无跳转,无法对话。
|
||
|
||
**根因一(P0,已修复):DB uid 与系统账号 uid 错配**
|
||
- 主 DB(`/var/lib/dshs/dshs.db`,服务 `--db` 显式指向)users.uid=**100001**(bootstrap 写入 `baseUid+rowid`)
|
||
- 但 OS 账号按 `hashUid` 建:`dsh-cce6d1cdb376430480f0` uid=**114801**(早期版本 provision 用 hash;后 provision 改为读 DB uid-for-user,但账号没重建)
|
||
- 结果:编排器 spawn 用 DB uid=100001 → `setpriv --reuid 100001` → 子进程以**不存在的 uid** 运行 → 打不开 `700 owner=114801` 的用户 home → `EACCES: mkdir .../home/profiles/web` 秒崩
|
||
- 编排器 `spawn` 事件即置 status=running,子进程崩后自动重启循环 → 前端一直显示"运行中"→ 再次点击启动返回 409 already_running(即用户看到的提示)
|
||
- **修复**:`UPDATE users SET uid=114801 WHERE id='cce6d1cd-...'`(备份在 `/opt/dsh/docs/dshs.db.bak-20260908-uidfix`)+ `systemctl restart dshs` + provision 脚本 `uid-for-user` 补 `--db /var/lib/dshs/dshs.db`(消除默认库分裂)
|
||
|
||
**根因二(上游集成缺口,未修复):dsh web 强制浏览器 token,编排器"打开"不带 token**
|
||
- dsh web(0.1.2-rc.1,`dsh-client-connection` BrowserAuth)强制:首次访问需 `?token=<launch token>`(进程启动随机、打印到 stdout)换取签名 cookie(绑定 authority=`127.0.0.1:<port>`,30 天)
|
||
- 上游 dshs 0.1.0:launch 返回 url **不带 token**,desktop.html "打开 DSH" 直接 `window.open(url)` → 首访 401 `dsh web authentication required`;且端口动态分配,实例重启换端口 → 旧 cookie 失效需重新带 token
|
||
- 已实测全链路可行:门户 sid + `?token=` → 303 种 dsh-auth cookie → 双 cookie 访问 `https://admin.dsh.alotbuy.com/` 200(24233B SPA)+ `/api/llm/listProviders` ok:true
|
||
- **修复方向(待用户定)**:A. 小改上游——编排器解析子进程 stdout 的 token,launch/status url 自动拼 `?token=`(改 src/supervisor/orchestrator.ts + routes/dsh.ts,tsc 可构建);B. 接受现状——每次启动后手动用 journald 里 token 开一次;C. 等上游修复
|
||
|
||
**排障经验**:`launch folder` 是相对 **ws/** 根的路径(空串=根);`/api/dsh/status` 返回 url 为子域;子域下所有路径(含 /api/dsh/status)都会被代理到实例,故探测实例要用 `/api/llm/*` 等实例 API;cookie 域 `.dsh.alotbuy.com`(sid)与 dsh-auth 双 cookie 缺一不可。
|
||
|
||
|
||
## 附录 A:常用命令速查
|
||
|
||
```bash
|
||
# 备份 / 恢复
|
||
/opt/dsh/backup.sh
|
||
/opt/dsh/restore.sh /opt/dsh/backups/dsh-backup-<时间戳>.tar.gz
|
||
|
||
# 容器日志(看 token / 排障)
|
||
docker logs --tail 50 dsh-web
|
||
|
||
# 进入容器
|
||
docker exec -it dsh-web bash
|
||
|
||
# 插件管理(profile web)
|
||
dsh plugin --profile web add <npm包名|github:owner/repo|link:/path>
|
||
dsh plugin --profile web remove <包名>
|
||
dsh --profile web --dump-config # 打印生效插件树
|
||
|
||
# 模型 API 直测(服务器本机)
|
||
curl -s -X POST http://127.0.0.1:8900/api/llm/listProviders \
|
||
-H 'Content-Type: application/json' \
|
||
-d '{"type":"client-request","rpcId":"v1","method":"llm/listProviders","payload":{"args":{}}}'
|
||
|
||
# 测试 session 生成(**PG 直插**,连接串取自 env / dshs.env / dshs.service.d;10 分钟;user_agent=poc-curl2)
|
||
node /opt/dshs/mksess.cjs
|
||
# ❗ 勘误(2026-09-17 序⑬ Q1 实测):档案 77 与更早文档对该脚本存储机制的描述属**当时形态**(写 `/var/lib/dshs/dshs.db`);
|
||
# 集群化后权威库 = 47 的 PG13(DSHS_DB_URL)、那份文件仅回滚用 ⇒ 现实现 = **PG 直插**,旧描述已失效。
|
||
|
||
# 普通用户角色化 profile patch(隐藏「模型」设置分区,档案 09;admin 不受影响)
|
||
node /opt/dshs/ensure-role-profile-patch.cjs [--restart] <username>
|
||
# 新普通用户 SOP:注册→approve→首登一次(建 profile)→ 跑上命令(--restart 使已运行实例生效)
|
||
```
|
||
|
||
|
||
## 附录 B:参考链接
|
||
|
||
- npm:https://www.npmjs.com/package/@deepseek-ai/dsh(0.1.2-rc.1)
|
||
- 官方仓库:https://github.com/deepseek-ai/deepseek-harness
|
||
- CLI 行为参考:apps/cli/reference/README.md(分层优先级/部署默认)
|
||
- dsh skills 安装与优先级:`~/.dsh/skills`、`~/.agents/skills`、项目级 `/.dsh/skills`(社区文档,见当日讨论记录)
|
||
- 社区多租户方案调研(dshs / dsh-remote / dsh-passwords / dsh-im):见当日讨论记录
|
||
|
||
---
|
||
|
||
## 附录 C:2026-09-11 增补
|
||
|
||
> **【历史 vs 现行 · 2026-09-11 档案 53】** 正文各章(六~十五)写于 **2026-09-08/09**,属**当时的事实轨迹**(含旧域名 `dsh.alotbuy.com`、旧目录形态 `data/` `main`);**现行状态以本附录 C 与 `DEPLOY-本部署.md` 为准**,正文与附录冲突时**一律以附录为准**。
|
||
|
||
|
||
### C.1 域名与证书(档案 22)
|
||
|
||
| 项 | 值 |
|
||
|---|---|
|
||
| 门户 / 用户实例 | `https://alotbuy.com` / `https://<用户名>.alotbuy.com`(旧域 `dsh.alotbuy.com` 及子域 **301**,含用户名映射) |
|
||
| 环境变量 | `DSHS_BASE_DOMAIN=alotbuy.com`、`COOKIE_DOMAIN=.alotbuy.com`(备份 `/etc/dshs.env.bak-*`) |
|
||
| 证书 | `/etc/letsencrypt/live/alotbuy.com/`(`alotbuy.com + *.alotbuy.com`,DNS-01);旧域证书仅用于 301 |
|
||
| **签发/续期必须带传播等待** | `certbot … --dns-cloudflare-propagation-seconds 60`(默认 10 s 会失败;renewal conf 已写死该值) |
|
||
| 切换脚本 | `/opt/dsh/switch-domain-alotbuy.sh`(改 env → drain → 重启,幂等) |
|
||
| Cloudflare | token 仅 DNS 权限;回源实测走 **443**;`*.alotbuy.com` 为共享通配(gao/mao/work 在用,勿改) |
|
||
|
||
### C.2 nginx 关键配置(改了容易被面板覆盖,务必记住)
|
||
|
||
| 指令 | 作用 | 失效症状 |
|
||
|---|---|---|
|
||
| `proxy_buffering off;` | 让流式响应逐块下发 | 「看不到流式输出,结果一次性出现」 |
|
||
| `gzip_proxied any;` | 压缩反代响应(那个 ~930 KB 插件 bundle) | 刷新载入极慢 |
|
||
| `location ~* ^/assets/ { expires 30d; }` | 哈希静态资源长缓存 | 每次刷新重下 ~1.2 MB |
|
||
| `set_real_ip_from <CF段>` + `real_ip_header CF-Connecting-IP;` | 还原真实客户端 IP(**须写在 server 块内**) | 日志/风控里全是 CF 机房 IP |
|
||
|
||
> ⚠️ 在宝塔面板里保存站点配置会按模板**重写 vhost** → 上述自定义指令可能被抹掉;若症状回归,先查这几行。
|
||
|
||
### C.3 平台脚本族
|
||
|
||
| 脚本 | 用途 |
|
||
|---|---|
|
||
| `/opt/dsh/switch-domain-alotbuy.sh` | 域名切换(含 drain) |
|
||
| `/opt/dshs/ensure-role-profile-patch.cjs` | 角色化 profile patch(非 admin;`--force` 升级、`--restart`) |
|
||
| `/opt/dshs/scripts/ci.sh` | 类型检查 + 构建 + 单测(**不含**需要实例/凭据的 smoke) |
|
||
| `/opt/dshs/poc/workspace-scoped-picker/test/poc.mjs` | 目录选择器插件自检(26 项,须以实例 uid 从可读路径运行) |
|
||
| `docs/scripts/docs-sync-check.sh` | 文档库双端对账(0=全绿) |
|
||
|
||
### C.4 排障速查(本手册新增条目)
|
||
|
||
| 症状 | 先查 |
|
||
|---|---|
|
||
| 用户实例异常/被清 | `journalctl -u dshs | grep -E "crash-restart|circuit-open|idle-reap"` |
|
||
| 实例反复起不来 | 同上(含 `crash-loop-circuit-open` = 已熔断,需用户重新进入或人工介入) |
|
||
| 会话「连接中」/卡顿 | 先看 nginx 是否仍 `proxy_buffering off`;再看 `/www/wwwlogs/alotbuy.com.log` 的状态码与耗时 |
|
||
| 日志里客户端 IP 全是 CF 段 | vhost 的 `real_ip_header CF-Connecting-IP` 是否被覆盖 |
|
||
| 证书告警/续期失败 | renewal conf 是否含 `dns_cloudflare_propagation_seconds = 60` |
|
||
| 需要看实例资源 | `ps -eo pid,user,pcpu,rss,args | grep "[d]sh --profile"`;scope 名见 journald |
|
||
|
||
> 注:诊断实例内文件系统时,`pgrep -f "dsh --profile web"` 会先命中 **bwrap 包装进程(root)**,在其命名空间内探测会得出"全部可读写"的**假象**——必须取 `ps -eo user,args` 中 user 为 `dsh-*` 的子进程(档案 17 §一)。
|
||
|
||
### C.5 本部署**未使用/未验证**的子系统(升级 dshs 时重点回归,档案 19 §C8)
|
||
|
||
| 子系统 | 文件 | 本部署情况 |
|
||
|---|---|---|
|
||
| Kubernetes 后端 | `src/supervisor/k8s-spawner.ts`(679 行)、`deploy/*.yaml` | **未使用**(`ISOLATION_MODE=account`);升级时需按"未验证"对待 |
|
||
| PostgreSQL 后端 | `src/db/pg.ts`(508 行) | **未使用**(本部署用 sqlite/better-sqlite3) |
|
||
| Leader 选举 | `src/cluster/leader.ts`(254 行) | **未使用**(单实例部署) |
|
||
| 对账 | `src/supervisor/reconcile.ts` | 仅 k8s 路径使用 |
|
||
|
||
> 结论:可保留上游能力,但**任何涉及它们的改动都不应在生产直接验证**;升级后若这些文件报编译错误,优先改为"绕过/排除"而非线上调试。
|
||
|
||
### C.6 上传安全扫描:分级(2026-09-11,档案 19 §C6)
|
||
|
||
| 档 | 行为 | 规则 |
|
||
|---|---|---|
|
||
| **P0 阻断** | 命中即 **HTTP 400** 拒绝上传 | 原有 7 条(`rm -rf /`、`curl \| sh`、读系统凭证/SSH 私钥/云凭证/平台数据目录/`.credentials.yaml`)+ 新增:`nc -e` 反弹、`/dev/tcp` 反弹、base64 解码后直接执行、给系统二进制加 setuid、fork 炸弹 |
|
||
| **P1 告警** | **不阻断**,写日志 + 作为 findings 返回 | 动态执行子进程、`vm`/`eval`/`new Function`、敏感 env 读取、超长 base64、外部网络访问、隐藏目录/`.git` |
|
||
|
||
- 覆盖面:文本扩展名 **19 → 31**(含 `.toml/.ini/.conf/.env/.rs/.go/.java/.php/.rb/.pl/.lua/.sql/.ps1/.bat`…);无扩展名按 **shebang** 判定;二进制按 `0x00` 跳过;体积上限 **2MB → 8MB**。
|
||
- 排查:`journalctl -u dshs | grep upload-scan-warnings`(P1 告警);P0 直接体现在上传接口的 400 响应里。
|
||
- 冒烟(可复跑):`node /tmp/scan-smoke.cjs` 思路见档案 19 §C6 记录(P1 不阻断 / P0 阻断 / 良性零误报)。
|
||
|
||
### C.7 用户数据清理(档案 28,cron 自动跑)
|
||
|
||
| 任务 | 时间 | 阈值/年龄 | 日志 |
|
||
|---|---|---|---|
|
||
| `storage-report.cjs` | 每小时 05 分 | 生成 `/var/run/dsh-storage-report.json`(门户「存储用量」面板的数据源) | `/var/log/dsh-storage-report.log` |
|
||
| `ws-cleanup.cjs --apply` | 每天 04:10 | ws ≥ 2048 MB;清 T1 平台产物 + T2 超 90 天顶层脚本(`ws/.keep` 可豁免 T2) | `/var/log/dsh-ws-cleanup.log` |
|
||
| `session-gc.cjs --apply` | 每天 04:20 | sessions ≥ 1024 MB;清超 **365 天**会话(并同步回收 projcache) | `/var/log/dsh-session-gc.log` |
|
||
| `purge-trash.sh 30` | 每天 04:40 | 回收站超 30 天 | `/var/log/dsh-trash-purge.log` |
|
||
|
||
- **所有清理都先 `mv` 到 `<userRoot>/trash/<日期>-<任务>/`**(30 天可恢复);只有 `purge-trash.sh` 会真删。
|
||
- 手动排查:`node scripts/ws-cleanup.cjs`(dry-run 出画像)、`node scripts/session-gc.cjs`、`node scripts/clean-ws-pollution.cjs`。
|
||
- 三档口径:T1 平台产物/明确垃圾直接清;T2 顶层一次性脚本 _超 90 天_ 才清;**T3 其余(含所有目录)永不自动删**。
|
||
(域名 / 证书 / nginx / 平台脚本 / 排障速查)
|
||
|
||
### C.8 备份与恢复(2026-09-11 修复:脚本曾有但不可用)
|
||
|
||
**问题**:`/opt/dsh/backup.sh` 存在但**无执行权限**(`-rw-------`)且**未接任何调度** → 最后一次全量备份停留在 2026-09-08。
|
||
|
||
**现状(已修)**:
|
||
- 脚本:`/opt/dsh/backup.sh`(副本入库 `scripts/backup-platform.sh`),`chmod +x`,并加固为
|
||
**① SQLite 一致性快照**(`sqlite3 .backup`,避免运行中只拷到 `-wal` 造成库不一致)
|
||
**② 归档内容扩展**:`var/lib/dshs`(用户数据)+ `etc/dshs.env` + systemd 单元 + `opt/dsh/artifacts`(插件包)+ `opt/dshs/scripts` + `etc/cron.d/dsh-*` + `nftables-dsh-egress.nft` + `www/server/panel/vhost/nginx`(可重建)
|
||
- 调度:`/etc/cron.d/dsh-backup` —— **每周日 05:00 全量** + 05:30 清理超 60 天的旧备份;日志 `/var/log/dsh-backup.log`
|
||
- 产物:`/opt/dsh/backups/dsh-platform-backup-<TS>.tar.gz`(全量)+ `…-db-snapshot.tar.gz`(DB 快照)
|
||
|
||
**恢复(演练已验证)**:
|
||
```bash
|
||
B=/opt/dsh/backups/dsh-platform-backup-<TS>.tar.gz
|
||
tar -tzf "$B" | head # 先看清单
|
||
tar -xzf "$B" -C / # 原路径还原(谨慎!会覆盖现网文件)
|
||
sqlite3 /var/lib/dshs/dshs.db "select count(*) from users;" # 校验
|
||
```
|
||
|
||
**实测证据**:首跑全量 **8.7 MB / 6919 条目**(users 6858 + DB + env + artifacts + scripts 20 + cron 2 + nginx vhost 22 + systemd 3);**恢复演练**解出 DB 并查询 `users` = 2 行 ✓。
|