KAIROSLLL/dsh-plugin-oss-backup ↗★ 0

dsh-plugin-oss-backup

DeepSeek Harness 插件:把工作区文件备份到 S3 兼容对象存储(RustFS / 阿里云 OSS / AWS S3)。AK/SK 只从环境变量读取,绝不落盘、绝不进日志。 适合需要将本地工作区自动备份到阿里云OSS或AWS S3的用户。

パッケージ
dsh-plugin-oss-backup
互換性
未検証
バージョン
0.23.1
ライセンス
MIT
最終更新
2026/10/11

インストール

$npx -p @deepseek-ai/dsh dsh plugin --profile web add github:KAIROSLLL/dsh-plugin-oss-backup

ドキュメント

README 全文を読む ↗

三、配置(照着做就行)

第 1 步:决定用哪个 provider

选一个就好:

OSS_PROVIDER=rustfs     # 默认值,自建服务(RustFS / MinIO)
OSS_PROVIDER=aliyun     # 阿里云 OSS
OSS_PROVIDER=aws        # AWS S3

不设置 OSS_PROVIDER 时按 rustfs 处理。

第 2 步:创建最小权限子账号(必做)

不要跳过这一步。 用主账号密钥意味着一旦泄漏,整个云账号都危险。

阿里云 OSS(RAM 子账号)

  1. 控制台 → RAM 访问控制 → 用户 → 创建用户,勾选"OpenAPI 调用访问" (只拿 AccessKey,不要开控制台登录);
  2. 创建自定义权限策略(不要用 AliyunOSSFullAccess 这类系统策略),把 dsh-backup 换成你自己的桶名:
{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["oss:PutObject", "oss:GetObject", "oss:HeadObject"],
      "Resource": ["acs:oss:*:*:dsh-backup/*"]
    },
    {
      "Effect": "Allow",
      "Action": ["oss:ListObjects", "oss:GetBucketInfo"],
      "Resource": ["acs:oss:*:*:dsh-backup"]
    }
  ]
}
  1. 把这个策略授权给刚建的用户;
  2. 记下它的 AccessKey ID 与 Secret,下一步要用。

AWS S3(IAM 用户)

