@procedures/restic 0.28.0 → 0.29.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/CHANGELOG.md +61 -0
- package/README.md +11 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/module.d.ts +3 -2
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +3 -2
- package/dist/module.js.map +1 -1
- package/dist/procedures.d.ts +19 -2
- package/dist/procedures.d.ts.map +1 -1
- package/dist/procedures.js +122 -7
- package/dist/procedures.js.map +1 -1
- package/package.json +3 -3
- package/src/module.ts +3 -2
- package/src/procedures.ts +131 -10
package/dist/module.d.ts
CHANGED
|
@@ -7,11 +7,12 @@ export declare const resticModule: ProcedureModule;
|
|
|
7
7
|
export default resticModule;
|
|
8
8
|
/**
|
|
9
9
|
* This function gives the module with the settings of the host, for example the secrets of the
|
|
10
|
-
* repository in `env`. Without options, it gives `resticModule`.
|
|
10
|
+
* repository in `env` and the program in `executable`. Without options, it gives `resticModule`.
|
|
11
11
|
*
|
|
12
12
|
* @example
|
|
13
13
|
* ```ts
|
|
14
|
-
* const
|
|
14
|
+
* const executable = await findRestic();
|
|
15
|
+
* const registry = createRegistry(createResticModule({ executable, env: { RESTIC_REPOSITORY: repository, RESTIC_PASSWORD: password } }));
|
|
15
16
|
* ```
|
|
16
17
|
*/
|
|
17
18
|
export declare function createResticModule(options?: ResticProceduresOptions): ProcedureModule;
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAqD,KAAK,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACrI,OAAO,EAA4C,KAAK,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAEzG,eAAO,MAAM,YAAY,EAAE,eAAsE,CAAC;eAEnF,YAAY;AAE3B
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAqD,KAAK,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACrI,OAAO,EAA4C,KAAK,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAEzG,eAAO,MAAM,YAAY,EAAE,eAAsE,CAAC;eAEnF,YAAY;AAE3B;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,uBAA4B,GAAG,eAAe,CAGzF;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,GAAE,iBAAsC,EAAE,OAAO,GAAE,uBAA4B,GAAG,IAAI,CAEtI"}
|
package/dist/module.js
CHANGED
|
@@ -7,11 +7,12 @@ export const resticModule = defineModule("@procedures/restic", resticProcedures)
|
|
|
7
7
|
export default resticModule;
|
|
8
8
|
/**
|
|
9
9
|
* This function gives the module with the settings of the host, for example the secrets of the
|
|
10
|
-
* repository in `env`. Without options, it gives `resticModule`.
|
|
10
|
+
* repository in `env` and the program in `executable`. Without options, it gives `resticModule`.
|
|
11
11
|
*
|
|
12
12
|
* @example
|
|
13
13
|
* ```ts
|
|
14
|
-
* const
|
|
14
|
+
* const executable = await findRestic();
|
|
15
|
+
* const registry = createRegistry(createResticModule({ executable, env: { RESTIC_REPOSITORY: repository, RESTIC_PASSWORD: password } }));
|
|
15
16
|
* ```
|
|
16
17
|
*/
|
|
17
18
|
export function createResticModule(options = {}) {
|
package/dist/module.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAgD,MAAM,oBAAoB,CAAC;AACrI,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAgC,MAAM,iBAAiB,CAAC;AAEzG,MAAM,CAAC,MAAM,YAAY,GAAoB,YAAY,CAAC,oBAAoB,EAAE,gBAAgB,CAAC,CAAC;AAElG,eAAe,YAAY,CAAC;AAE5B
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAgD,MAAM,oBAAoB,CAAC;AACrI,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAgC,MAAM,iBAAiB,CAAC;AAEzG,MAAM,CAAC,MAAM,YAAY,GAAoB,YAAY,CAAC,oBAAoB,EAAE,gBAAgB,CAAC,CAAC;AAElG,eAAe,YAAY,CAAC;AAE5B;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAO,GAA4B,EAAE;IACtE,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC;QAAE,OAAO,YAAY,CAAC;IACtF,OAAO,YAAY,CAAC,oBAAoB,EAAE,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,QAAQ,GAAsB,kBAAkB,EAAE,OAAO,GAA4B,EAAE;IAC9H,eAAe,CAAC,QAAQ,EAAE,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC3D,CAAC"}
|
package/dist/procedures.d.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* restic reads the repository and its keys from the environment (`RESTIC_REPOSITORY`,
|
|
5
5
|
* `RESTIC_PASSWORD`, `B2_ACCOUNT_ID`, `AWS_ACCESS_KEY_ID` and the others), so no secret is an
|
|
6
|
-
* input.
|
|
6
|
+
* input. `restic.run` refuses each input that lets the caller choose a program for restic to
|
|
7
|
+
* start (`resticArgv`).
|
|
7
8
|
*
|
|
8
9
|
* A host gives secrets only to the restic process with the `env` option of `runRestic()` or of
|
|
9
10
|
* `createResticProcedures()`. The procedure input has no field for them.
|
|
@@ -33,7 +34,18 @@ export interface ResticCommand {
|
|
|
33
34
|
readonly repository?: string | undefined;
|
|
34
35
|
readonly json?: boolean | undefined;
|
|
35
36
|
}
|
|
36
|
-
/**
|
|
37
|
+
/**
|
|
38
|
+
* This function gives the argv of a restic command. It refuses each input that lets the caller
|
|
39
|
+
* choose a program for restic to start:
|
|
40
|
+
*
|
|
41
|
+
* - the flags `--password-command`, `--from-password-command` and `--password-command2`;
|
|
42
|
+
* - an extended option (`--option`) that sets `sftp.command`, `sftp.args`, `rclone.program` or
|
|
43
|
+
* `rclone.args`, in each spelling that restic reads;
|
|
44
|
+
* - an sftp or rclone repository whose host or remote starts with `-` (`repository`, `--repo`,
|
|
45
|
+
* `--from-repo`, `--repo2`);
|
|
46
|
+
* - a positional argument that starts with `-` after a last flag without a value. A flag that
|
|
47
|
+
* takes a value then reads the `--` that ends the flags, and restic reads the next arguments as flags.
|
|
48
|
+
*/
|
|
37
49
|
export declare function resticArgv(input: ResticCommand): string[];
|
|
38
50
|
/** A function that starts a program: `runCommand` of `@procedures/shell/command`, or a fake in a test. */
|
|
39
51
|
export type ResticRunner = (command: string, options: CommandOptions) => Promise<CommandResult>;
|
|
@@ -80,6 +92,11 @@ export interface ResticRunOptions {
|
|
|
80
92
|
export declare function runRestic(command: ResticCommand, options?: ResticRunOptions): Promise<ResticResult>;
|
|
81
93
|
/** The settings of the host for the procedures of this package. The procedure input cannot change them. */
|
|
82
94
|
export interface ResticProceduresOptions {
|
|
95
|
+
/**
|
|
96
|
+
* The restic program, for example the result of `findRestic()`. The default is `RESTIC_EXE` of
|
|
97
|
+
* the environment of the host, then `restic`. The deprecated input `executable` wins over it.
|
|
98
|
+
*/
|
|
99
|
+
readonly executable?: string | undefined;
|
|
83
100
|
/** The variables for the restic process only, for example `RESTIC_PASSWORD`. Refer to `ResticRunOptions.env`. */
|
|
84
101
|
readonly env?: Readonly<Record<string, string | undefined>> | undefined;
|
|
85
102
|
/** The function that starts restic (default: `runCommand`). A test gives a fake. */
|
package/dist/procedures.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"procedures.d.ts","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"procedures.d.ts","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAA+B,KAAK,YAAY,EAAyB,MAAM,oBAAoB,CAAC;AAC3G,OAAO,EAAc,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAEhG,OAAO,EAA8B,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AA8FtE,sCAAsC;AACtC,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,4FAA4F;IAC5F,SAAS,CAAC,EAAE;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,CAAC;CAC9D;AAWD,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IACpD,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,GAAG,SAAS,CAAC;IAC/D,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,EAAE,CAsBzD;AAED,0GAA0G;AAC1G,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;AAEhG,sEAAsE;AACtE,MAAM,WAAW,gBAAgB;IAC/B,+FAA+F;IAC/F,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,CAAC;IACxE,yBAAyB;IACzB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,sDAAsD;IACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,oFAAoF;IACpF,QAAQ,CAAC,GAAG,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;CACzC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,aAAa,EAAE,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,YAAY,CAAC,CAsB7G;AAiBD,2GAA2G;AAC3G,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,iHAAiH;IACjH,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,CAAC;IACxE,oFAAoF;IACpF,QAAQ,CAAC,GAAG,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;CACzC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,uBAA4B,GAAG,YAAY,EAAE,CAsB5F;AAED,eAAO,MAAM,gBAAgB,EAAE,YAAY,EAA6B,CAAC;AAEzE,eAAO,MAAM,kBAAkB,EAAE,YAAmC,CAAC"}
|
package/dist/procedures.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* restic reads the repository and its keys from the environment (`RESTIC_REPOSITORY`,
|
|
5
5
|
* `RESTIC_PASSWORD`, `B2_ACCOUNT_ID`, `AWS_ACCESS_KEY_ID` and the others), so no secret is an
|
|
6
|
-
* input.
|
|
6
|
+
* input. `restic.run` refuses each input that lets the caller choose a program for restic to
|
|
7
|
+
* start (`resticArgv`).
|
|
7
8
|
*
|
|
8
9
|
* A host gives secrets only to the restic process with the `env` option of `runRestic()` or of
|
|
9
10
|
* `createResticProcedures()`. The procedure input has no field for them.
|
|
@@ -12,8 +13,66 @@ import { createProcedure, zodAdapter } from "@procedures/client";
|
|
|
12
13
|
import { runCommand } from "@procedures/shell/command";
|
|
13
14
|
import { z } from "zod";
|
|
14
15
|
import { buildArgv, parseJsonOutput } from "./argv.js";
|
|
15
|
-
/**
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* The flags that `restic.run` refuses: each one names a program for restic to start.
|
|
18
|
+
* `password-command2` is the hidden old name of `from-password-command`.
|
|
19
|
+
*/
|
|
20
|
+
const REFUSED_FLAGS = new Set(["password-command", "from-password-command", "password-command2"]);
|
|
21
|
+
/** The flags of extended options (`-o key=value`). `o` gives `--o`, which restic refuses, but the check is the same. */
|
|
22
|
+
const OPTION_FLAGS = new Set(["option", "o"]);
|
|
23
|
+
/**
|
|
24
|
+
* The extended options that start a program: the command or the arguments of `ssh` for an sftp
|
|
25
|
+
* repository, and the program or the arguments of `rclone` for an rclone repository.
|
|
26
|
+
*/
|
|
27
|
+
const PROGRAM_OPTIONS = ["sftp.command", "sftp.args", "rclone.program", "rclone.args"];
|
|
28
|
+
/** The flags whose value is a repository location. `repo2` is the hidden old name of `from-repo`. */
|
|
29
|
+
const REPOSITORY_FLAGS = new Set(["repo", "from-repo", "repo2"]);
|
|
30
|
+
/** The values of one named argument, as texts. */
|
|
31
|
+
function argTexts(value) {
|
|
32
|
+
if (Array.isArray(value))
|
|
33
|
+
return [...value];
|
|
34
|
+
return typeof value === "boolean" ? [] : [String(value)];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The program option that an `--option` value sets, or undefined. restic reads the value as a list
|
|
38
|
+
* of `key=value` pairs separated with commas, with quotes, and it ignores the case and the spaces of
|
|
39
|
+
* a key. A key is one run of characters in each of these forms, so a search in the lowercase value
|
|
40
|
+
* finds each spelling.
|
|
41
|
+
*/
|
|
42
|
+
function programOption(value) {
|
|
43
|
+
const text = value.toLowerCase();
|
|
44
|
+
return PROGRAM_OPTIONS.find((key) => text.includes(key));
|
|
45
|
+
}
|
|
46
|
+
/** The text with its percent escapes decoded, or the text itself when an escape is not valid. */
|
|
47
|
+
function decoded(text) {
|
|
48
|
+
try {
|
|
49
|
+
return decodeURIComponent(text);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return text;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The reason to refuse a repository location, or undefined. restic gives the host of an sftp
|
|
57
|
+
* repository to `ssh`, and the remote of an rclone repository to `rclone`, as one argument. A value
|
|
58
|
+
* that starts with `-` becomes an option of that program, and an option of `ssh` can start a program.
|
|
59
|
+
*/
|
|
60
|
+
function repositoryRefusal(location) {
|
|
61
|
+
let target;
|
|
62
|
+
if (location.startsWith("sftp://")) {
|
|
63
|
+
// restic reads this form as a URL: the host comes without its brackets and with its escapes decoded
|
|
64
|
+
const authority = location.slice("sftp://".length).split("/")[0] ?? "";
|
|
65
|
+
target = decoded(authority.slice(authority.lastIndexOf("@") + 1)).replace(/^\[/, "");
|
|
66
|
+
}
|
|
67
|
+
else if (location.startsWith("sftp:")) {
|
|
68
|
+
const head = location.slice("sftp:".length).split(":")[0] ?? "";
|
|
69
|
+
target = head.split("@").slice(-1)[0];
|
|
70
|
+
}
|
|
71
|
+
else if (location.startsWith("rclone:")) {
|
|
72
|
+
target = location.slice("rclone:".length);
|
|
73
|
+
}
|
|
74
|
+
return target?.startsWith("-") ? "the host or the remote of the repository starts with -, so ssh or rclone reads it as an option" : undefined;
|
|
75
|
+
}
|
|
17
76
|
const RunInputSchema = z.object({
|
|
18
77
|
path: z
|
|
19
78
|
.array(z.string().regex(/^[a-z][a-z-]*$/))
|
|
@@ -26,7 +85,17 @@ const RunInputSchema = z.object({
|
|
|
26
85
|
.describe('The flags: { tag: ["daily"], "keep-daily": 7, "dry-run": true }'),
|
|
27
86
|
repository: z.string().min(1).optional().describe("The repository (--repo). Default: RESTIC_REPOSITORY of the environment."),
|
|
28
87
|
json: z.boolean().default(true).describe("Ask for JSON output (--json) and read it (default: true)"),
|
|
29
|
-
|
|
88
|
+
/**
|
|
89
|
+
* The restic program.
|
|
90
|
+
*
|
|
91
|
+
* @deprecated Give `executable` to `createResticModule()`, or set `RESTIC_EXE`. Version 0.31.0
|
|
92
|
+
* refuses this input (audit pass 2, O55), because it lets the caller start any program.
|
|
93
|
+
*/
|
|
94
|
+
executable: z
|
|
95
|
+
.string()
|
|
96
|
+
.min(1)
|
|
97
|
+
.optional()
|
|
98
|
+
.describe("Deprecated: give executable to createResticModule(), or set RESTIC_EXE. The restic program (default: RESTIC_EXE of the environment, then restic)"),
|
|
30
99
|
cwd: z.string().optional().describe("The working folder"),
|
|
31
100
|
timeout: z.number().int().positive().optional().describe("The time limit, in milliseconds"),
|
|
32
101
|
});
|
|
@@ -38,11 +107,39 @@ const RunOutputSchema = z.object({
|
|
|
38
107
|
json: z.unknown().optional(),
|
|
39
108
|
truncated: z.object({ stdout: z.boolean(), stderr: z.boolean() }).optional(),
|
|
40
109
|
});
|
|
41
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* This function gives the argv of a restic command. It refuses each input that lets the caller
|
|
112
|
+
* choose a program for restic to start:
|
|
113
|
+
*
|
|
114
|
+
* - the flags `--password-command`, `--from-password-command` and `--password-command2`;
|
|
115
|
+
* - an extended option (`--option`) that sets `sftp.command`, `sftp.args`, `rclone.program` or
|
|
116
|
+
* `rclone.args`, in each spelling that restic reads;
|
|
117
|
+
* - an sftp or rclone repository whose host or remote starts with `-` (`repository`, `--repo`,
|
|
118
|
+
* `--from-repo`, `--repo2`);
|
|
119
|
+
* - a positional argument that starts with `-` after a last flag without a value. A flag that
|
|
120
|
+
* takes a value then reads the `--` that ends the flags, and restic reads the next arguments as flags.
|
|
121
|
+
*/
|
|
42
122
|
export function resticArgv(input) {
|
|
43
|
-
|
|
123
|
+
const entries = Object.entries(input.args ?? {});
|
|
124
|
+
for (const [key, value] of entries) {
|
|
44
125
|
if (REFUSED_FLAGS.has(key))
|
|
45
126
|
throw new Error(`restic.run refuses --${key}: it starts a program`);
|
|
127
|
+
for (const text of argTexts(value)) {
|
|
128
|
+
const option = OPTION_FLAGS.has(key) ? programOption(text) : undefined;
|
|
129
|
+
if (option !== undefined)
|
|
130
|
+
throw new Error(`restic.run refuses the option ${option}: it starts a program`);
|
|
131
|
+
const refusal = REPOSITORY_FLAGS.has(key) ? repositoryRefusal(text) : undefined;
|
|
132
|
+
if (refusal !== undefined)
|
|
133
|
+
throw new Error(`restic.run refuses --${key}: ${refusal}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const repositoryProblem = input.repository !== undefined ? repositoryRefusal(input.repository) : undefined;
|
|
137
|
+
if (repositoryProblem !== undefined)
|
|
138
|
+
throw new Error(`restic.run refuses the repository: ${repositoryProblem}`);
|
|
139
|
+
// The last named argument that gives argv an element: false and an empty list give none
|
|
140
|
+
const last = entries.filter(([, value]) => value !== false && !(Array.isArray(value) && value.length === 0)).at(-1);
|
|
141
|
+
if (last?.[1] === true && (input.positional ?? []).some((item) => item.startsWith("-"))) {
|
|
142
|
+
throw new Error(`restic.run refuses a positional argument that starts with - after the flag --${last[0]} without a value: put a flag with a value last, or start the argument with ./`);
|
|
46
143
|
}
|
|
47
144
|
const flags = [...(input.repository !== undefined ? [`--repo=${input.repository}`] : []), ...(input.json ? ["--json"] : [])];
|
|
48
145
|
return buildArgv({ path: input.path, args: input.args, positional: input.positional, flags });
|
|
@@ -87,6 +184,18 @@ export async function runRestic(command, options = {}) {
|
|
|
87
184
|
...(result.truncated !== undefined ? { truncated: result.truncated } : {}),
|
|
88
185
|
};
|
|
89
186
|
}
|
|
187
|
+
/** The codes of the deprecation notices that this process emitted. */
|
|
188
|
+
const emitted = new Set();
|
|
189
|
+
/** This function emits a deprecation notice one time in each process. */
|
|
190
|
+
function deprecationNotice(code, message) {
|
|
191
|
+
if (emitted.has(code))
|
|
192
|
+
return;
|
|
193
|
+
emitted.add(code);
|
|
194
|
+
process.emitWarning(message, { type: "DeprecationWarning", code });
|
|
195
|
+
}
|
|
196
|
+
/** The notice of the deprecated input `executable` of `restic.run`. */
|
|
197
|
+
const EXECUTABLE_INPUT_NOTICE = "The input executable of restic.run is deprecated. @procedures/restic 0.31.0 refuses it. " +
|
|
198
|
+
"Give the program to the host: createResticModule({ executable }), or set RESTIC_EXE.";
|
|
90
199
|
/**
|
|
91
200
|
* This function gives the procedures of this package with the settings of the host. With `env`,
|
|
92
201
|
* each restic process gets those variables, and the caller of `restic.run` does not see them.
|
|
@@ -105,7 +214,13 @@ export function createResticProcedures(options = {}) {
|
|
|
105
214
|
// repository of type sftp or rclone starts that program.
|
|
106
215
|
effects: { reads: ["fs", "network", "process"], writes: ["fs", "network", "process"], destructive: true },
|
|
107
216
|
})
|
|
108
|
-
.handler(async (input, ctx) =>
|
|
217
|
+
.handler(async (input, ctx) => {
|
|
218
|
+
// The deprecated input wins over the module, as before (O55: 0.31.0 removes it)
|
|
219
|
+
if (input.executable !== undefined)
|
|
220
|
+
deprecationNotice("PROCEDURES_RESTIC_EXECUTABLE_INPUT", EXECUTABLE_INPUT_NOTICE);
|
|
221
|
+
const executable = input.executable ?? options.executable;
|
|
222
|
+
return runRestic(input, { executable, env: options.env, cwd: input.cwd, timeout: input.timeout, signal: ctx?.signal, run: options.run });
|
|
223
|
+
})
|
|
109
224
|
.build();
|
|
110
225
|
return [run];
|
|
111
226
|
}
|
package/dist/procedures.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"procedures.js","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"procedures.js","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@procedures/restic",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.29.0",
|
|
5
5
|
"description": "The restic backup program as one procedure: backup, snapshots, restore and forget, with JSON output and no secret as input.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"restic",
|
|
@@ -56,9 +56,9 @@
|
|
|
56
56
|
"provenance": true
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
|
-
"@procedures/client": "0.
|
|
59
|
+
"@procedures/client": "0.29.0",
|
|
60
60
|
"zod": "^4.6.5",
|
|
61
|
-
"@procedures/shell": "0.
|
|
61
|
+
"@procedures/shell": "0.29.0"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"typescript": "^7.0.2",
|
package/src/module.ts
CHANGED
|
@@ -11,11 +11,12 @@ export default resticModule;
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* This function gives the module with the settings of the host, for example the secrets of the
|
|
14
|
-
* repository in `env`. Without options, it gives `resticModule`.
|
|
14
|
+
* repository in `env` and the program in `executable`. Without options, it gives `resticModule`.
|
|
15
15
|
*
|
|
16
16
|
* @example
|
|
17
17
|
* ```ts
|
|
18
|
-
* const
|
|
18
|
+
* const executable = await findRestic();
|
|
19
|
+
* const registry = createRegistry(createResticModule({ executable, env: { RESTIC_REPOSITORY: repository, RESTIC_PASSWORD: password } }));
|
|
19
20
|
* ```
|
|
20
21
|
*/
|
|
21
22
|
export function createResticModule(options: ResticProceduresOptions = {}): ProcedureModule {
|
package/src/procedures.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* restic reads the repository and its keys from the environment (`RESTIC_REPOSITORY`,
|
|
5
5
|
* `RESTIC_PASSWORD`, `B2_ACCOUNT_ID`, `AWS_ACCESS_KEY_ID` and the others), so no secret is an
|
|
6
|
-
* input.
|
|
6
|
+
* input. `restic.run` refuses each input that lets the caller choose a program for restic to
|
|
7
|
+
* start (`resticArgv`).
|
|
7
8
|
*
|
|
8
9
|
* A host gives secrets only to the restic process with the `env` option of `runRestic()` or of
|
|
9
10
|
* `createResticProcedures()`. The procedure input has no field for them.
|
|
@@ -14,8 +15,69 @@ import { runCommand, type CommandOptions, type CommandResult } from "@procedures
|
|
|
14
15
|
import { z } from "zod";
|
|
15
16
|
import { buildArgv, parseJsonOutput, type ArgValue } from "./argv.js";
|
|
16
17
|
|
|
17
|
-
/**
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The flags that `restic.run` refuses: each one names a program for restic to start.
|
|
20
|
+
* `password-command2` is the hidden old name of `from-password-command`.
|
|
21
|
+
*/
|
|
22
|
+
const REFUSED_FLAGS = new Set(["password-command", "from-password-command", "password-command2"]);
|
|
23
|
+
|
|
24
|
+
/** The flags of extended options (`-o key=value`). `o` gives `--o`, which restic refuses, but the check is the same. */
|
|
25
|
+
const OPTION_FLAGS = new Set(["option", "o"]);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The extended options that start a program: the command or the arguments of `ssh` for an sftp
|
|
29
|
+
* repository, and the program or the arguments of `rclone` for an rclone repository.
|
|
30
|
+
*/
|
|
31
|
+
const PROGRAM_OPTIONS = ["sftp.command", "sftp.args", "rclone.program", "rclone.args"] as const;
|
|
32
|
+
|
|
33
|
+
/** The flags whose value is a repository location. `repo2` is the hidden old name of `from-repo`. */
|
|
34
|
+
const REPOSITORY_FLAGS = new Set(["repo", "from-repo", "repo2"]);
|
|
35
|
+
|
|
36
|
+
/** The values of one named argument, as texts. */
|
|
37
|
+
function argTexts(value: ArgValue): string[] {
|
|
38
|
+
if (Array.isArray(value)) return [...value];
|
|
39
|
+
return typeof value === "boolean" ? [] : [String(value)];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The program option that an `--option` value sets, or undefined. restic reads the value as a list
|
|
44
|
+
* of `key=value` pairs separated with commas, with quotes, and it ignores the case and the spaces of
|
|
45
|
+
* a key. A key is one run of characters in each of these forms, so a search in the lowercase value
|
|
46
|
+
* finds each spelling.
|
|
47
|
+
*/
|
|
48
|
+
function programOption(value: string): string | undefined {
|
|
49
|
+
const text = value.toLowerCase();
|
|
50
|
+
return PROGRAM_OPTIONS.find((key) => text.includes(key));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The text with its percent escapes decoded, or the text itself when an escape is not valid. */
|
|
54
|
+
function decoded(text: string): string {
|
|
55
|
+
try {
|
|
56
|
+
return decodeURIComponent(text);
|
|
57
|
+
} catch {
|
|
58
|
+
return text;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The reason to refuse a repository location, or undefined. restic gives the host of an sftp
|
|
64
|
+
* repository to `ssh`, and the remote of an rclone repository to `rclone`, as one argument. A value
|
|
65
|
+
* that starts with `-` becomes an option of that program, and an option of `ssh` can start a program.
|
|
66
|
+
*/
|
|
67
|
+
function repositoryRefusal(location: string): string | undefined {
|
|
68
|
+
let target: string | undefined;
|
|
69
|
+
if (location.startsWith("sftp://")) {
|
|
70
|
+
// restic reads this form as a URL: the host comes without its brackets and with its escapes decoded
|
|
71
|
+
const authority = location.slice("sftp://".length).split("/")[0] ?? "";
|
|
72
|
+
target = decoded(authority.slice(authority.lastIndexOf("@") + 1)).replace(/^\[/, "");
|
|
73
|
+
} else if (location.startsWith("sftp:")) {
|
|
74
|
+
const head = location.slice("sftp:".length).split(":")[0] ?? "";
|
|
75
|
+
target = head.split("@").slice(-1)[0];
|
|
76
|
+
} else if (location.startsWith("rclone:")) {
|
|
77
|
+
target = location.slice("rclone:".length);
|
|
78
|
+
}
|
|
79
|
+
return target?.startsWith("-") ? "the host or the remote of the repository starts with -, so ssh or rclone reads it as an option" : undefined;
|
|
80
|
+
}
|
|
19
81
|
|
|
20
82
|
const RunInputSchema = z.object({
|
|
21
83
|
path: z
|
|
@@ -29,7 +91,17 @@ const RunInputSchema = z.object({
|
|
|
29
91
|
.describe('The flags: { tag: ["daily"], "keep-daily": 7, "dry-run": true }'),
|
|
30
92
|
repository: z.string().min(1).optional().describe("The repository (--repo). Default: RESTIC_REPOSITORY of the environment."),
|
|
31
93
|
json: z.boolean().default(true).describe("Ask for JSON output (--json) and read it (default: true)"),
|
|
32
|
-
|
|
94
|
+
/**
|
|
95
|
+
* The restic program.
|
|
96
|
+
*
|
|
97
|
+
* @deprecated Give `executable` to `createResticModule()`, or set `RESTIC_EXE`. Version 0.31.0
|
|
98
|
+
* refuses this input (audit pass 2, O55), because it lets the caller start any program.
|
|
99
|
+
*/
|
|
100
|
+
executable: z
|
|
101
|
+
.string()
|
|
102
|
+
.min(1)
|
|
103
|
+
.optional()
|
|
104
|
+
.describe("Deprecated: give executable to createResticModule(), or set RESTIC_EXE. The restic program (default: RESTIC_EXE of the environment, then restic)"),
|
|
33
105
|
cwd: z.string().optional().describe("The working folder"),
|
|
34
106
|
timeout: z.number().int().positive().optional().describe("The time limit, in milliseconds"),
|
|
35
107
|
});
|
|
@@ -65,10 +137,37 @@ export interface ResticCommand {
|
|
|
65
137
|
readonly json?: boolean | undefined;
|
|
66
138
|
}
|
|
67
139
|
|
|
68
|
-
/**
|
|
140
|
+
/**
|
|
141
|
+
* This function gives the argv of a restic command. It refuses each input that lets the caller
|
|
142
|
+
* choose a program for restic to start:
|
|
143
|
+
*
|
|
144
|
+
* - the flags `--password-command`, `--from-password-command` and `--password-command2`;
|
|
145
|
+
* - an extended option (`--option`) that sets `sftp.command`, `sftp.args`, `rclone.program` or
|
|
146
|
+
* `rclone.args`, in each spelling that restic reads;
|
|
147
|
+
* - an sftp or rclone repository whose host or remote starts with `-` (`repository`, `--repo`,
|
|
148
|
+
* `--from-repo`, `--repo2`);
|
|
149
|
+
* - a positional argument that starts with `-` after a last flag without a value. A flag that
|
|
150
|
+
* takes a value then reads the `--` that ends the flags, and restic reads the next arguments as flags.
|
|
151
|
+
*/
|
|
69
152
|
export function resticArgv(input: ResticCommand): string[] {
|
|
70
|
-
|
|
153
|
+
const entries = Object.entries(input.args ?? {});
|
|
154
|
+
for (const [key, value] of entries) {
|
|
71
155
|
if (REFUSED_FLAGS.has(key)) throw new Error(`restic.run refuses --${key}: it starts a program`);
|
|
156
|
+
for (const text of argTexts(value)) {
|
|
157
|
+
const option = OPTION_FLAGS.has(key) ? programOption(text) : undefined;
|
|
158
|
+
if (option !== undefined) throw new Error(`restic.run refuses the option ${option}: it starts a program`);
|
|
159
|
+
const refusal = REPOSITORY_FLAGS.has(key) ? repositoryRefusal(text) : undefined;
|
|
160
|
+
if (refusal !== undefined) throw new Error(`restic.run refuses --${key}: ${refusal}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const repositoryProblem = input.repository !== undefined ? repositoryRefusal(input.repository) : undefined;
|
|
164
|
+
if (repositoryProblem !== undefined) throw new Error(`restic.run refuses the repository: ${repositoryProblem}`);
|
|
165
|
+
// The last named argument that gives argv an element: false and an empty list give none
|
|
166
|
+
const last = entries.filter(([, value]) => value !== false && !(Array.isArray(value) && value.length === 0)).at(-1);
|
|
167
|
+
if (last?.[1] === true && (input.positional ?? []).some((item) => item.startsWith("-"))) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`restic.run refuses a positional argument that starts with - after the flag --${last[0]} without a value: put a flag with a value last, or start the argument with ./`
|
|
170
|
+
);
|
|
72
171
|
}
|
|
73
172
|
const flags = [...(input.repository !== undefined ? [`--repo=${input.repository}`] : []), ...(input.json ? ["--json"] : [])];
|
|
74
173
|
return buildArgv({ path: input.path, args: input.args, positional: input.positional, flags });
|
|
@@ -142,8 +241,28 @@ export async function runRestic(command: ResticCommand, options: ResticRunOption
|
|
|
142
241
|
};
|
|
143
242
|
}
|
|
144
243
|
|
|
244
|
+
/** The codes of the deprecation notices that this process emitted. */
|
|
245
|
+
const emitted = new Set<string>();
|
|
246
|
+
|
|
247
|
+
/** This function emits a deprecation notice one time in each process. */
|
|
248
|
+
function deprecationNotice(code: string, message: string): void {
|
|
249
|
+
if (emitted.has(code)) return;
|
|
250
|
+
emitted.add(code);
|
|
251
|
+
process.emitWarning(message, { type: "DeprecationWarning", code });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** The notice of the deprecated input `executable` of `restic.run`. */
|
|
255
|
+
const EXECUTABLE_INPUT_NOTICE =
|
|
256
|
+
"The input executable of restic.run is deprecated. @procedures/restic 0.31.0 refuses it. " +
|
|
257
|
+
"Give the program to the host: createResticModule({ executable }), or set RESTIC_EXE.";
|
|
258
|
+
|
|
145
259
|
/** The settings of the host for the procedures of this package. The procedure input cannot change them. */
|
|
146
260
|
export interface ResticProceduresOptions {
|
|
261
|
+
/**
|
|
262
|
+
* The restic program, for example the result of `findRestic()`. The default is `RESTIC_EXE` of
|
|
263
|
+
* the environment of the host, then `restic`. The deprecated input `executable` wins over it.
|
|
264
|
+
*/
|
|
265
|
+
readonly executable?: string | undefined;
|
|
147
266
|
/** The variables for the restic process only, for example `RESTIC_PASSWORD`. Refer to `ResticRunOptions.env`. */
|
|
148
267
|
readonly env?: Readonly<Record<string, string | undefined>> | undefined;
|
|
149
268
|
/** The function that starts restic (default: `runCommand`). A test gives a fake. */
|
|
@@ -168,10 +287,12 @@ export function createResticProcedures(options: ResticProceduresOptions = {}): A
|
|
|
168
287
|
// repository of type sftp or rclone starts that program.
|
|
169
288
|
effects: { reads: ["fs", "network", "process"], writes: ["fs", "network", "process"], destructive: true },
|
|
170
289
|
})
|
|
171
|
-
.handler(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
290
|
+
.handler(async (input: RunInput, ctx?: ProcedureContext): Promise<RunOutput> => {
|
|
291
|
+
// The deprecated input wins over the module, as before (O55: 0.31.0 removes it)
|
|
292
|
+
if (input.executable !== undefined) deprecationNotice("PROCEDURES_RESTIC_EXECUTABLE_INPUT", EXECUTABLE_INPUT_NOTICE);
|
|
293
|
+
const executable = input.executable ?? options.executable;
|
|
294
|
+
return runRestic(input, { executable, env: options.env, cwd: input.cwd, timeout: input.timeout, signal: ctx?.signal, run: options.run });
|
|
295
|
+
})
|
|
175
296
|
.build() as AnyProcedure;
|
|
176
297
|
return [run];
|
|
177
298
|
}
|