@datatruck/cli 0.41.3 → 0.41.5
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/lib/repositories/ResticRepository.js +4 -10
- package/lib/utils/fs.js +1 -1
- package/lib/utils/restic.d.ts +23 -5
- package/lib/utils/restic.js +111 -18
- package/package.json +1 -1
|
@@ -96,7 +96,6 @@ class ResticRepository extends RepositoryAbstract_1.RepositoryAbstract {
|
|
|
96
96
|
log: data.options.verbose,
|
|
97
97
|
});
|
|
98
98
|
const result = await restic.snapshots({
|
|
99
|
-
json: true,
|
|
100
99
|
tags: [
|
|
101
100
|
...(data.options.ids?.map((id) => ResticRepository.createSnapshotTag(id.length === 8 ? RepositoryAbstract_1.SnapshotTagEnum.SHORT_ID : RepositoryAbstract_1.SnapshotTagEnum.ID, id)) ?? []),
|
|
102
101
|
],
|
|
@@ -196,7 +195,6 @@ class ResticRepository extends RepositoryAbstract_1.RepositoryAbstract {
|
|
|
196
195
|
},
|
|
197
196
|
});
|
|
198
197
|
const [lastSnapshot] = await restic.snapshots({
|
|
199
|
-
json: true,
|
|
200
198
|
tags: [packageTag],
|
|
201
199
|
latest: 1,
|
|
202
200
|
});
|
|
@@ -289,18 +287,14 @@ class ResticRepository extends RepositoryAbstract_1.RepositoryAbstract {
|
|
|
289
287
|
if (!snapshot)
|
|
290
288
|
throw new error_1.AppError(`Snapshot not found`);
|
|
291
289
|
const restic = new restic_1.Restic({
|
|
292
|
-
env:
|
|
293
|
-
...(await this.buildEnv()),
|
|
294
|
-
...(typeof config.password === "string"
|
|
295
|
-
? { RESTIC_PASSWORD2: config.password }
|
|
296
|
-
: { RESTIC_PASSWORD_FILE2: (0, path_1.resolve)(config.password.path) }),
|
|
297
|
-
RESTIC_REPOSITORY2: await restic_1.Restic.formatRepository(config.repository),
|
|
298
|
-
},
|
|
290
|
+
env: await this.buildEnv(),
|
|
299
291
|
log: data.options.verbose,
|
|
300
292
|
});
|
|
301
293
|
let bytes = 0;
|
|
302
294
|
await restic.copy({
|
|
303
|
-
|
|
295
|
+
ids: [snapshot.originalId],
|
|
296
|
+
fromRepo: await restic_1.Restic.formatRepository(config.repository),
|
|
297
|
+
fromRepoPassword: config.password,
|
|
304
298
|
onStream(data) {
|
|
305
299
|
if (data.message_type === "status") {
|
|
306
300
|
bytes = data.total_bytes;
|
package/lib/utils/fs.js
CHANGED
|
@@ -81,7 +81,7 @@ function pathIterator(stream) {
|
|
|
81
81
|
return stream;
|
|
82
82
|
}
|
|
83
83
|
function isLocalDir(path) {
|
|
84
|
-
return /^[\/\.]|([A-Z]:)/i.test(path);
|
|
84
|
+
return /^[\/\.]|([A-Z]:)/i.test(path) || !path.includes(":");
|
|
85
85
|
}
|
|
86
86
|
async function mkdirIfNotExists(path) {
|
|
87
87
|
if (!(await existsDir(path)))
|
package/lib/utils/restic.d.ts
CHANGED
|
@@ -40,16 +40,24 @@ export type ResticBackupStream = {
|
|
|
40
40
|
export declare class Restic {
|
|
41
41
|
readonly options: {
|
|
42
42
|
log?: boolean;
|
|
43
|
-
env:
|
|
43
|
+
env: {
|
|
44
|
+
RESTIC_REPOSITORY: string;
|
|
45
|
+
RESTIC_PASSWORD?: string;
|
|
46
|
+
RESTIC_PASSWORD_FILE?: string;
|
|
47
|
+
};
|
|
44
48
|
};
|
|
45
49
|
constructor(options: {
|
|
46
50
|
log?: boolean;
|
|
47
|
-
env:
|
|
51
|
+
env: {
|
|
52
|
+
RESTIC_REPOSITORY: string;
|
|
53
|
+
RESTIC_PASSWORD?: string;
|
|
54
|
+
RESTIC_PASSWORD_FILE?: string;
|
|
55
|
+
};
|
|
48
56
|
});
|
|
49
57
|
static formatRepository(input: ResticRepositoryUri, hidePassword?: boolean): Promise<string>;
|
|
50
58
|
private createProcess;
|
|
51
59
|
exec(args: string[], options?: AsyncProcessOptions): Promise<number>;
|
|
52
|
-
|
|
60
|
+
json<T>(args: string[], options?: AsyncProcessOptions): Promise<T>;
|
|
53
61
|
checkRepository(): Promise<boolean>;
|
|
54
62
|
forget(options: {
|
|
55
63
|
snapshotId?: string;
|
|
@@ -63,13 +71,16 @@ export declare class Restic {
|
|
|
63
71
|
keepTag?: string[];
|
|
64
72
|
tag?: string[];
|
|
65
73
|
prune?: boolean;
|
|
74
|
+
args?: string[];
|
|
66
75
|
}): Promise<void>;
|
|
67
76
|
snapshots(options: {
|
|
77
|
+
ids?: string[];
|
|
68
78
|
tags?: string[];
|
|
69
79
|
paths?: string[];
|
|
70
80
|
latest?: number;
|
|
71
81
|
json?: boolean;
|
|
72
|
-
|
|
82
|
+
group?: ("path" | "tags" | "host")[];
|
|
83
|
+
args?: string[];
|
|
73
84
|
}): Promise<{
|
|
74
85
|
time: string;
|
|
75
86
|
tree: string;
|
|
@@ -92,10 +103,16 @@ export declare class Restic {
|
|
|
92
103
|
allowEmptySnapshot?: boolean;
|
|
93
104
|
onStream?: (data: ResticBackupStream) => void;
|
|
94
105
|
createEmptyDir?: () => Promise<string>;
|
|
106
|
+
args?: string[];
|
|
95
107
|
}): Promise<void>;
|
|
96
108
|
copy(options: {
|
|
97
|
-
|
|
109
|
+
ids: string[];
|
|
110
|
+
fromRepo?: string;
|
|
111
|
+
fromRepoPassword?: string | {
|
|
112
|
+
path: string;
|
|
113
|
+
};
|
|
98
114
|
onStream?: (data: ResticBackupStream) => void;
|
|
115
|
+
args?: string[];
|
|
99
116
|
}): Promise<void>;
|
|
100
117
|
restore(options: {
|
|
101
118
|
id: string;
|
|
@@ -105,5 +122,6 @@ export declare class Restic {
|
|
|
105
122
|
*/
|
|
106
123
|
progressInterval?: number | false;
|
|
107
124
|
onStream?: (data: ResticBackupStream) => void;
|
|
125
|
+
args?: string[];
|
|
108
126
|
}): Promise<AsyncProcess>;
|
|
109
127
|
}
|
package/lib/utils/restic.js
CHANGED
|
@@ -1,9 +1,62 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
3
|
+
if (value !== null && value !== void 0) {
|
|
4
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
5
|
+
var dispose, inner;
|
|
6
|
+
if (async) {
|
|
7
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
8
|
+
dispose = value[Symbol.asyncDispose];
|
|
9
|
+
}
|
|
10
|
+
if (dispose === void 0) {
|
|
11
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
12
|
+
dispose = value[Symbol.dispose];
|
|
13
|
+
if (async) inner = dispose;
|
|
14
|
+
}
|
|
15
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
16
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
17
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
18
|
+
}
|
|
19
|
+
else if (async) {
|
|
20
|
+
env.stack.push({ async: true });
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
};
|
|
24
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
25
|
+
return function (env) {
|
|
26
|
+
function fail(e) {
|
|
27
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
28
|
+
env.hasError = true;
|
|
29
|
+
}
|
|
30
|
+
var r, s = 0;
|
|
31
|
+
function next() {
|
|
32
|
+
while (r = env.stack.pop()) {
|
|
33
|
+
try {
|
|
34
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
35
|
+
if (r.dispose) {
|
|
36
|
+
var result = r.dispose.call(r.value);
|
|
37
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
38
|
+
}
|
|
39
|
+
else s |= 1;
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
fail(e);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
46
|
+
if (env.hasError) throw env.error;
|
|
47
|
+
}
|
|
48
|
+
return next();
|
|
49
|
+
};
|
|
50
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
51
|
+
var e = new Error(message);
|
|
52
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
53
|
+
});
|
|
2
54
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
55
|
exports.Restic = void 0;
|
|
4
56
|
const async_process_1 = require("./async-process");
|
|
5
57
|
const fs_1 = require("./fs");
|
|
6
58
|
const string_1 = require("./string");
|
|
59
|
+
const temp_1 = require("./temp");
|
|
7
60
|
const promises_1 = require("fs/promises");
|
|
8
61
|
const path_1 = require("path");
|
|
9
62
|
const emptySnapshotTag = "empty-snapshot";
|
|
@@ -31,7 +84,8 @@ class Restic {
|
|
|
31
84
|
createProcess(args, options) {
|
|
32
85
|
return new async_process_1.AsyncProcess("restic", args, {
|
|
33
86
|
stdio: ["ignore", "pipe", "pipe"],
|
|
34
|
-
|
|
87
|
+
...(options ?? {}),
|
|
88
|
+
env: { ...process.env, ...this.options.env, ...options?.env },
|
|
35
89
|
$log: this.options.log
|
|
36
90
|
? {
|
|
37
91
|
exec: true,
|
|
@@ -46,14 +100,14 @@ class Restic {
|
|
|
46
100
|
],
|
|
47
101
|
}
|
|
48
102
|
: {},
|
|
49
|
-
...(options ?? {}),
|
|
50
103
|
});
|
|
51
104
|
}
|
|
52
105
|
async exec(args, options) {
|
|
53
106
|
return await this.createProcess(args, options).waitForClose();
|
|
54
107
|
}
|
|
55
|
-
async
|
|
56
|
-
|
|
108
|
+
async json(args, options) {
|
|
109
|
+
const stdout = await this.createProcess(args, options).stdout.fetch();
|
|
110
|
+
return JSON.parse(stdout);
|
|
57
111
|
}
|
|
58
112
|
async checkRepository() {
|
|
59
113
|
return ((await this.exec(["cat", "config"], {
|
|
@@ -87,19 +141,22 @@ class Restic {
|
|
|
87
141
|
: []),
|
|
88
142
|
...(options.tag ? options.tag.flatMap((v) => ["--tag", v]) : []),
|
|
89
143
|
...(options.prune ? ["--prune"] : []),
|
|
144
|
+
...(options.args || []),
|
|
90
145
|
...(options.snapshotId ? [options.snapshotId] : []),
|
|
91
146
|
]);
|
|
92
147
|
}
|
|
93
148
|
async snapshots(options) {
|
|
94
|
-
const json =
|
|
149
|
+
const json = options.json ?? true;
|
|
150
|
+
return await this.json([
|
|
95
151
|
"snapshots",
|
|
152
|
+
...(json ? ["--json"] : []),
|
|
96
153
|
...(options.tags?.flatMap((tag) => [`--tag`, tag]) ?? []),
|
|
97
|
-
...(options.json ? ["--json"] : []),
|
|
98
154
|
...(options.paths?.flatMap((path) => ["--path", path]) ?? []),
|
|
155
|
+
...(options.group ? ["--group-by", options.group.join(",")] : []),
|
|
99
156
|
...(options.latest ? ["--latest", options.latest.toString()] : []),
|
|
100
|
-
...(options.
|
|
157
|
+
...(options.args || []),
|
|
158
|
+
...(options.ids || []),
|
|
101
159
|
]);
|
|
102
|
-
return JSON.parse(json);
|
|
103
160
|
}
|
|
104
161
|
async backup(options) {
|
|
105
162
|
try {
|
|
@@ -111,6 +168,7 @@ class Restic {
|
|
|
111
168
|
...(options.tags?.flatMap((v) => ["--tag", v]) ?? []),
|
|
112
169
|
...(options.setPaths?.flatMap((v) => ["--set-path", v]) ?? []),
|
|
113
170
|
...(options.parent ? ["--parent", options.parent] : []),
|
|
171
|
+
...(options.args || []),
|
|
114
172
|
...options.paths,
|
|
115
173
|
], { cwd: options.cwd });
|
|
116
174
|
if (options.onStream) {
|
|
@@ -151,16 +209,51 @@ class Restic {
|
|
|
151
209
|
}
|
|
152
210
|
}
|
|
153
211
|
async copy(options) {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
212
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
213
|
+
try {
|
|
214
|
+
const rawPassword = typeof options.fromRepoPassword === "string"
|
|
215
|
+
? options.fromRepoPassword
|
|
216
|
+
: undefined;
|
|
217
|
+
const fromPasswordDir = __addDisposableResource(env_1, rawPassword
|
|
218
|
+
? await (0, temp_1.useTempDir)("restic-copy")
|
|
219
|
+
: undefined, true);
|
|
220
|
+
const fromPasswordFile = fromPasswordDir
|
|
221
|
+
? (0, path_1.join)(fromPasswordDir.path, "password.txt")
|
|
222
|
+
: !!options.fromRepoPassword &&
|
|
223
|
+
typeof options.fromRepoPassword !== "string"
|
|
224
|
+
? options.fromRepoPassword.path
|
|
225
|
+
: undefined;
|
|
226
|
+
if (fromPasswordFile && rawPassword)
|
|
227
|
+
await (0, promises_1.writeFile)(fromPasswordFile, rawPassword);
|
|
228
|
+
const copy = this.createProcess([
|
|
229
|
+
"copy",
|
|
230
|
+
"--json",
|
|
231
|
+
...(fromPasswordFile ? ["--from-password-file", fromPasswordFile] : []),
|
|
232
|
+
...(options.fromRepo ? ["--from-repo", options.fromRepo] : []),
|
|
233
|
+
...(options.args || []),
|
|
234
|
+
...options.ids,
|
|
235
|
+
], {
|
|
236
|
+
env: {},
|
|
160
237
|
});
|
|
238
|
+
if (options.onStream) {
|
|
239
|
+
await copy.stdout.parseLines((line) => {
|
|
240
|
+
if (line.startsWith("{") && line.endsWith("}")) {
|
|
241
|
+
options.onStream?.(JSON.parse(line));
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
await copy.waitForClose();
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch (e_1) {
|
|
250
|
+
env_1.error = e_1;
|
|
251
|
+
env_1.hasError = true;
|
|
161
252
|
}
|
|
162
|
-
|
|
163
|
-
|
|
253
|
+
finally {
|
|
254
|
+
const result_1 = __disposeResources(env_1);
|
|
255
|
+
if (result_1)
|
|
256
|
+
await result_1;
|
|
164
257
|
}
|
|
165
258
|
}
|
|
166
259
|
async restore(options) {
|
|
@@ -180,8 +273,7 @@ class Restic {
|
|
|
180
273
|
}
|
|
181
274
|
}
|
|
182
275
|
const snapshots = await this.snapshots({
|
|
183
|
-
|
|
184
|
-
json: true,
|
|
276
|
+
ids: [options.id],
|
|
185
277
|
});
|
|
186
278
|
if (typeof progressInterval === "number")
|
|
187
279
|
progressRutine();
|
|
@@ -192,6 +284,7 @@ class Restic {
|
|
|
192
284
|
options.id,
|
|
193
285
|
"--target",
|
|
194
286
|
options.target,
|
|
287
|
+
...(options.args || []),
|
|
195
288
|
]);
|
|
196
289
|
if (options.onStream) {
|
|
197
290
|
await result.stdout.parseLines((line) => {
|