把上面的策略换成 IAM 语法即可(Resource 形如 arn:aws:s3:::dsh-backup/*):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowBackupObjectsOnly",
      "Effect": "Allow",
      "Action": ["s3:PutObject", "s3:GetObject", "s3:DeleteObject"],
      "Resource": ["arn:aws:s3:::dsh-backup/*"]
    },
    {
      "Sid": "AllowListOwnBucket",
      "Effect": "Allow",
      "Action": ["s3:ListBucket", "s3:GetBucketLocation"],
      "Resource": ["arn:aws:s3:::dsh-backup"]
    }
  ]
}

RustFS / MinIO

mc admin user add  dsh-backup-bot ''
mc admin policy create  dsh-backup-rw ./dsh-backup-rw.json   # 内容即上面的通用策略
mc admin policy attach  dsh-backup-rw --user dsh-backup-bot

第 3 步:设置环境变量

挑你自己那一档,把值换成上面拿到的密钥。

强烈建议优先用密钥管理器接入。 下面的 setx 示例只是"最不依赖任何工具"的写法; 更好的做法是让 1Password / Vault / AWS Secrets Manager 这类工具在启动 dsh web 时 把凭证注入环境变量 —— 密钥就不用出现在 shell 历史、脚本或任何磁盘文件里。 完整的接入清单(1Password / Bitwarden / Vault / AWS / Azure / Doppler / direnv / Docker / systemd)见 凭证接入指南。

先说清一件事:值从哪来都行。 插件只关心"这个键在你的进程环境里存不存在", 不关心它怎么来的。下面这些完全等价,用你顺手的那个:

来源怎么做
密钥管理器(推荐)op run -- dsh web(1Password)、doppler run -- dsh web、Vault / AWS Secrets Manager 等 —— 见接入指南
系统环境变量Windows setx、macOS / Linux export(下面就是这一种)
.env 文件复制 .env.example 改成 .env,放到 /oss-backup/.env 或当前工作目录 —— 插件启动时自动读(只适合本地临时用,务必确认它被 gitignore)
Dockerdocker run -e RUSTFS_ACCESS_KEY=...
CIGitHub Actions Secrets / GitLab CI Variables 之类

.env 的几条规则(只读,插件永不写入):

  • 优先级:.env > 系统环境变量。.env 是你显式写下来的那份,比机器上残留的 旧 export 更权威 —— 否则"我在 .env 里改了却毫无效果"会非常难查。 被覆盖的键名会进日志与界面(只有名字,没有值);
  • 只采用第一个能读到的文件(先看 /oss-backup/.env,再看当前工作目录), 不做多层合并 —— 否则出问题时没法解释某个值来自哪份文件;
  • 解析规则就那几条:KEY=value、KEY="value"、export KEY=value、# 注释。 不支持行内注释、多行值、变量展开 —— 支持它们只会让"值被悄悄改掉"成为可能的解释;
  • 改了 .env 要重启 dsh web 才生效(和系统环境变量一样)。

① RustFS / MinIO

setx OSS_PROVIDER      "rustfs"
setx RUSTFS_ENDPOINT   "http://127.0.0.1:9000"
setx RUSTFS_BUCKET     "dsh-backup"
setx RUSTFS_ACCESS_KEY ""
setx RUSTFS_SECRET_KEY ""

② 阿里云 OSS

setx OSS_PROVIDER           "aliyun"
setx OSS_BUCKET             "dsh-backup"
setx OSS_REGION             "oss-cn-hangzhou"        # 默认就是这个

### 第 5 步:确认配置成功

重启后打开 **设置 → 对象存储备份**,看「**环境自检**」:

- 状态应显示「**就绪**」;
- `provider` 一行会写明当前用的是哪一档(例如 `RustFS / MinIO 等自建 S3 兼容端点 · path-style`);
- 密钥两行应显示「**已设置(值不显示)**」。

**⚠️ 「就绪」不只看变量配没配齐**:插件会真的对存储桶发一次 `HeadBucket` 去确认服务能连上
(结果缓存 30 秒)。所以还有第三种状态:

| 状态 | 含义 | 怎么办 |
| --- | --- | --- |
| **就绪** | 变量齐了,服务也连得上 | 可以直接用 |
| **变量齐了,但服务连不上** | 端点/桶/密钥有一处不对,或者服务根本没启动 | 看下方红字里的具体原因 |
| **缺少 N 个变量** | 环境变量没配齐 | 那一行会列出缺哪个变量名 |

「服务连不上」这一条是专门为下面这种情况加的:**变量都填好了、但 RustFS 没开** ——
以前会显示成"就绪",看着一切正常,实际上每次上传都在连接被拒。

> **小贴士:`setx` 设了但插件说"未配置"?**
> `setx` 写入的是用户级环境变量,而已启动的进程(尤其是从快捷方式、启动器拉起来的
> `dsh web`)**不一定继承得到它**。所以插件每次启动都会主动去读一遍系统里的环境变量
> (先看进程已有的,再看用户级,最后看系统级),缺什么补什么 —— **不会覆盖你已经设好的值**。
> 界面上会标注「当前来自系统环境变量(启动时自动补读:…)」,看到它就说明补读成功了。
> 你只需要保证**设过值 + 重启过一次**。

---

## 四、使用

### 手动备份一个文件夹

1. 打开 **设置 → 对象存储备份 → 设置**;
2. 在「**备份目录**」里填要备份的目录(例如 `D:\projects\my-project`);
3. 点「**保存**」;
4. 回到「**手动备份**」块,点「**立即备份**」。

它会**递归备份整个文件夹**,完成后显示:共多少个文件 → 成功几个 / 失败几个 · 总大小。

**没变化的文件会自动跳过。** 插件在 `~/.dsh/oss-backup/uploaded.json` 里记着每个对象键对应的
`(文件大小, 修改时间)`,下次备份时一致的直接跳过 —— 所以重复点「立即备份」只会传真正改过的
那几个,不会再把你几千个没动过的文件重传一遍。界面会显示「跳过 N 个未变化的」。

> 判定依据是**大小 + 修改时间**。改了内容一般也就会改修改时间,所以会被正确重传;
> 只有"内容变了但大小和修改时间都没变"这种刻意为之的情况才会漏判。
> 换机器或删掉那个索引文件也不影响正确性 —— 只是会重传一次,之后恢复正常。

上传后的对象路径规则是 **前缀 + 被备份目录自己的名字 + 相对路径**。例如备份
`D:\projects\projects\yy`(里面是 `11`、`22` 两个子文件夹),桶里会出现:

workspace-backup/yy/11/... workspace-backup/yy/22/...


**`yy` 这一层会保留**,这样在桶里一眼就能看出这批文件属于哪个目录。

> **会自动跳过的目录**:`node_modules`、`.git`、`.hg`、`.svn`、`.pnpm-store`、`.npm-cache`、
> `__pycache__`、`.venv`。这些要么体积巨大,要么是可再生的产物。
> 想让备份"一个文件都不少",把设置里的「**跳过依赖 / 缓存目录**」开关关掉即可。
>
> **限额**:最多 2000 个文件、单个文件最大 5 GB、总量 50 GB。触发上限时界面会提示
> 「已触到文件数上限」。
>
> **超过 8 MB 的文件走分片上传**(`@aws-sdk/lib-storage`,每片 8 MB、4 片并发),
> 内存占用恒定、与文件大小无关 —— 所以单文件上限敢放到 5 GB(S3 单次 PUT 的上限;
> 分片协议本身允许到 5 TB)。这几个数只是**防呆**,用来拦住"误选了整个磁盘"。
>
> **误选了也没关系:备份途中可以点「强行打断」。** 它会立刻停下 —— 正在传的那个文件
> 被掐断(在途的分片请求一并中止),已经传完的不受影响,也不会被记成"失败"。
> 下次备份会从没传完的地方继续(查重会跳过已成功的那部分)。

### 《接数据》(备份前的小游戏)

点「**立即备份**」时,如果游戏开关是开着的,会先在「**上传状态**」那一格里来一局节奏小游戏
《**接数据**》—— **通关了才真的开始备份**。
不想玩的话,把设置里的开关关掉,之后点按钮直接开始。

**怎么玩**

- 四条轨道,方块从右往左滑;**键盘 `D` `F` `J` `K`** 对应四条轨道(键位标在判定线左侧,一直在),
  也可以**直接用鼠标点那条轨道**;
- 左侧有一条**判定线**,方块经过它的一瞬间点掉它;
- 判定分三级,**窗口挺严的**:

| 判定 | 条件 | 扣分 |
| --- | --- | --- |
| **PERFECT** | ±0.09 秒内点中 | **不扣分** |
| **GOOD** | ±0.175 秒内(不够准) | 扣 0.5 个 |
| **LOST** | 过了 0.175 秒都没点 | 扣 1 个 |

命中时判定线会亮起、对应轨道扫过一层光、判定处扩散一圈光环 —— PERFECT 是品牌色、GOOD 是灰色,
一眼就能看出自己准不准。

**四档难度**

| 难度 | 一局时长 | 特点 |
| --- | --- | --- |
| 杂鱼 | 约 22 秒 |
| 简单 | 约 29 秒 |
| 中等 | 约 36 秒 |
| **齁哦哦哦** | 约 45 秒 |

难度会记住,下次打开还是这一档。

**计分规则**

满分 100 分,**扣分按总方块数分摊**:

扣分 = (GOOD × 0.5 + LOST × 1) ÷ 总方块数 × 100


也就是 **PERFECT 完全不扣分**,GOOD 扣半个,LOST 扣一个。参考:

- 全部 PERFECT → 100 分;
- 全部 GOOD → 50 分(**这也会失败**);
- 全部 LOST → 0 分。

结果为:

- 扣分 ** 用"按总数分摊"而不是"每个失败固定扣几分",是为了让四档难度的容错率一致 ——
> 高难度方块多得多,固定扣分会让它直接变成不可能完成。

### 文件改动自动备份

在设置里把「**自动备份**」开关打开(点一下立即生效)。

之后每次模型保存文件,该文件会在**停止改动 2 秒后**自动上传;上传失败会自动重试 3 次
(间隔 1 秒 / 2 秒 / 4 秒),仍失败就只记一条日志,**不会影响你正常的写文件操作**。
开关关闭时完全不生效,这是默认状态。

**连不上时直接不提供上传**

- **确认连不上之后,所有上传入口一起停**:模型调 `oss_backup` 会被直接拒绝、
  「立即备份」按钮变灰并说明原因、自动备份与定期备份都不再排期;
- **连接 / 凭证 / 桶名这类错误不重试** —— 它们不会因为多等 4 秒就变好,直接失败;
- **连续失败 3 次也会熔断**,暂停 60 秒,期间的文件改动直接跳过、不排队;
- 界面上写明原因(例如 `ECONNREFUSED`),**服务恢复后自动重新可用**,不用重启。

> 注意:这个开关监听的是**模型的文件写入操作**。如果你用命令行工具(例如 `git`、
> 脚本)直接改文件,不会触发自动备份 —— 那种情况请用手动备份。

### 定期全量备份

在设置界面的「**定期全量备份**」一行选模式(改完点「保存」生效),宿主会按时间表把**整个备份目录
完整传一遍** —— 与上面「文件改动自动备份」是**两套独立**的机制,可以只开一个,也可以都开。

| 模式 | 说明 | 例子 |
| --- | --- | --- |
| **关闭** | 默认,不排期 | — |
| **按间隔** | 每 N 分钟 / 小时 / 天 | 每 6 小时 |
| **每天** | 每天固定时刻 | 每天 03:00 |
| **每月** | 每月固定几号的固定时刻 | 每月 1 日 03:00 |

设置那一行下面会显示**下次实际触发时间**,方便确认排期对不对。

几个刻意的设计,避免你踩坑:

- **固定节拍**:「按间隔」对齐到**排定时刻**,不是"从现在往后加一段"。每 30 分钟就是
  `:00` 和 `:30`,每 6 小时就是 `00/06/12/18`,「每 N 天」对齐到当地零点 ——
  一次备份跑多久都不会把后续时间点往后推;
- 内部用的是"跑完再排下一次"的链式定时器(不是 `setInterval`),所以也不会堆叠出好几次备份;
- **上一次还在跑就跳过这一轮**(不排队),日志里会记一条"跳过";
- **每月只支持 1-28 号**:这样每个月都一定存在,不会遇到"2 月没有 30 号"那种要么跳过一个月、
  要么被顺延到 3 月 1 号的情况;
- **还没设置备份目录时不会排期**,只记一条日志提醒你;
- 时间点**用的是本机时区**。

> 备份范围、排除规则(`node_modules` / `__pycache__` 等)和文件数 / 体积限额,
> 与「手动备份」完全一致 —— 它俩共用同一套逻辑。

### 凭证(在设置界面里临时填写密钥)

如果你不想用 `setx`,也可以在设置界面的「**凭证**」块里直接填 AK/SK,点
「**应用到本次进程**」:

- **填完立刻生效**,可以马上试一次手动备份;
- **只对本次运行有效** —— 重启 `dsh web` 后需要重新填;
- 提交后输入框会立即清空,界面上**永远不会回显**你填过的值;
- 想让配置永久有效、以后都不用填,还是用 `setx`(见第三节)。

### 高级设置

展开「**高级设置**」,可以改这些**非机密**项(会保存下来,重启后仍在):

| 项 | 说明 |
| --- | --- |
| 对象键前缀 | 桶里的目录层级,默认 `workspace-backup/` |
| Provider | 覆盖 `OSS_PROVIDER`(留空则用环境变量) |
| 端点 | 覆盖端点(留空则用环境变量或按区域自动推导) |
| 存储桶 | 覆盖桶名(留空则用环境变量) |
| 区域 | 覆盖区域(留空则用环境变量或默认值) |

> 优先级:**Provider 以设置页为准**(这是你在界面上明确选的),端点 / 桶 / 区域
> **以环境变量为准**(已经设过的不会被悄悄改掉)。

### 让模型上传单个文件

模型可以直接调用 `oss_backup` 工具,传一个本地文件和目标对象路径,例如:

> 把 `D:\backup\report.tar.gz` 上传到对象存储,`objectKey` 用 `backups/report.tar.gz`

这是**单文件**上传;要备份整个文件夹请用上面的「手动备份」。

### 云端备份按钮

「**云端备份**」按钮会打开 RustFS 控制台(`http://127.0.0.1:9001/`),
方便你在网页里核对桶里的内容。用阿里云 / AWS 时它不会指向对应控制台,
请自行打开服务商后台查看。

---