@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/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 registry = createRegistry(createResticModule({ env: { RESTIC_REPOSITORY: repository, RESTIC_PASSWORD: password } }));
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;
@@ -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;;;;;;;;GAQG;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"}
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 registry = createRegistry(createResticModule({ env: { RESTIC_REPOSITORY: repository, RESTIC_PASSWORD: password } }));
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 = {}) {
@@ -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;;;;;;;;GAQG;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"}
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"}
@@ -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. `--password-command` is refused: it starts a program of the caller.
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
- /** This function gives the argv of a restic command. It refuses the flags that start a program. */
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. */
@@ -1 +1 @@
1
- {"version":3,"file":"procedures.d.ts","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"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"}
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"}
@@ -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. `--password-command` is refused: it starts a program of the caller.
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
- /** The flags that `restic.run` refuses. */
16
- const REFUSED_FLAGS = new Set(["password-command"]);
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
- executable: z.string().min(1).optional().describe("The restic program (default: RESTIC_EXE of the environment, then restic)"),
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
- /** This function gives the argv of a restic command. It refuses the flags that start a program. */
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
- for (const key of Object.keys(input.args ?? {})) {
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) => runRestic(input, { executable: input.executable, env: options.env, cwd: input.cwd, timeout: input.timeout, signal: ctx?.signal, run: options.run }))
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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"procedures.js","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"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"}
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.28.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.28.0",
59
+ "@procedures/client": "0.29.0",
60
60
  "zod": "^4.6.5",
61
- "@procedures/shell": "0.28.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 registry = createRegistry(createResticModule({ env: { RESTIC_REPOSITORY: repository, RESTIC_PASSWORD: password } }));
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. `--password-command` is refused: it starts a program of the caller.
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
- /** The flags that `restic.run` refuses. */
18
- const REFUSED_FLAGS = new Set(["password-command"]);
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
- executable: z.string().min(1).optional().describe("The restic program (default: RESTIC_EXE of the environment, then restic)"),
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
- /** This function gives the argv of a restic command. It refuses the flags that start a program. */
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
- for (const key of Object.keys(input.args ?? {})) {
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
- async (input: RunInput, ctx?: ProcedureContext): Promise<RunOutput> =>
173
- runRestic(input, { executable: input.executable, env: options.env, cwd: input.cwd, timeout: input.timeout, signal: ctx?.signal, run: options.run }),
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
  }