@shellus/way 0.5.1 → 0.5.2
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 +13 -1
- package/dist/cli.js +48 -13
- 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.16.4,其他平台需自行安装 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 snapshots
|
|
89
|
+
```
|
|
90
|
+
|
|
79
91
|
## 快速开始
|
|
80
92
|
|
|
81
93
|
### 1. 初始化配置
|
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) {
|
|
@@ -57,11 +89,11 @@ function buildS3Options(repo) {
|
|
|
57
89
|
}
|
|
58
90
|
async function execRestic(args, env, s3Options = []) {
|
|
59
91
|
try {
|
|
60
|
-
await execa(
|
|
92
|
+
await execa(resolveResticBin(), [...s3Options, ...args], { env: { ...process.env, ...env }, stdio: "inherit" });
|
|
61
93
|
} catch (error) {
|
|
62
94
|
if (error.code === "ENOENT") {
|
|
63
|
-
console.error("Error: restic not found.
|
|
64
|
-
console.error("
|
|
95
|
+
console.error("Error: restic not found. Linux x64 packages include restic; other platforms must install it first.");
|
|
96
|
+
console.error("Set WAY_RESTIC_BIN to use a custom restic binary, or visit: https://restic.net/");
|
|
65
97
|
process.exit(1);
|
|
66
98
|
}
|
|
67
99
|
throw error;
|
|
@@ -76,6 +108,8 @@ async function run(options) {
|
|
|
76
108
|
const s3Options = buildS3Options(config.repository);
|
|
77
109
|
const globalExcludes = config.rules.global_excludes || [];
|
|
78
110
|
const projects = options.projects && options.projects.length > 0 ? options.projects : Object.keys(config.rules.projects);
|
|
111
|
+
const dryRun = options.dryRun || options.extraArgs?.includes("--dry-run") || false;
|
|
112
|
+
const extraArgs = options.extraArgs?.filter((arg) => arg !== "--dry-run") || [];
|
|
79
113
|
const succeeded = [];
|
|
80
114
|
const failed = [];
|
|
81
115
|
const startTime = Date.now();
|
|
@@ -89,7 +123,8 @@ async function run(options) {
|
|
|
89
123
|
console.log(`=== Backing up: ${projectName} ===`);
|
|
90
124
|
try {
|
|
91
125
|
const args = buildBackupArgs(projectName, project, globalExcludes);
|
|
92
|
-
if (
|
|
126
|
+
if (dryRun) args.push("--dry-run");
|
|
127
|
+
args.push(...extraArgs);
|
|
93
128
|
await execRestic(args, env, s3Options);
|
|
94
129
|
succeeded.push(projectName);
|
|
95
130
|
} catch (error) {
|
|
@@ -101,7 +136,7 @@ async function run(options) {
|
|
|
101
136
|
console.log("\n=== Summary ===");
|
|
102
137
|
if (succeeded.length > 0) console.log("Succeeded:", succeeded.join(", "));
|
|
103
138
|
if (failed.length > 0) console.log("Failed:", failed.join(", "));
|
|
104
|
-
if (config.rules.uptime_kuma?.push_url) {
|
|
139
|
+
if (!dryRun && config.rules.uptime_kuma?.push_url) {
|
|
105
140
|
await notifyUptimeKuma({ succeeded, failed, duration }, config.rules.uptime_kuma.push_url);
|
|
106
141
|
}
|
|
107
142
|
return { succeeded, failed, duration };
|
|
@@ -149,8 +184,8 @@ async function gc(options) {
|
|
|
149
184
|
|
|
150
185
|
// src/commands/systemd.ts
|
|
151
186
|
import { execSync } from "child_process";
|
|
152
|
-
import
|
|
153
|
-
import
|
|
187
|
+
import fs3 from "fs";
|
|
188
|
+
import path3 from "path";
|
|
154
189
|
async function systemd(options) {
|
|
155
190
|
const wayDir = process.env.WAY_DIR || `${process.env.HOME}/.way`;
|
|
156
191
|
const config = loadConfig(wayDir, options.remote);
|
|
@@ -178,9 +213,9 @@ WantedBy=multi-user.target
|
|
|
178
213
|
return;
|
|
179
214
|
}
|
|
180
215
|
const systemdDir = "/etc/systemd/system";
|
|
181
|
-
const servicePath =
|
|
216
|
+
const servicePath = path3.join(systemdDir, "way-backup.service");
|
|
182
217
|
if (options.action === "install") {
|
|
183
|
-
|
|
218
|
+
fs3.writeFileSync(servicePath, serviceContent);
|
|
184
219
|
execSync("systemctl daemon-reload");
|
|
185
220
|
execSync("systemctl enable way-backup.service");
|
|
186
221
|
execSync("systemctl start way-backup.service");
|
|
@@ -196,7 +231,7 @@ WantedBy=multi-user.target
|
|
|
196
231
|
execSync("systemctl disable way-backup.service", { stdio: "ignore" });
|
|
197
232
|
} catch {
|
|
198
233
|
}
|
|
199
|
-
if (
|
|
234
|
+
if (fs3.existsSync(servicePath)) fs3.unlinkSync(servicePath);
|
|
200
235
|
execSync("systemctl daemon-reload");
|
|
201
236
|
console.log("Systemd service uninstalled");
|
|
202
237
|
}
|
|
@@ -268,7 +303,7 @@ async function daemon(options) {
|
|
|
268
303
|
|
|
269
304
|
// src/cli.ts
|
|
270
305
|
var program = new Command();
|
|
271
|
-
program.name("way").version("0.5.
|
|
306
|
+
program.name("way").version("0.5.2").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
307
|
\u793A\u4F8B:
|
|
273
308
|
$ way run \u6267\u884C\u6240\u6709\u9879\u76EE\u5907\u4EFD
|
|
274
309
|
$ way run data \u53EA\u5907\u4EFD data \u9879\u76EE
|
|
@@ -282,9 +317,9 @@ program.name("way").version("0.5.1").description("\u7B56\u7565\u5907\u4EFD\u5DE5
|
|
|
282
317
|
`);
|
|
283
318
|
program.command("run [projects...]").description("\u6267\u884C\u5907\u4EFD").option("--dry-run", "\u6A21\u62DF\u5907\u4EFD\uFF08\u4E0D\u5B9E\u9645\u5199\u5165\uFF09").allowUnknownOption().allowExcessArguments().action(async function(projects) {
|
|
284
319
|
const remote = this.parent.opts().remote;
|
|
320
|
+
const dryRun = this.opts().dryRun;
|
|
285
321
|
const extraArgs = this.args.filter((a) => a.startsWith("-") && !["--dry-run"].includes(a));
|
|
286
|
-
|
|
287
|
-
await run({ remote, projects: projects.filter((p) => !p.startsWith("-")), extraArgs });
|
|
322
|
+
await run({ remote, projects: projects.filter((p) => !p.startsWith("-")), extraArgs, dryRun });
|
|
288
323
|
});
|
|
289
324
|
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
325
|
const remote = this.parent.opts().remote;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shellus/way",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
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.16.4 compiled with go1.22.2 on linux/amd64
|
|
Binary file
|