@datatruck/restic 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config.schema.json +136 -21
- package/lib/actions/backup.d.ts +38 -36
- package/lib/actions/backup.js +169 -112
- package/lib/actions/base.d.ts +10 -0
- package/lib/actions/base.js +19 -0
- package/lib/actions/copy.d.ts +17 -11
- package/lib/actions/copy.js +99 -109
- package/lib/actions/init.d.ts +3 -8
- package/lib/actions/init.js +34 -49
- package/lib/actions/prune.d.ts +10 -0
- package/lib/actions/prune.js +105 -0
- package/lib/bin.js +15 -0
- package/lib/config.d.ts +42 -6
- package/lib/config.js +31 -0
- package/lib/index.d.ts +3 -2
- package/lib/index.js +1 -0
- package/lib/utils/async.d.ts +10 -0
- package/lib/utils/async.js +36 -0
- package/lib/utils/fs.d.ts +10 -1
- package/lib/utils/fs.js +39 -2
- package/lib/utils/mysql.d.ts +2 -8
- package/lib/utils/mysql.js +7 -10
- package/lib/utils/ntfy.d.ts +12 -4
- package/lib/utils/ntfy.js +36 -16
- package/lib/utils/string.d.ts +1 -0
- package/lib/utils/string.js +3 -0
- package/package.json +2 -2
- package/lib/utils/restic-backup.d.ts +0 -49
- package/lib/utils/restic-backup.js +0 -91
package/config.schema.json
CHANGED
|
@@ -16,6 +16,33 @@
|
|
|
16
16
|
"verbose": {
|
|
17
17
|
"type": "boolean"
|
|
18
18
|
},
|
|
19
|
+
"prunePolicy": {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"properties": {
|
|
22
|
+
"keepMinutely": {
|
|
23
|
+
"type": "number"
|
|
24
|
+
},
|
|
25
|
+
"keepDaily": {
|
|
26
|
+
"type": "number"
|
|
27
|
+
},
|
|
28
|
+
"keepHourly": {
|
|
29
|
+
"type": "number"
|
|
30
|
+
},
|
|
31
|
+
"keepLast": {
|
|
32
|
+
"type": "number"
|
|
33
|
+
},
|
|
34
|
+
"keepMonthly": {
|
|
35
|
+
"type": "number"
|
|
36
|
+
},
|
|
37
|
+
"keepWeekly": {
|
|
38
|
+
"type": "number"
|
|
39
|
+
},
|
|
40
|
+
"keepYearly": {
|
|
41
|
+
"type": "number"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"additionalProperties": false
|
|
45
|
+
},
|
|
19
46
|
"tasks": {
|
|
20
47
|
"type": "array",
|
|
21
48
|
"items": {
|
|
@@ -84,27 +111,7 @@
|
|
|
84
111
|
"type": "number"
|
|
85
112
|
},
|
|
86
113
|
"connection": {
|
|
87
|
-
"
|
|
88
|
-
"properties": {
|
|
89
|
-
"hostname": {
|
|
90
|
-
"type": "string"
|
|
91
|
-
},
|
|
92
|
-
"username": {
|
|
93
|
-
"type": "string"
|
|
94
|
-
},
|
|
95
|
-
"password": {
|
|
96
|
-
"type": "string"
|
|
97
|
-
},
|
|
98
|
-
"database": {
|
|
99
|
-
"type": "string"
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
"additionalProperties": false,
|
|
103
|
-
"required": [
|
|
104
|
-
"hostname",
|
|
105
|
-
"password",
|
|
106
|
-
"username"
|
|
107
|
-
]
|
|
114
|
+
"$ref": "#/definitions/Omit<MysqlCliOptions,\"verbose\">"
|
|
108
115
|
}
|
|
109
116
|
},
|
|
110
117
|
"additionalProperties": false,
|
|
@@ -140,6 +147,33 @@
|
|
|
140
147
|
"items": {
|
|
141
148
|
"type": "string"
|
|
142
149
|
}
|
|
150
|
+
},
|
|
151
|
+
"prunePolicy": {
|
|
152
|
+
"type": "object",
|
|
153
|
+
"properties": {
|
|
154
|
+
"keepMinutely": {
|
|
155
|
+
"type": "number"
|
|
156
|
+
},
|
|
157
|
+
"keepDaily": {
|
|
158
|
+
"type": "number"
|
|
159
|
+
},
|
|
160
|
+
"keepHourly": {
|
|
161
|
+
"type": "number"
|
|
162
|
+
},
|
|
163
|
+
"keepLast": {
|
|
164
|
+
"type": "number"
|
|
165
|
+
},
|
|
166
|
+
"keepMonthly": {
|
|
167
|
+
"type": "number"
|
|
168
|
+
},
|
|
169
|
+
"keepWeekly": {
|
|
170
|
+
"type": "number"
|
|
171
|
+
},
|
|
172
|
+
"keepYearly": {
|
|
173
|
+
"type": "number"
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"additionalProperties": false
|
|
143
177
|
}
|
|
144
178
|
},
|
|
145
179
|
"additionalProperties": false,
|
|
@@ -162,6 +196,33 @@
|
|
|
162
196
|
},
|
|
163
197
|
"uri": {
|
|
164
198
|
"type": "string"
|
|
199
|
+
},
|
|
200
|
+
"prunePolicy": {
|
|
201
|
+
"type": "object",
|
|
202
|
+
"properties": {
|
|
203
|
+
"keepMinutely": {
|
|
204
|
+
"type": "number"
|
|
205
|
+
},
|
|
206
|
+
"keepDaily": {
|
|
207
|
+
"type": "number"
|
|
208
|
+
},
|
|
209
|
+
"keepHourly": {
|
|
210
|
+
"type": "number"
|
|
211
|
+
},
|
|
212
|
+
"keepLast": {
|
|
213
|
+
"type": "number"
|
|
214
|
+
},
|
|
215
|
+
"keepMonthly": {
|
|
216
|
+
"type": "number"
|
|
217
|
+
},
|
|
218
|
+
"keepWeekly": {
|
|
219
|
+
"type": "number"
|
|
220
|
+
},
|
|
221
|
+
"keepYearly": {
|
|
222
|
+
"type": "number"
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
"additionalProperties": false
|
|
165
226
|
}
|
|
166
227
|
},
|
|
167
228
|
"additionalProperties": false,
|
|
@@ -178,5 +239,59 @@
|
|
|
178
239
|
"packages",
|
|
179
240
|
"repositories"
|
|
180
241
|
],
|
|
242
|
+
"definitions": {
|
|
243
|
+
"Omit<MysqlCliOptions,\"verbose\">": {
|
|
244
|
+
"type": "object",
|
|
245
|
+
"properties": {
|
|
246
|
+
"password": {
|
|
247
|
+
"anyOf": [
|
|
248
|
+
{
|
|
249
|
+
"type": "object",
|
|
250
|
+
"properties": {
|
|
251
|
+
"path": {
|
|
252
|
+
"type": "string"
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
"additionalProperties": false,
|
|
256
|
+
"required": [
|
|
257
|
+
"path"
|
|
258
|
+
]
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"type": "string"
|
|
262
|
+
}
|
|
263
|
+
]
|
|
264
|
+
},
|
|
265
|
+
"hostname": {
|
|
266
|
+
"type": "string"
|
|
267
|
+
},
|
|
268
|
+
"port": {
|
|
269
|
+
"type": "number"
|
|
270
|
+
},
|
|
271
|
+
"username": {
|
|
272
|
+
"type": "string"
|
|
273
|
+
},
|
|
274
|
+
"database": {
|
|
275
|
+
"type": "string"
|
|
276
|
+
},
|
|
277
|
+
"ssl": {
|
|
278
|
+
"type": "boolean"
|
|
279
|
+
},
|
|
280
|
+
"vars": {
|
|
281
|
+
"$ref": "#/definitions/Record<string,string|number>"
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
"additionalProperties": false,
|
|
285
|
+
"required": [
|
|
286
|
+
"hostname",
|
|
287
|
+
"password",
|
|
288
|
+
"username"
|
|
289
|
+
]
|
|
290
|
+
},
|
|
291
|
+
"Record<string,string|number>": {
|
|
292
|
+
"type": "object",
|
|
293
|
+
"additionalProperties": false
|
|
294
|
+
}
|
|
295
|
+
},
|
|
181
296
|
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
182
297
|
}
|
package/lib/actions/backup.d.ts
CHANGED
|
@@ -1,40 +1,42 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { Ntfy } from "../utils/ntfy.js";
|
|
4
|
-
import { CommonResticBackupTags, ResticBackup } from "../utils/restic-backup.js";
|
|
5
|
-
export type BackupRunOptions = {
|
|
1
|
+
import { Action } from "./base.js";
|
|
2
|
+
export type BackupOptions = {
|
|
6
3
|
packages?: string[];
|
|
7
4
|
repositories?: string[];
|
|
5
|
+
prune?: boolean;
|
|
8
6
|
};
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
7
|
+
export type CommonResticBackupTags = {
|
|
8
|
+
id: string;
|
|
9
|
+
shortId: string;
|
|
10
|
+
hostname: string;
|
|
11
|
+
date: string;
|
|
12
|
+
vendor: string;
|
|
13
|
+
version: string;
|
|
14
|
+
};
|
|
15
|
+
export type ResticBackupTags = CommonResticBackupTags & {
|
|
16
|
+
package: string;
|
|
17
|
+
tags?: string[];
|
|
18
|
+
};
|
|
19
|
+
export declare class Backup extends Action {
|
|
20
|
+
protected runSingle(repoName: string, pkgName: string, tags: CommonResticBackupTags): Promise<{
|
|
21
|
+
error: Error | undefined;
|
|
22
|
+
files: number;
|
|
23
|
+
bytes: number;
|
|
24
|
+
diffSize: number | undefined;
|
|
25
|
+
}>;
|
|
26
|
+
protected filterTasks(packageNames: string[]): {
|
|
27
|
+
type: "mysql-dump";
|
|
28
|
+
packages: string[];
|
|
29
|
+
name: string;
|
|
30
|
+
config: {
|
|
31
|
+
database: string;
|
|
32
|
+
out: {
|
|
33
|
+
package?: string;
|
|
34
|
+
tables: string[];
|
|
35
|
+
path: string | false;
|
|
36
|
+
}[] | string;
|
|
37
|
+
concurrency?: number;
|
|
38
|
+
connection: import("../utils/mysql.js").MySQLDumpOptions["connection"];
|
|
39
|
+
};
|
|
40
|
+
}[] | undefined;
|
|
41
|
+
run(options?: BackupOptions): Promise<void>;
|
|
40
42
|
}
|
package/lib/actions/backup.js
CHANGED
|
@@ -1,78 +1,116 @@
|
|
|
1
|
+
import { createRunner, safeRun } from "../utils/async.js";
|
|
2
|
+
import { checkDiskSpace, fetchMultipleDiskStats } from "../utils/fs.js";
|
|
1
3
|
import { MySQLDump } from "../utils/mysql.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
+
import { Action } from "./base.js";
|
|
5
|
+
import { Prune } from "./prune.js";
|
|
6
|
+
import { ResticRepository } from "@datatruck/cli/repositories/ResticRepository.js";
|
|
4
7
|
import { formatBytes } from "@datatruck/cli/utils/bytes.js";
|
|
5
|
-
import {
|
|
8
|
+
import { isLocalDir } from "@datatruck/cli/utils/fs.js";
|
|
9
|
+
import { progressPercent } from "@datatruck/cli/utils/math.js";
|
|
10
|
+
import { Restic } from "@datatruck/cli/utils/restic.js";
|
|
6
11
|
import { match } from "@datatruck/cli/utils/string.js";
|
|
7
12
|
import { randomUUID } from "crypto";
|
|
8
13
|
import { hostname } from "os";
|
|
9
|
-
export class Backup {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
this.tags = {
|
|
19
|
-
id: randomUUID().replaceAll("-", ""),
|
|
20
|
-
get shortId() {
|
|
21
|
-
return this.id.slice(0, 8);
|
|
14
|
+
export class Backup extends Action {
|
|
15
|
+
async runSingle(repoName, pkgName, tags) {
|
|
16
|
+
const repo = this.cm.findRepository(repoName);
|
|
17
|
+
const pkg = this.cm.findPackage(pkgName);
|
|
18
|
+
const restic = new Restic({
|
|
19
|
+
log: this.verbose,
|
|
20
|
+
env: {
|
|
21
|
+
RESTIC_PASSWORD: repo.password,
|
|
22
|
+
RESTIC_REPOSITORY: repo.uri,
|
|
22
23
|
},
|
|
23
|
-
hostname: this.config.hostname ?? hostname(),
|
|
24
|
-
date: new Date().toISOString(),
|
|
25
|
-
vendor: "dtt-restic",
|
|
26
|
-
version: "1",
|
|
27
|
-
};
|
|
28
|
-
this.verbose = this.global?.verbose ?? this.config.verbose;
|
|
29
|
-
this.ntfy = new Ntfy({
|
|
30
|
-
token: this.config.ntfyToken,
|
|
31
|
-
titlePrefix: `[${this.tags.hostname}] `,
|
|
32
24
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
name: repo.name,
|
|
41
|
-
connection: {
|
|
42
|
-
password: repo.password,
|
|
43
|
-
uri: repo.uri,
|
|
44
|
-
},
|
|
45
|
-
}, this.ntfy, this.verbose));
|
|
46
|
-
const sqlDumps = this.config.tasks
|
|
47
|
-
?.filter((task) => task.type === "mysql-dump" &&
|
|
48
|
-
task.packages.some((name) => match(name, packageNames)))
|
|
49
|
-
.map((task) => [
|
|
50
|
-
new MySQLDump({
|
|
25
|
+
let space;
|
|
26
|
+
let bytes = 0;
|
|
27
|
+
let files = 0;
|
|
28
|
+
return await createRunner(async () => {
|
|
29
|
+
await restic.tryInit();
|
|
30
|
+
const targetPath = isLocalDir(repo.uri) ? repo.uri : undefined;
|
|
31
|
+
space = await checkDiskSpace({
|
|
51
32
|
minFreeSpace: this.config.minFreeSpace,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
33
|
+
minFreeSpacePath: targetPath ?? process.cwd(),
|
|
34
|
+
targetPath,
|
|
35
|
+
rutine: () => {
|
|
36
|
+
const pkgTags = {
|
|
37
|
+
...tags,
|
|
38
|
+
package: pkg.name,
|
|
39
|
+
tags: [],
|
|
40
|
+
};
|
|
41
|
+
return restic.backup({
|
|
42
|
+
tags: ResticRepository.createSnapshotTags(pkgTags),
|
|
43
|
+
paths: [pkg.path],
|
|
44
|
+
exclude: pkg.exclude,
|
|
45
|
+
onStream(data) {
|
|
46
|
+
if (data.message_type === "summary") {
|
|
47
|
+
files = data.total_files_processed;
|
|
48
|
+
bytes = data.total_bytes_processed;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}).start(async (data) => {
|
|
55
|
+
await this.ntfy.send("Backup", {
|
|
56
|
+
Repository: repo.name,
|
|
57
|
+
Package: pkg.name,
|
|
58
|
+
Size: formatBytes(bytes) +
|
|
59
|
+
(space !== undefined ? ` (${formatBytes(space.diff, true)})` : ""),
|
|
60
|
+
Files: files,
|
|
61
|
+
Duration: data.duration,
|
|
62
|
+
Error: data.error?.message,
|
|
63
|
+
}, data.error);
|
|
64
|
+
return {
|
|
65
|
+
error: data.error,
|
|
66
|
+
files,
|
|
67
|
+
bytes,
|
|
68
|
+
diffSize: space?.diff,
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
filterTasks(packageNames) {
|
|
73
|
+
return this.config.tasks?.filter((task) => task.type === "mysql-dump" &&
|
|
74
|
+
task.packages.some((pattern) => match(pattern, packageNames)));
|
|
60
75
|
}
|
|
61
76
|
async run(options = {}) {
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
const sqlDumps = [];
|
|
78
|
+
const backups = [];
|
|
79
|
+
let localRepositoryPaths = [];
|
|
80
|
+
await createRunner(async () => {
|
|
81
|
+
const repositories = this.cm.filterRepositories(options.repositories);
|
|
82
|
+
const packages = this.cm.filterPackages(options.packages);
|
|
83
|
+
const packageNames = packages.map((p) => p.name);
|
|
84
|
+
const tasks = this.filterTasks(packageNames);
|
|
68
85
|
await this.ntfy.send(`Backup start`, {
|
|
69
|
-
|
|
86
|
+
Repositories: repositories.length,
|
|
87
|
+
Packages: packageNames.length,
|
|
88
|
+
Tasks: tasks?.length,
|
|
70
89
|
});
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
90
|
+
localRepositoryPaths = repositories
|
|
91
|
+
.filter((repo) => isLocalDir(repo.uri))
|
|
92
|
+
.map((repo) => repo.uri);
|
|
93
|
+
const tags = {
|
|
94
|
+
id: randomUUID().replaceAll("-", ""),
|
|
95
|
+
get shortId() {
|
|
96
|
+
return this.id.slice(0, 8);
|
|
97
|
+
},
|
|
98
|
+
hostname: this.config.hostname ?? hostname(),
|
|
99
|
+
date: new Date().toISOString(),
|
|
100
|
+
vendor: "dtt-restic",
|
|
101
|
+
version: "1",
|
|
102
|
+
};
|
|
103
|
+
for (const task of tasks ?? []) {
|
|
104
|
+
if (task.type === "mysql-dump") {
|
|
105
|
+
const mysqlDump = new MySQLDump({
|
|
106
|
+
minFreeSpace: this.config.minFreeSpace,
|
|
107
|
+
verbose: this.verbose,
|
|
108
|
+
name: task.name,
|
|
109
|
+
connection: task.config.connection,
|
|
110
|
+
concurrency: task.config.concurrency,
|
|
111
|
+
}, this.ntfy);
|
|
112
|
+
sqlDumps.push(mysqlDump);
|
|
113
|
+
await mysqlDump.run({
|
|
76
114
|
database: task.config.database,
|
|
77
115
|
name: task.name,
|
|
78
116
|
out: typeof task.config.out === "string"
|
|
@@ -83,60 +121,79 @@ export class Backup {
|
|
|
83
121
|
? o.path
|
|
84
122
|
: false,
|
|
85
123
|
})),
|
|
86
|
-
}
|
|
87
|
-
|
|
124
|
+
});
|
|
125
|
+
}
|
|
88
126
|
}
|
|
89
|
-
for (const backup of repositories)
|
|
90
|
-
await backup.run(packages);
|
|
91
|
-
}
|
|
92
|
-
catch (inError) {
|
|
93
|
-
fatalError = inError;
|
|
94
|
-
}
|
|
95
|
-
finally {
|
|
96
|
-
for (const [sqlDump] of sqlDumps)
|
|
97
|
-
await sqlDump.cleanup();
|
|
98
|
-
const backupSummary = {};
|
|
99
127
|
for (const repo of repositories) {
|
|
100
|
-
for (const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
errors: 0,
|
|
107
|
-
bytes: 0,
|
|
108
|
-
};
|
|
109
|
-
backupSummary[process.name].total++;
|
|
110
|
-
backupSummary[process.name].bytes += process.stats.bytes;
|
|
111
|
-
if (process.error) {
|
|
112
|
-
backupSummary[process.name].errors++;
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
backupSummary[process.name].success++;
|
|
116
|
-
}
|
|
128
|
+
for (const pkg of packages) {
|
|
129
|
+
const result = await this.runSingle(repo.name, pkg.name, tags);
|
|
130
|
+
backups.push({
|
|
131
|
+
pkgName: pkg.name,
|
|
132
|
+
...result,
|
|
133
|
+
});
|
|
117
134
|
}
|
|
118
135
|
}
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
136
|
+
}).start(async (data) => {
|
|
137
|
+
for (const sqlDump of sqlDumps)
|
|
138
|
+
await sqlDump.cleanup();
|
|
139
|
+
const summary = backups.reduce((acc, p) => {
|
|
140
|
+
if (!acc[p.pkgName])
|
|
141
|
+
acc[p.pkgName] = {
|
|
142
|
+
name: p.pkgName,
|
|
143
|
+
total: 0,
|
|
144
|
+
success: 0,
|
|
145
|
+
errors: 0,
|
|
146
|
+
bytes: 0,
|
|
147
|
+
};
|
|
148
|
+
const group = acc[p.pkgName];
|
|
149
|
+
group.total++;
|
|
150
|
+
group.bytes += p.bytes;
|
|
151
|
+
group[p.error ? "errors" : "success"]++;
|
|
152
|
+
return acc;
|
|
153
|
+
}, {});
|
|
154
|
+
const backupsValues = Object.values(summary);
|
|
155
|
+
const sqlDumpProcesses = sqlDumps.flatMap((sql) => sql.processes);
|
|
156
|
+
const error = !!data.error ||
|
|
157
|
+
sqlDumpProcesses.some((p) => p.error) ||
|
|
158
|
+
backupsValues.some((p) => p.errors);
|
|
124
159
|
const size = [
|
|
125
|
-
...
|
|
126
|
-
...
|
|
160
|
+
...sqlDumpProcesses.map((p) => p.stats.bytes),
|
|
161
|
+
...backupsValues.map((b) => b.bytes),
|
|
127
162
|
].reduce((r, b) => r + b, 0);
|
|
128
|
-
await
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
163
|
+
const diskStats = await safeRun(() => fetchMultipleDiskStats(localRepositoryPaths));
|
|
164
|
+
if (diskStats.error)
|
|
165
|
+
console.error(diskStats.error);
|
|
166
|
+
await this.ntfy.send("Backup end", {
|
|
167
|
+
Duration: data.duration,
|
|
168
|
+
Size: formatBytes(size),
|
|
169
|
+
Error: data.error?.message,
|
|
170
|
+
"": [
|
|
171
|
+
!!diskStats.result?.length && { key: "Disk stats", value: "" },
|
|
172
|
+
...(diskStats.result?.map((p) => ({
|
|
173
|
+
key: p.name,
|
|
174
|
+
value: `${formatBytes(p.free)}/${formatBytes(p.total)} (${progressPercent(p.total, p.free)}%)`,
|
|
175
|
+
level: 1,
|
|
176
|
+
})) || []),
|
|
177
|
+
!!sqlDumpProcesses.length && { key: "SQL Dumps", value: "" },
|
|
178
|
+
...sqlDumpProcesses.map((p) => ({
|
|
179
|
+
key: p.name,
|
|
180
|
+
value: formatBytes(p.stats.bytes),
|
|
181
|
+
level: 1,
|
|
182
|
+
})),
|
|
183
|
+
!!backupsValues.length && { key: "Packages", value: "" },
|
|
184
|
+
...backupsValues.map((p) => ({
|
|
185
|
+
key: p.name,
|
|
186
|
+
value: `${p.success}/${p.total}`,
|
|
187
|
+
level: 1,
|
|
188
|
+
})),
|
|
189
|
+
],
|
|
190
|
+
}, error);
|
|
191
|
+
if (options.prune) {
|
|
192
|
+
await new Prune(this.config, this.global).run({
|
|
193
|
+
packages: options.packages,
|
|
194
|
+
repositories: options.repositories,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
});
|
|
141
198
|
}
|
|
142
199
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Config, ConfigManager, GlobalConfig } from "../config.js";
|
|
2
|
+
import { Ntfy } from "../utils/ntfy.js";
|
|
3
|
+
export declare class Action {
|
|
4
|
+
readonly config: Config;
|
|
5
|
+
readonly global?: GlobalConfig | undefined;
|
|
6
|
+
readonly ntfy: Ntfy;
|
|
7
|
+
protected verbose: boolean | undefined;
|
|
8
|
+
protected cm: ConfigManager;
|
|
9
|
+
constructor(config: Config, global?: GlobalConfig | undefined);
|
|
10
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ConfigManager } from "../config.js";
|
|
2
|
+
import { Ntfy } from "../utils/ntfy.js";
|
|
3
|
+
export class Action {
|
|
4
|
+
config;
|
|
5
|
+
global;
|
|
6
|
+
ntfy;
|
|
7
|
+
verbose;
|
|
8
|
+
cm;
|
|
9
|
+
constructor(config, global) {
|
|
10
|
+
this.config = config;
|
|
11
|
+
this.global = global;
|
|
12
|
+
this.cm = new ConfigManager(this.config);
|
|
13
|
+
this.verbose = this.global?.verbose ?? this.config.verbose;
|
|
14
|
+
this.ntfy = new Ntfy({
|
|
15
|
+
token: this.config.ntfyToken,
|
|
16
|
+
titlePrefix: this.config.hostname,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
package/lib/actions/copy.d.ts
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export type CopyRunOptions = {
|
|
1
|
+
import { Action } from "./base.js";
|
|
2
|
+
export type CopyOptions = {
|
|
4
3
|
packages?: string[];
|
|
5
4
|
source: string;
|
|
6
5
|
targets: string[];
|
|
6
|
+
prune?: boolean;
|
|
7
7
|
};
|
|
8
|
-
export declare class Copy {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
export declare class Copy extends Action {
|
|
9
|
+
protected initializedRepos: Set<string>;
|
|
10
|
+
private findSnapshots;
|
|
11
|
+
private findPackageTag;
|
|
12
|
+
runSingle(options: {
|
|
13
|
+
source: string;
|
|
14
|
+
target: string;
|
|
15
|
+
snapshot: {
|
|
16
|
+
id: string;
|
|
17
|
+
short_id: string;
|
|
18
|
+
tags: string[];
|
|
19
|
+
};
|
|
20
|
+
}): Promise<number>;
|
|
21
|
+
run(options: CopyOptions): Promise<void>;
|
|
16
22
|
}
|