@shellus/way 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -30
- package/dist/cli.js +88 -14
- package/package.json +1 -1
- package/vendor/restic/linux-x64/VERSION +1 -1
- package/vendor/restic/linux-x64/restic +0 -0
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
## 依赖
|
|
69
69
|
|
|
70
70
|
- Node.js >= 18
|
|
71
|
-
- Linux x64 平台内置 [restic](https://restic.net/) 0.
|
|
71
|
+
- Linux x64 平台内置 [restic](https://restic.net/) 0.18.1,其他平台需自行安装 restic
|
|
72
72
|
|
|
73
73
|
`way` 查找 restic 的顺序:
|
|
74
74
|
|
|
@@ -85,7 +85,7 @@ npm install -g @shellus/way
|
|
|
85
85
|
Linux x64 用户无需额外安装 restic。如需使用自定义 restic,可设置:
|
|
86
86
|
|
|
87
87
|
```bash
|
|
88
|
-
WAY_RESTIC_BIN=/usr/local/bin/restic way snapshots
|
|
88
|
+
WAY_RESTIC_BIN=/usr/local/bin/restic way restic snapshots
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
## 快速开始
|
|
@@ -124,18 +124,23 @@ way systemd status
|
|
|
124
124
|
### 4. 手动执行备份
|
|
125
125
|
|
|
126
126
|
```bash
|
|
127
|
-
way
|
|
128
|
-
way
|
|
129
|
-
way
|
|
127
|
+
way backup # 备份所有项目
|
|
128
|
+
way backup data # 只备份 data 项目
|
|
129
|
+
way restore data --target /tmp/restore --dry-run # 按规则预演恢复
|
|
130
|
+
way restic snapshots # 查看快照
|
|
130
131
|
```
|
|
131
132
|
|
|
132
133
|
## 命令参考
|
|
133
134
|
|
|
134
135
|
```bash
|
|
135
136
|
# 备份命令
|
|
136
|
-
way
|
|
137
|
-
way
|
|
138
|
-
way
|
|
137
|
+
way backup # 执行所有项目备份
|
|
138
|
+
way backup data # 只备份 data 项目
|
|
139
|
+
way backup data config # 备份多个项目
|
|
140
|
+
way backup --dry-run # 模拟备份(不实际写入)
|
|
141
|
+
way restore data --target /tmp/restore # 按规则恢复 data 项目
|
|
142
|
+
way restore data --target /tmp/restore --dry-run # 模拟恢复
|
|
143
|
+
way restore data --target /tmp/restore --delete # 删除目标中快照不存在的文件
|
|
139
144
|
way gc # 按 retention 策略清理旧快照
|
|
140
145
|
way gc --dry-run # 模拟清理(不实际删除)
|
|
141
146
|
|
|
@@ -148,14 +153,14 @@ way systemd show # 显示 systemd 配置
|
|
|
148
153
|
way systemd status # 查看服务状态
|
|
149
154
|
way systemd uninstall # 卸载服务
|
|
150
155
|
|
|
151
|
-
#
|
|
152
|
-
way snapshots
|
|
153
|
-
way
|
|
154
|
-
way
|
|
155
|
-
way
|
|
156
|
+
# 显式透传 restic(way 只设置环境变量)
|
|
157
|
+
way restic snapshots # → restic snapshots
|
|
158
|
+
way restic check # → restic check
|
|
159
|
+
way restic stats # → restic stats
|
|
160
|
+
way restic restore abc123 --target /tmp/restore # → restic restore abc123 --target /tmp/restore
|
|
156
161
|
|
|
157
162
|
# 指定 repository(默认用 repositories.yaml 中的 default)
|
|
158
|
-
way --remote=oss snapshots
|
|
163
|
+
way --remote=oss restic snapshots
|
|
159
164
|
```
|
|
160
165
|
|
|
161
166
|
## 生命周期
|
|
@@ -186,7 +191,7 @@ cp ~/.way/rules.yaml.example ~/.way/rules.yaml
|
|
|
186
191
|
也可以通过 `WAY_DIR` 环境变量指定其他目录:
|
|
187
192
|
|
|
188
193
|
```bash
|
|
189
|
-
WAY_DIR=/path/to/config way snapshots
|
|
194
|
+
WAY_DIR=/path/to/config way restic snapshots
|
|
190
195
|
```
|
|
191
196
|
|
|
192
197
|
### rules.yaml
|
|
@@ -287,42 +292,68 @@ gpg -c way-config-$(date +%Y%m%d).tar.gz
|
|
|
287
292
|
|
|
288
293
|
## 数据恢复
|
|
289
294
|
|
|
290
|
-
way
|
|
295
|
+
way 提供两种恢复方式:
|
|
296
|
+
|
|
297
|
+
1. **按规则恢复**:使用 `way restore`,根据 `rules.yaml` 中的项目路径和 `way:<project>` 标签恢复。
|
|
298
|
+
2. **原始 restic 恢复**:使用 `way restic restore`,显式透传 restic 参数。
|
|
291
299
|
|
|
292
300
|
### 1. 查看快照列表
|
|
293
301
|
|
|
294
302
|
```bash
|
|
295
|
-
way snapshots # 列出所有快照
|
|
296
|
-
way snapshots --tag=way:home # 只看 home 项目的快照
|
|
297
|
-
way snapshots --tag=way:data # 只看 data 项目的快照
|
|
303
|
+
way restic snapshots # 列出所有快照
|
|
304
|
+
way restic snapshots --tag=way:home # 只看 home 项目的快照
|
|
305
|
+
way restic snapshots --tag=way:data # 只看 data 项目的快照
|
|
298
306
|
```
|
|
299
307
|
|
|
300
308
|
### 2. 浏览快照内容
|
|
301
309
|
|
|
302
310
|
```bash
|
|
303
|
-
way ls <snapshot-id> # 列出快照中的所有文件
|
|
304
|
-
way ls <snapshot-id> /root/.ssh # 列出快照中指定目录的文件
|
|
305
|
-
way find --snapshot <id> <filename> # 在快照中搜索文件
|
|
311
|
+
way restic ls <snapshot-id> # 列出快照中的所有文件
|
|
312
|
+
way restic ls <snapshot-id> /root/.ssh # 列出快照中指定目录的文件
|
|
313
|
+
way restic find --snapshot <id> <filename> # 在快照中搜索文件
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### 3. 按规则恢复项目
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
# 恢复 data 项目的最新快照到指定目录
|
|
320
|
+
way restore data --target /tmp/restore
|
|
321
|
+
|
|
322
|
+
# 恢复所有项目
|
|
323
|
+
way restore --target /tmp/restore
|
|
324
|
+
|
|
325
|
+
# 指定快照 ID
|
|
326
|
+
way restore data --snapshot abc123 --target /tmp/restore
|
|
327
|
+
|
|
328
|
+
# 预演恢复,不实际写入
|
|
329
|
+
way restore data --target /tmp/restore --dry-run
|
|
330
|
+
|
|
331
|
+
# 让目标目录与快照一致,先 dry-run 核对删除清单
|
|
332
|
+
way restore data --target /tmp/restore --delete --dry-run
|
|
333
|
+
way restore data --target /tmp/restore --delete
|
|
306
334
|
```
|
|
307
335
|
|
|
308
|
-
|
|
336
|
+
`way restore` 会为项目自动添加 `--tag=way:<project>` 和项目 `paths` 对应的 `--include` 参数。
|
|
337
|
+
|
|
338
|
+
### 4. 原始 restic 恢复
|
|
309
339
|
|
|
310
340
|
```bash
|
|
311
|
-
#
|
|
312
|
-
way restore <snapshot-id> --target /tmp/restore
|
|
341
|
+
# 显式透传 restic restore
|
|
342
|
+
way restic restore <snapshot-id> --target /tmp/restore
|
|
313
343
|
|
|
314
344
|
# 只恢复特定路径
|
|
315
|
-
way restore <snapshot-id> --target /tmp/restore --include /root/.ssh
|
|
345
|
+
way restic restore <snapshot-id> --target /tmp/restore --include /root/.ssh
|
|
316
346
|
|
|
317
347
|
# 恢复单个文件到标准输出(适合快速查看)
|
|
318
|
-
way dump <snapshot-id> /root/.gitconfig
|
|
348
|
+
way restic dump <snapshot-id> /root/.gitconfig
|
|
319
349
|
```
|
|
320
350
|
|
|
321
|
-
###
|
|
351
|
+
### 5. 从其他仓库恢复
|
|
322
352
|
|
|
323
353
|
```bash
|
|
324
|
-
way --remote=oss snapshots
|
|
325
|
-
way --remote=oss restore
|
|
354
|
+
way --remote=oss restic snapshots
|
|
355
|
+
way --remote=oss restore data --target /tmp/restore
|
|
356
|
+
way --remote=oss restic restore <snapshot-id> --target /tmp/restore
|
|
326
357
|
```
|
|
327
358
|
|
|
328
359
|
## 开发
|
package/dist/cli.js
CHANGED
|
@@ -80,6 +80,19 @@ function buildBackupArgs(name, project, globalExcludes) {
|
|
|
80
80
|
args.push(...project.paths);
|
|
81
81
|
return args;
|
|
82
82
|
}
|
|
83
|
+
function buildRestoreArgs(name, project, options) {
|
|
84
|
+
const args = [
|
|
85
|
+
"restore",
|
|
86
|
+
options.snapshot || "latest",
|
|
87
|
+
`--tag=way:${name}`,
|
|
88
|
+
`--target=${options.target}`
|
|
89
|
+
];
|
|
90
|
+
for (const path4 of project.paths) args.push(`--include=${path4}`);
|
|
91
|
+
if (options.dryRun) args.push("--dry-run");
|
|
92
|
+
if (options.delete) args.push("--delete");
|
|
93
|
+
if (options.verbose) args.push("--verbose=2");
|
|
94
|
+
return args;
|
|
95
|
+
}
|
|
83
96
|
function buildS3Options(repo) {
|
|
84
97
|
const options = [];
|
|
85
98
|
if (repo.options?.bucket_lookup) {
|
|
@@ -100,8 +113,8 @@ async function execRestic(args, env, s3Options = []) {
|
|
|
100
113
|
}
|
|
101
114
|
}
|
|
102
115
|
|
|
103
|
-
// src/commands/
|
|
104
|
-
async function
|
|
116
|
+
// src/commands/backup.ts
|
|
117
|
+
async function backup(options) {
|
|
105
118
|
const wayDir = process.env.WAY_DIR || `${process.env.HOME}/.way`;
|
|
106
119
|
const config = loadConfig(wayDir, options.remote);
|
|
107
120
|
const env = buildResticEnv(config.repository);
|
|
@@ -157,6 +170,47 @@ async function notifyUptimeKuma(result, pushUrl) {
|
|
|
157
170
|
}
|
|
158
171
|
}
|
|
159
172
|
|
|
173
|
+
// src/commands/restore.ts
|
|
174
|
+
async function restore(options) {
|
|
175
|
+
if (!options.target) throw new Error("--target is required");
|
|
176
|
+
const wayDir = process.env.WAY_DIR || `${process.env.HOME}/.way`;
|
|
177
|
+
const config = loadConfig(wayDir, options.remote);
|
|
178
|
+
const env = buildResticEnv(config.repository);
|
|
179
|
+
const s3Options = buildS3Options(config.repository);
|
|
180
|
+
const projects = options.projects && options.projects.length > 0 ? options.projects : Object.keys(config.rules.projects);
|
|
181
|
+
const succeeded = [];
|
|
182
|
+
const failed = [];
|
|
183
|
+
const startTime = Date.now();
|
|
184
|
+
for (const projectName of projects) {
|
|
185
|
+
const project = config.rules.projects[projectName];
|
|
186
|
+
if (!project) {
|
|
187
|
+
console.error(`Project not found: ${projectName}`);
|
|
188
|
+
failed.push(projectName);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
console.log(`=== Restoring: ${projectName} ===`);
|
|
192
|
+
try {
|
|
193
|
+
const args = buildRestoreArgs(projectName, project, {
|
|
194
|
+
target: options.target,
|
|
195
|
+
snapshot: options.snapshot,
|
|
196
|
+
dryRun: options.dryRun,
|
|
197
|
+
delete: options.delete,
|
|
198
|
+
verbose: options.verbose
|
|
199
|
+
});
|
|
200
|
+
await execRestic(args, env, s3Options);
|
|
201
|
+
succeeded.push(projectName);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
console.error(`Failed to restore ${projectName}:`, error);
|
|
204
|
+
failed.push(projectName);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const duration = Date.now() - startTime;
|
|
208
|
+
console.log("\n=== Summary ===");
|
|
209
|
+
if (succeeded.length > 0) console.log("Succeeded:", succeeded.join(", "));
|
|
210
|
+
if (failed.length > 0) console.log("Failed:", failed.join(", "));
|
|
211
|
+
return { succeeded, failed, duration };
|
|
212
|
+
}
|
|
213
|
+
|
|
160
214
|
// src/commands/gc.ts
|
|
161
215
|
async function gc(options) {
|
|
162
216
|
const wayDir = process.env.WAY_DIR || `${process.env.HOME}/.way`;
|
|
@@ -267,7 +321,7 @@ async function daemon(options) {
|
|
|
267
321
|
cron.schedule(schedule, () => {
|
|
268
322
|
executeTask(async () => {
|
|
269
323
|
console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] Running backup: ${name}`);
|
|
270
|
-
await
|
|
324
|
+
await backup({ remote: options.remote, projects: [name] });
|
|
271
325
|
});
|
|
272
326
|
});
|
|
273
327
|
console.log(`Scheduled backup for ${name}: ${schedule}`);
|
|
@@ -303,23 +357,40 @@ async function daemon(options) {
|
|
|
303
357
|
|
|
304
358
|
// src/cli.ts
|
|
305
359
|
var program = new Command();
|
|
306
|
-
program.name("way").version("0.
|
|
360
|
+
program.name("way").version("0.6.0").description("\u7B56\u7565\u5907\u4EFD\u5DE5\u5177 - \u57FA\u4E8E restic \u7684\u7B56\u7565\u5C01\u88C5").option("--remote <name>", "\u6307\u5B9A\u4ED3\u5E93", "default").addHelpText("after", `
|
|
307
361
|
\u793A\u4F8B:
|
|
308
|
-
$ way
|
|
309
|
-
$ way
|
|
310
|
-
$ way
|
|
362
|
+
$ way backup \u6267\u884C\u6240\u6709\u9879\u76EE\u5907\u4EFD
|
|
363
|
+
$ way backup data \u53EA\u5907\u4EFD data \u9879\u76EE
|
|
364
|
+
$ way backup --dry-run \u6A21\u62DF\u5907\u4EFD\uFF08\u4E0D\u5B9E\u9645\u5199\u5165\uFF09
|
|
365
|
+
$ way restore data --target /tmp/restore --dry-run
|
|
311
366
|
$ way gc \u6E05\u7406\u65E7\u5FEB\u7167
|
|
312
367
|
$ way systemd install \u5B89\u88C5\u5B9A\u65F6\u4EFB\u52A1
|
|
313
|
-
$ way snapshots
|
|
314
|
-
$ way
|
|
368
|
+
$ way restic snapshots \u67E5\u770B\u5FEB\u7167\u5217\u8868
|
|
369
|
+
$ way restic restore abc123 --target /tmp/restore
|
|
370
|
+
$ way --remote=s3 restic snapshots \u4F7F\u7528 s3 \u4ED3\u5E93
|
|
315
371
|
|
|
316
372
|
\u6587\u6863: https://github.com/shellus/way
|
|
317
373
|
`);
|
|
318
|
-
|
|
374
|
+
function collectBackupArgs(command) {
|
|
375
|
+
return command.args.filter((a) => a.startsWith("-") && !["--dry-run"].includes(a));
|
|
376
|
+
}
|
|
377
|
+
program.command("backup [projects...]").description("\u6309 rules.yaml \u6267\u884C\u5907\u4EFD").option("--dry-run", "\u6A21\u62DF\u5907\u4EFD\uFF08\u4E0D\u5B9E\u9645\u5199\u5165\uFF09").allowUnknownOption().allowExcessArguments().action(async function(projects) {
|
|
319
378
|
const remote = this.parent.opts().remote;
|
|
320
379
|
const dryRun = this.opts().dryRun;
|
|
321
|
-
const extraArgs = this
|
|
322
|
-
await
|
|
380
|
+
const extraArgs = collectBackupArgs(this);
|
|
381
|
+
await backup({ remote, projects: projects.filter((p) => !p.startsWith("-")), extraArgs, dryRun });
|
|
382
|
+
});
|
|
383
|
+
program.command("restore [projects...]").description("\u6309 rules.yaml \u6062\u590D\u9879\u76EE").requiredOption("--target <dir>", "\u6062\u590D\u76EE\u6807\u76EE\u5F55").option("--snapshot <snapshot>", "\u5FEB\u7167 ID \u6216 latest", "latest").option("--dry-run", "\u6A21\u62DF\u6062\u590D\uFF08\u4E0D\u5B9E\u9645\u5199\u5165\uFF09").option("--delete", "\u5220\u9664\u76EE\u6807\u4E2D\u5FEB\u7167\u4E0D\u5B58\u5728\u7684\u6587\u4EF6").option("-v, --verbose", "\u663E\u793A\u8BE6\u7EC6\u6062\u590D\u8BA1\u5212\uFF08\u4F20\u9012 --verbose=2 \u7ED9 restic\uFF09").action(async function(projects, cmdOptions) {
|
|
384
|
+
const remote = this.parent.opts().remote;
|
|
385
|
+
await restore({
|
|
386
|
+
remote,
|
|
387
|
+
projects,
|
|
388
|
+
target: cmdOptions.target,
|
|
389
|
+
snapshot: cmdOptions.snapshot,
|
|
390
|
+
dryRun: cmdOptions.dryRun,
|
|
391
|
+
delete: cmdOptions.delete,
|
|
392
|
+
verbose: cmdOptions.verbose
|
|
393
|
+
});
|
|
323
394
|
});
|
|
324
395
|
program.command("gc").description("\u6E05\u7406\u65E7\u5FEB\u7167").option("--dry-run", "\u6A21\u62DF\u6E05\u7406\uFF08\u4E0D\u5B9E\u9645\u5220\u9664\uFF09").action(async function(cmdOptions) {
|
|
325
396
|
const remote = this.parent.opts().remote;
|
|
@@ -339,13 +410,16 @@ program.command("env").description("\u663E\u793A\u73AF\u5883\u53D8\u91CF").actio
|
|
|
339
410
|
console.log(`${key}=${value}`);
|
|
340
411
|
}
|
|
341
412
|
});
|
|
342
|
-
program.command("
|
|
413
|
+
program.command("restic [args...]").description("\u663E\u5F0F\u900F\u4F20\u7ED9 restic").allowUnknownOption().allowExcessArguments().action(async function(args) {
|
|
343
414
|
const remote = this.parent.opts().remote;
|
|
344
415
|
const wayDir = process.env.WAY_DIR || `${process.env.HOME}/.way`;
|
|
345
416
|
const config = loadConfig(wayDir, remote);
|
|
346
417
|
const env = buildResticEnv(config.repository);
|
|
347
418
|
const s3Options = buildS3Options(config.repository);
|
|
348
|
-
const args = this.parent.args;
|
|
349
419
|
await execRestic(args, env, s3Options);
|
|
350
420
|
});
|
|
421
|
+
if (process.argv.length <= 2) {
|
|
422
|
+
program.outputHelp();
|
|
423
|
+
process.exit(0);
|
|
424
|
+
}
|
|
351
425
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
restic 0.
|
|
1
|
+
restic 0.18.1 compiled with go1.25.1 on linux/amd64
|
|
Binary file
|