@shellus/way 0.5.2 → 0.6.1
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 +91 -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 --host old-host --target /tmp/restore --dry-run # 从指定 host 模拟恢复
|
|
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 --host old-host --target /tmp/restore --dry-run
|
|
330
|
+
|
|
331
|
+
# 让目标目录与快照一致,先 dry-run 核对删除清单
|
|
332
|
+
way restore data --host old-host --target /tmp/restore --delete --dry-run
|
|
333
|
+
way restore data --host old-host --target /tmp/restore --delete
|
|
306
334
|
```
|
|
307
335
|
|
|
308
|
-
|
|
336
|
+
`way restore` 会为项目自动添加 `--tag=way:<project>` 和项目 `paths` 对应的 `--include` 参数;传入 `--host` 时会只恢复指定 host 的快照。
|
|
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,20 @@ 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
|
+
];
|
|
89
|
+
if (options.host) args.push(`--host=${options.host}`);
|
|
90
|
+
args.push(`--target=${options.target}`);
|
|
91
|
+
for (const path4 of project.paths) args.push(`--include=${path4}`);
|
|
92
|
+
if (options.dryRun) args.push("--dry-run");
|
|
93
|
+
if (options.delete) args.push("--delete");
|
|
94
|
+
if (options.verbose) args.push("--verbose=2");
|
|
95
|
+
return args;
|
|
96
|
+
}
|
|
83
97
|
function buildS3Options(repo) {
|
|
84
98
|
const options = [];
|
|
85
99
|
if (repo.options?.bucket_lookup) {
|
|
@@ -100,8 +114,8 @@ async function execRestic(args, env, s3Options = []) {
|
|
|
100
114
|
}
|
|
101
115
|
}
|
|
102
116
|
|
|
103
|
-
// src/commands/
|
|
104
|
-
async function
|
|
117
|
+
// src/commands/backup.ts
|
|
118
|
+
async function backup(options) {
|
|
105
119
|
const wayDir = process.env.WAY_DIR || `${process.env.HOME}/.way`;
|
|
106
120
|
const config = loadConfig(wayDir, options.remote);
|
|
107
121
|
const env = buildResticEnv(config.repository);
|
|
@@ -157,6 +171,48 @@ async function notifyUptimeKuma(result, pushUrl) {
|
|
|
157
171
|
}
|
|
158
172
|
}
|
|
159
173
|
|
|
174
|
+
// src/commands/restore.ts
|
|
175
|
+
async function restore(options) {
|
|
176
|
+
if (!options.target) throw new Error("--target is required");
|
|
177
|
+
const wayDir = process.env.WAY_DIR || `${process.env.HOME}/.way`;
|
|
178
|
+
const config = loadConfig(wayDir, options.remote);
|
|
179
|
+
const env = buildResticEnv(config.repository);
|
|
180
|
+
const s3Options = buildS3Options(config.repository);
|
|
181
|
+
const projects = options.projects && options.projects.length > 0 ? options.projects : Object.keys(config.rules.projects);
|
|
182
|
+
const succeeded = [];
|
|
183
|
+
const failed = [];
|
|
184
|
+
const startTime = Date.now();
|
|
185
|
+
for (const projectName of projects) {
|
|
186
|
+
const project = config.rules.projects[projectName];
|
|
187
|
+
if (!project) {
|
|
188
|
+
console.error(`Project not found: ${projectName}`);
|
|
189
|
+
failed.push(projectName);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
console.log(`=== Restoring: ${projectName} ===`);
|
|
193
|
+
try {
|
|
194
|
+
const args = buildRestoreArgs(projectName, project, {
|
|
195
|
+
target: options.target,
|
|
196
|
+
snapshot: options.snapshot,
|
|
197
|
+
host: options.host,
|
|
198
|
+
dryRun: options.dryRun,
|
|
199
|
+
delete: options.delete,
|
|
200
|
+
verbose: options.verbose
|
|
201
|
+
});
|
|
202
|
+
await execRestic(args, env, s3Options);
|
|
203
|
+
succeeded.push(projectName);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
console.error(`Failed to restore ${projectName}:`, error);
|
|
206
|
+
failed.push(projectName);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const duration = Date.now() - startTime;
|
|
210
|
+
console.log("\n=== Summary ===");
|
|
211
|
+
if (succeeded.length > 0) console.log("Succeeded:", succeeded.join(", "));
|
|
212
|
+
if (failed.length > 0) console.log("Failed:", failed.join(", "));
|
|
213
|
+
return { succeeded, failed, duration };
|
|
214
|
+
}
|
|
215
|
+
|
|
160
216
|
// src/commands/gc.ts
|
|
161
217
|
async function gc(options) {
|
|
162
218
|
const wayDir = process.env.WAY_DIR || `${process.env.HOME}/.way`;
|
|
@@ -267,7 +323,7 @@ async function daemon(options) {
|
|
|
267
323
|
cron.schedule(schedule, () => {
|
|
268
324
|
executeTask(async () => {
|
|
269
325
|
console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] Running backup: ${name}`);
|
|
270
|
-
await
|
|
326
|
+
await backup({ remote: options.remote, projects: [name] });
|
|
271
327
|
});
|
|
272
328
|
});
|
|
273
329
|
console.log(`Scheduled backup for ${name}: ${schedule}`);
|
|
@@ -303,23 +359,41 @@ async function daemon(options) {
|
|
|
303
359
|
|
|
304
360
|
// src/cli.ts
|
|
305
361
|
var program = new Command();
|
|
306
|
-
program.name("way").version("0.
|
|
362
|
+
program.name("way").version("0.6.1").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
363
|
\u793A\u4F8B:
|
|
308
|
-
$ way
|
|
309
|
-
$ way
|
|
310
|
-
$ way
|
|
364
|
+
$ way backup \u6267\u884C\u6240\u6709\u9879\u76EE\u5907\u4EFD
|
|
365
|
+
$ way backup data \u53EA\u5907\u4EFD data \u9879\u76EE
|
|
366
|
+
$ way backup --dry-run \u6A21\u62DF\u5907\u4EFD\uFF08\u4E0D\u5B9E\u9645\u5199\u5165\uFF09
|
|
367
|
+
$ way restore data --target /tmp/restore --dry-run
|
|
311
368
|
$ way gc \u6E05\u7406\u65E7\u5FEB\u7167
|
|
312
369
|
$ way systemd install \u5B89\u88C5\u5B9A\u65F6\u4EFB\u52A1
|
|
313
|
-
$ way snapshots
|
|
314
|
-
$ way
|
|
370
|
+
$ way restic snapshots \u67E5\u770B\u5FEB\u7167\u5217\u8868
|
|
371
|
+
$ way restic restore abc123 --target /tmp/restore
|
|
372
|
+
$ way --remote=s3 restic snapshots \u4F7F\u7528 s3 \u4ED3\u5E93
|
|
315
373
|
|
|
316
374
|
\u6587\u6863: https://github.com/shellus/way
|
|
317
375
|
`);
|
|
318
|
-
|
|
376
|
+
function collectBackupArgs(command) {
|
|
377
|
+
return command.args.filter((a) => a.startsWith("-") && !["--dry-run"].includes(a));
|
|
378
|
+
}
|
|
379
|
+
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
380
|
const remote = this.parent.opts().remote;
|
|
320
381
|
const dryRun = this.opts().dryRun;
|
|
321
|
-
const extraArgs = this
|
|
322
|
-
await
|
|
382
|
+
const extraArgs = collectBackupArgs(this);
|
|
383
|
+
await backup({ remote, projects: projects.filter((p) => !p.startsWith("-")), extraArgs, dryRun });
|
|
384
|
+
});
|
|
385
|
+
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("--host <host>", "\u53EA\u6062\u590D\u6307\u5B9A host \u7684\u5FEB\u7167").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) {
|
|
386
|
+
const remote = this.parent.opts().remote;
|
|
387
|
+
await restore({
|
|
388
|
+
remote,
|
|
389
|
+
projects,
|
|
390
|
+
target: cmdOptions.target,
|
|
391
|
+
snapshot: cmdOptions.snapshot,
|
|
392
|
+
host: cmdOptions.host,
|
|
393
|
+
dryRun: cmdOptions.dryRun,
|
|
394
|
+
delete: cmdOptions.delete,
|
|
395
|
+
verbose: cmdOptions.verbose
|
|
396
|
+
});
|
|
323
397
|
});
|
|
324
398
|
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
399
|
const remote = this.parent.opts().remote;
|
|
@@ -339,13 +413,16 @@ program.command("env").description("\u663E\u793A\u73AF\u5883\u53D8\u91CF").actio
|
|
|
339
413
|
console.log(`${key}=${value}`);
|
|
340
414
|
}
|
|
341
415
|
});
|
|
342
|
-
program.command("
|
|
416
|
+
program.command("restic [args...]").description("\u663E\u5F0F\u900F\u4F20\u7ED9 restic").allowUnknownOption().allowExcessArguments().action(async function(args) {
|
|
343
417
|
const remote = this.parent.opts().remote;
|
|
344
418
|
const wayDir = process.env.WAY_DIR || `${process.env.HOME}/.way`;
|
|
345
419
|
const config = loadConfig(wayDir, remote);
|
|
346
420
|
const env = buildResticEnv(config.repository);
|
|
347
421
|
const s3Options = buildS3Options(config.repository);
|
|
348
|
-
const args = this.parent.args;
|
|
349
422
|
await execRestic(args, env, s3Options);
|
|
350
423
|
});
|
|
424
|
+
if (process.argv.length <= 2) {
|
|
425
|
+
program.outputHelp();
|
|
426
|
+
process.exit(0);
|
|
427
|
+
}
|
|
351
428
|
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
|