@shellus/way 0.5.1 → 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 +72 -29
- package/dist/cli.js +134 -25
- package/package.json +2 -4
- package/vendor/restic/linux-x64/LICENSE +25 -0
- package/vendor/restic/linux-x64/VERSION +1 -0
- package/vendor/restic/linux-x64/restic +0 -0
package/README.md
CHANGED
|
@@ -67,8 +67,14 @@
|
|
|
67
67
|
|
|
68
68
|
## 依赖
|
|
69
69
|
|
|
70
|
-
- [restic](https://restic.net/) - 备份引擎
|
|
71
70
|
- Node.js >= 18
|
|
71
|
+
- Linux x64 平台内置 [restic](https://restic.net/) 0.18.1,其他平台需自行安装 restic
|
|
72
|
+
|
|
73
|
+
`way` 查找 restic 的顺序:
|
|
74
|
+
|
|
75
|
+
1. `WAY_RESTIC_BIN` 指定的二进制
|
|
76
|
+
2. Linux x64 包内置的 restic
|
|
77
|
+
3. 系统 `PATH` 中的 `restic`
|
|
72
78
|
|
|
73
79
|
## 安装
|
|
74
80
|
|
|
@@ -76,6 +82,12 @@
|
|
|
76
82
|
npm install -g @shellus/way
|
|
77
83
|
```
|
|
78
84
|
|
|
85
|
+
Linux x64 用户无需额外安装 restic。如需使用自定义 restic,可设置:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
WAY_RESTIC_BIN=/usr/local/bin/restic way restic snapshots
|
|
89
|
+
```
|
|
90
|
+
|
|
79
91
|
## 快速开始
|
|
80
92
|
|
|
81
93
|
### 1. 初始化配置
|
|
@@ -112,18 +124,23 @@ way systemd status
|
|
|
112
124
|
### 4. 手动执行备份
|
|
113
125
|
|
|
114
126
|
```bash
|
|
115
|
-
way
|
|
116
|
-
way
|
|
117
|
-
way
|
|
127
|
+
way backup # 备份所有项目
|
|
128
|
+
way backup data # 只备份 data 项目
|
|
129
|
+
way restore data --target /tmp/restore --dry-run # 按规则预演恢复
|
|
130
|
+
way restic snapshots # 查看快照
|
|
118
131
|
```
|
|
119
132
|
|
|
120
133
|
## 命令参考
|
|
121
134
|
|
|
122
135
|
```bash
|
|
123
136
|
# 备份命令
|
|
124
|
-
way
|
|
125
|
-
way
|
|
126
|
-
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 # 删除目标中快照不存在的文件
|
|
127
144
|
way gc # 按 retention 策略清理旧快照
|
|
128
145
|
way gc --dry-run # 模拟清理(不实际删除)
|
|
129
146
|
|
|
@@ -136,14 +153,14 @@ way systemd show # 显示 systemd 配置
|
|
|
136
153
|
way systemd status # 查看服务状态
|
|
137
154
|
way systemd uninstall # 卸载服务
|
|
138
155
|
|
|
139
|
-
#
|
|
140
|
-
way snapshots
|
|
141
|
-
way
|
|
142
|
-
way
|
|
143
|
-
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
|
|
144
161
|
|
|
145
162
|
# 指定 repository(默认用 repositories.yaml 中的 default)
|
|
146
|
-
way --remote=oss snapshots
|
|
163
|
+
way --remote=oss restic snapshots
|
|
147
164
|
```
|
|
148
165
|
|
|
149
166
|
## 生命周期
|
|
@@ -174,7 +191,7 @@ cp ~/.way/rules.yaml.example ~/.way/rules.yaml
|
|
|
174
191
|
也可以通过 `WAY_DIR` 环境变量指定其他目录:
|
|
175
192
|
|
|
176
193
|
```bash
|
|
177
|
-
WAY_DIR=/path/to/config way snapshots
|
|
194
|
+
WAY_DIR=/path/to/config way restic snapshots
|
|
178
195
|
```
|
|
179
196
|
|
|
180
197
|
### rules.yaml
|
|
@@ -275,42 +292,68 @@ gpg -c way-config-$(date +%Y%m%d).tar.gz
|
|
|
275
292
|
|
|
276
293
|
## 数据恢复
|
|
277
294
|
|
|
278
|
-
way
|
|
295
|
+
way 提供两种恢复方式:
|
|
296
|
+
|
|
297
|
+
1. **按规则恢复**:使用 `way restore`,根据 `rules.yaml` 中的项目路径和 `way:<project>` 标签恢复。
|
|
298
|
+
2. **原始 restic 恢复**:使用 `way restic restore`,显式透传 restic 参数。
|
|
279
299
|
|
|
280
300
|
### 1. 查看快照列表
|
|
281
301
|
|
|
282
302
|
```bash
|
|
283
|
-
way snapshots # 列出所有快照
|
|
284
|
-
way snapshots --tag=way:home # 只看 home 项目的快照
|
|
285
|
-
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 项目的快照
|
|
286
306
|
```
|
|
287
307
|
|
|
288
308
|
### 2. 浏览快照内容
|
|
289
309
|
|
|
290
310
|
```bash
|
|
291
|
-
way ls <snapshot-id> # 列出快照中的所有文件
|
|
292
|
-
way ls <snapshot-id> /root/.ssh # 列出快照中指定目录的文件
|
|
293
|
-
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
|
|
294
334
|
```
|
|
295
335
|
|
|
296
|
-
|
|
336
|
+
`way restore` 会为项目自动添加 `--tag=way:<project>` 和项目 `paths` 对应的 `--include` 参数。
|
|
337
|
+
|
|
338
|
+
### 4. 原始 restic 恢复
|
|
297
339
|
|
|
298
340
|
```bash
|
|
299
|
-
#
|
|
300
|
-
way restore <snapshot-id> --target /tmp/restore
|
|
341
|
+
# 显式透传 restic restore
|
|
342
|
+
way restic restore <snapshot-id> --target /tmp/restore
|
|
301
343
|
|
|
302
344
|
# 只恢复特定路径
|
|
303
|
-
way restore <snapshot-id> --target /tmp/restore --include /root/.ssh
|
|
345
|
+
way restic restore <snapshot-id> --target /tmp/restore --include /root/.ssh
|
|
304
346
|
|
|
305
347
|
# 恢复单个文件到标准输出(适合快速查看)
|
|
306
|
-
way dump <snapshot-id> /root/.gitconfig
|
|
348
|
+
way restic dump <snapshot-id> /root/.gitconfig
|
|
307
349
|
```
|
|
308
350
|
|
|
309
|
-
###
|
|
351
|
+
### 5. 从其他仓库恢复
|
|
310
352
|
|
|
311
353
|
```bash
|
|
312
|
-
way --remote=oss snapshots
|
|
313
|
-
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
|
|
314
357
|
```
|
|
315
358
|
|
|
316
359
|
## 开发
|
package/dist/cli.js
CHANGED
|
@@ -23,6 +23,38 @@ function loadConfig(wayDir, remoteName) {
|
|
|
23
23
|
|
|
24
24
|
// src/core/restic.ts
|
|
25
25
|
import { execa } from "execa";
|
|
26
|
+
|
|
27
|
+
// src/core/restic-bin.ts
|
|
28
|
+
import fs2 from "fs";
|
|
29
|
+
import path2 from "path";
|
|
30
|
+
import { fileURLToPath } from "url";
|
|
31
|
+
function getBundledResticBin(packageRoot) {
|
|
32
|
+
return path2.join(packageRoot, "vendor/restic/linux-x64/restic");
|
|
33
|
+
}
|
|
34
|
+
function findPackageRoot(startDir = path2.dirname(fileURLToPath(import.meta.url))) {
|
|
35
|
+
let current = startDir;
|
|
36
|
+
while (true) {
|
|
37
|
+
if (fs2.existsSync(path2.join(current, "package.json"))) return current;
|
|
38
|
+
const parent = path2.dirname(current);
|
|
39
|
+
if (parent === current) return startDir;
|
|
40
|
+
current = parent;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function resolveResticBin(options = {}) {
|
|
44
|
+
const env = options.env ?? process.env;
|
|
45
|
+
const platform = options.platform ?? process.platform;
|
|
46
|
+
const arch = options.arch ?? process.arch;
|
|
47
|
+
const packageRoot = options.packageRoot ?? findPackageRoot();
|
|
48
|
+
const existsSync = options.existsSync ?? fs2.existsSync;
|
|
49
|
+
if (env.WAY_RESTIC_BIN) return env.WAY_RESTIC_BIN;
|
|
50
|
+
if (platform === "linux" && arch === "x64") {
|
|
51
|
+
const bundled = getBundledResticBin(packageRoot);
|
|
52
|
+
if (existsSync(bundled)) return bundled;
|
|
53
|
+
}
|
|
54
|
+
return "restic";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/core/restic.ts
|
|
26
58
|
function buildResticEnv(repo) {
|
|
27
59
|
const env = {};
|
|
28
60
|
switch (repo.type) {
|
|
@@ -48,6 +80,19 @@ function buildBackupArgs(name, project, globalExcludes) {
|
|
|
48
80
|
args.push(...project.paths);
|
|
49
81
|
return args;
|
|
50
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
|
+
}
|
|
51
96
|
function buildS3Options(repo) {
|
|
52
97
|
const options = [];
|
|
53
98
|
if (repo.options?.bucket_lookup) {
|
|
@@ -57,25 +102,27 @@ function buildS3Options(repo) {
|
|
|
57
102
|
}
|
|
58
103
|
async function execRestic(args, env, s3Options = []) {
|
|
59
104
|
try {
|
|
60
|
-
await execa(
|
|
105
|
+
await execa(resolveResticBin(), [...s3Options, ...args], { env: { ...process.env, ...env }, stdio: "inherit" });
|
|
61
106
|
} catch (error) {
|
|
62
107
|
if (error.code === "ENOENT") {
|
|
63
|
-
console.error("Error: restic not found.
|
|
64
|
-
console.error("
|
|
108
|
+
console.error("Error: restic not found. Linux x64 packages include restic; other platforms must install it first.");
|
|
109
|
+
console.error("Set WAY_RESTIC_BIN to use a custom restic binary, or visit: https://restic.net/");
|
|
65
110
|
process.exit(1);
|
|
66
111
|
}
|
|
67
112
|
throw error;
|
|
68
113
|
}
|
|
69
114
|
}
|
|
70
115
|
|
|
71
|
-
// src/commands/
|
|
72
|
-
async function
|
|
116
|
+
// src/commands/backup.ts
|
|
117
|
+
async function backup(options) {
|
|
73
118
|
const wayDir = process.env.WAY_DIR || `${process.env.HOME}/.way`;
|
|
74
119
|
const config = loadConfig(wayDir, options.remote);
|
|
75
120
|
const env = buildResticEnv(config.repository);
|
|
76
121
|
const s3Options = buildS3Options(config.repository);
|
|
77
122
|
const globalExcludes = config.rules.global_excludes || [];
|
|
78
123
|
const projects = options.projects && options.projects.length > 0 ? options.projects : Object.keys(config.rules.projects);
|
|
124
|
+
const dryRun = options.dryRun || options.extraArgs?.includes("--dry-run") || false;
|
|
125
|
+
const extraArgs = options.extraArgs?.filter((arg) => arg !== "--dry-run") || [];
|
|
79
126
|
const succeeded = [];
|
|
80
127
|
const failed = [];
|
|
81
128
|
const startTime = Date.now();
|
|
@@ -89,7 +136,8 @@ async function run(options) {
|
|
|
89
136
|
console.log(`=== Backing up: ${projectName} ===`);
|
|
90
137
|
try {
|
|
91
138
|
const args = buildBackupArgs(projectName, project, globalExcludes);
|
|
92
|
-
if (
|
|
139
|
+
if (dryRun) args.push("--dry-run");
|
|
140
|
+
args.push(...extraArgs);
|
|
93
141
|
await execRestic(args, env, s3Options);
|
|
94
142
|
succeeded.push(projectName);
|
|
95
143
|
} catch (error) {
|
|
@@ -101,7 +149,7 @@ async function run(options) {
|
|
|
101
149
|
console.log("\n=== Summary ===");
|
|
102
150
|
if (succeeded.length > 0) console.log("Succeeded:", succeeded.join(", "));
|
|
103
151
|
if (failed.length > 0) console.log("Failed:", failed.join(", "));
|
|
104
|
-
if (config.rules.uptime_kuma?.push_url) {
|
|
152
|
+
if (!dryRun && config.rules.uptime_kuma?.push_url) {
|
|
105
153
|
await notifyUptimeKuma({ succeeded, failed, duration }, config.rules.uptime_kuma.push_url);
|
|
106
154
|
}
|
|
107
155
|
return { succeeded, failed, duration };
|
|
@@ -122,6 +170,47 @@ async function notifyUptimeKuma(result, pushUrl) {
|
|
|
122
170
|
}
|
|
123
171
|
}
|
|
124
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
|
+
|
|
125
214
|
// src/commands/gc.ts
|
|
126
215
|
async function gc(options) {
|
|
127
216
|
const wayDir = process.env.WAY_DIR || `${process.env.HOME}/.way`;
|
|
@@ -149,8 +238,8 @@ async function gc(options) {
|
|
|
149
238
|
|
|
150
239
|
// src/commands/systemd.ts
|
|
151
240
|
import { execSync } from "child_process";
|
|
152
|
-
import
|
|
153
|
-
import
|
|
241
|
+
import fs3 from "fs";
|
|
242
|
+
import path3 from "path";
|
|
154
243
|
async function systemd(options) {
|
|
155
244
|
const wayDir = process.env.WAY_DIR || `${process.env.HOME}/.way`;
|
|
156
245
|
const config = loadConfig(wayDir, options.remote);
|
|
@@ -178,9 +267,9 @@ WantedBy=multi-user.target
|
|
|
178
267
|
return;
|
|
179
268
|
}
|
|
180
269
|
const systemdDir = "/etc/systemd/system";
|
|
181
|
-
const servicePath =
|
|
270
|
+
const servicePath = path3.join(systemdDir, "way-backup.service");
|
|
182
271
|
if (options.action === "install") {
|
|
183
|
-
|
|
272
|
+
fs3.writeFileSync(servicePath, serviceContent);
|
|
184
273
|
execSync("systemctl daemon-reload");
|
|
185
274
|
execSync("systemctl enable way-backup.service");
|
|
186
275
|
execSync("systemctl start way-backup.service");
|
|
@@ -196,7 +285,7 @@ WantedBy=multi-user.target
|
|
|
196
285
|
execSync("systemctl disable way-backup.service", { stdio: "ignore" });
|
|
197
286
|
} catch {
|
|
198
287
|
}
|
|
199
|
-
if (
|
|
288
|
+
if (fs3.existsSync(servicePath)) fs3.unlinkSync(servicePath);
|
|
200
289
|
execSync("systemctl daemon-reload");
|
|
201
290
|
console.log("Systemd service uninstalled");
|
|
202
291
|
}
|
|
@@ -232,7 +321,7 @@ async function daemon(options) {
|
|
|
232
321
|
cron.schedule(schedule, () => {
|
|
233
322
|
executeTask(async () => {
|
|
234
323
|
console.log(`[${(/* @__PURE__ */ new Date()).toISOString()}] Running backup: ${name}`);
|
|
235
|
-
await
|
|
324
|
+
await backup({ remote: options.remote, projects: [name] });
|
|
236
325
|
});
|
|
237
326
|
});
|
|
238
327
|
console.log(`Scheduled backup for ${name}: ${schedule}`);
|
|
@@ -268,23 +357,40 @@ async function daemon(options) {
|
|
|
268
357
|
|
|
269
358
|
// src/cli.ts
|
|
270
359
|
var program = new Command();
|
|
271
|
-
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", `
|
|
272
361
|
\u793A\u4F8B:
|
|
273
|
-
$ way
|
|
274
|
-
$ way
|
|
275
|
-
$ 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
|
|
276
366
|
$ way gc \u6E05\u7406\u65E7\u5FEB\u7167
|
|
277
367
|
$ way systemd install \u5B89\u88C5\u5B9A\u65F6\u4EFB\u52A1
|
|
278
|
-
$ way snapshots
|
|
279
|
-
$ 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
|
|
280
371
|
|
|
281
372
|
\u6587\u6863: https://github.com/shellus/way
|
|
282
373
|
`);
|
|
283
|
-
|
|
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) {
|
|
378
|
+
const remote = this.parent.opts().remote;
|
|
379
|
+
const dryRun = this.opts().dryRun;
|
|
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) {
|
|
284
384
|
const remote = this.parent.opts().remote;
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
+
});
|
|
288
394
|
});
|
|
289
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) {
|
|
290
396
|
const remote = this.parent.opts().remote;
|
|
@@ -304,13 +410,16 @@ program.command("env").description("\u663E\u793A\u73AF\u5883\u53D8\u91CF").actio
|
|
|
304
410
|
console.log(`${key}=${value}`);
|
|
305
411
|
}
|
|
306
412
|
});
|
|
307
|
-
program.command("
|
|
413
|
+
program.command("restic [args...]").description("\u663E\u5F0F\u900F\u4F20\u7ED9 restic").allowUnknownOption().allowExcessArguments().action(async function(args) {
|
|
308
414
|
const remote = this.parent.opts().remote;
|
|
309
415
|
const wayDir = process.env.WAY_DIR || `${process.env.HOME}/.way`;
|
|
310
416
|
const config = loadConfig(wayDir, remote);
|
|
311
417
|
const env = buildResticEnv(config.repository);
|
|
312
418
|
const s3Options = buildS3Options(config.repository);
|
|
313
|
-
const args = this.parent.args;
|
|
314
419
|
await execRestic(args, env, s3Options);
|
|
315
420
|
});
|
|
421
|
+
if (process.argv.length <= 2) {
|
|
422
|
+
program.outputHelp();
|
|
423
|
+
process.exit(0);
|
|
424
|
+
}
|
|
316
425
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shellus/way",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "将备份作为持续运营的项目,而非一次性任务。基于 restic 的策略封装。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"dist",
|
|
18
|
+
"vendor",
|
|
18
19
|
"repositories.yaml.example",
|
|
19
20
|
"rules.yaml.example",
|
|
20
21
|
"migrate-to-v0.5.sh"
|
|
@@ -38,9 +39,6 @@
|
|
|
38
39
|
"engines": {
|
|
39
40
|
"node": ">=18.0.0"
|
|
40
41
|
},
|
|
41
|
-
"peerDependencies": {
|
|
42
|
-
"restic": "*"
|
|
43
|
-
},
|
|
44
42
|
"dependencies": {
|
|
45
43
|
"commander": "^14.0.3",
|
|
46
44
|
"execa": "^9.6.1",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
BSD 2-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2014, Alexander Neumann <alexander@bumpern.de>
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
7
|
+
are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
17
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
18
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
20
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
21
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
22
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
23
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
24
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
25
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
restic 0.18.1 compiled with go1.25.1 on linux/amd64
|
|
Binary file
|