@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 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 run # 备份所有项目
116
- way run data # 只备份 data 项目
117
- way snapshots # 查看快照
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 run # 执行所有项目备份
125
- way run data # 只备份 data 项目
126
- way run data config # 备份多个项目
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
- # 透传 restic(way 只设置环境变量)
140
- way snapshots # → restic snapshots
141
- way restore abc123 # → restic restore abc123
142
- way check # → restic check
143
- way stats # → restic stats
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 透传所有 restic 命令,恢复数据时只需通过 way 调用 restic 的恢复相关命令。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
- ### 3. 恢复文件
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
- ### 4. 从其他仓库恢复
351
+ ### 5. 从其他仓库恢复
310
352
 
311
353
  ```bash
312
- way --remote=oss snapshots
313
- way --remote=oss restore <snapshot-id> --target /tmp/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("restic", [...s3Options, ...args], { env: { ...process.env, ...env }, stdio: "inherit" });
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. Please install restic first.");
64
- console.error("Visit: https://restic.net/");
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/run.ts
72
- async function run(options) {
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 (options.extraArgs) args.push(...options.extraArgs);
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 fs2 from "fs";
153
- import path2 from "path";
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 = path2.join(systemdDir, "way-backup.service");
270
+ const servicePath = path3.join(systemdDir, "way-backup.service");
182
271
  if (options.action === "install") {
183
- fs2.writeFileSync(servicePath, serviceContent);
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 (fs2.existsSync(servicePath)) fs2.unlinkSync(servicePath);
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 run({ remote: options.remote, projects: [name] });
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.5.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", `
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 run \u6267\u884C\u6240\u6709\u9879\u76EE\u5907\u4EFD
274
- $ way run data \u53EA\u5907\u4EFD data \u9879\u76EE
275
- $ way run --dry-run \u6A21\u62DF\u5907\u4EFD\uFF08\u4E0D\u5B9E\u9645\u5199\u5165\uFF09
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 \u67E5\u770B\u5FEB\u7167\u5217\u8868
279
- $ way --remote=s3 snapshots \u4F7F\u7528 s3 \u4ED3\u5E93
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
- 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) {
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
- const extraArgs = this.args.filter((a) => a.startsWith("-") && !["--dry-run"].includes(a));
286
- if (this.opts().dryRun) extraArgs.push("--dry-run");
287
- await run({ remote, projects: projects.filter((p) => !p.startsWith("-")), extraArgs });
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("*", { isDefault: true }).description("\u900F\u4F20\u7ED9 restic").allowUnknownOption().allowExcessArguments().action(async function(name) {
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.5.1",
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