@datatruck/restic 0.0.5 → 0.0.6
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/actions/backup.js +21 -9
- package/lib/actions/copy.js +8 -2
- package/lib/actions/init.js +1 -1
- package/lib/actions/prune.js +7 -2
- package/lib/utils/fs.d.ts +1 -0
- package/lib/utils/fs.js +1 -0
- package/lib/utils/mysql.js +20 -3
- package/lib/utils/ntfy.d.ts +4 -1
- package/lib/utils/ntfy.js +15 -9
- package/package.json +1 -1
package/lib/actions/backup.js
CHANGED
|
@@ -21,11 +21,16 @@ export class Backup extends Action {
|
|
|
21
21
|
RESTIC_REPOSITORY: repo.uri,
|
|
22
22
|
},
|
|
23
23
|
});
|
|
24
|
+
let logId;
|
|
24
25
|
let space;
|
|
25
26
|
let snapshotsAmount;
|
|
26
27
|
let bytes = 0;
|
|
27
28
|
let files = 0;
|
|
28
29
|
return await createRunner(async () => {
|
|
30
|
+
logId = await this.ntfy.send("Backup", {
|
|
31
|
+
Repository: repo.name,
|
|
32
|
+
Package: pkg.name,
|
|
33
|
+
});
|
|
29
34
|
await restic.tryInit();
|
|
30
35
|
const targetPath = isLocalDir(repo.uri) ? repo.uri : undefined;
|
|
31
36
|
space = await checkDiskSpace({
|
|
@@ -71,7 +76,7 @@ export class Backup extends Action {
|
|
|
71
76
|
Snapshots: snapshotsAmount,
|
|
72
77
|
Duration: data.duration,
|
|
73
78
|
Error: data.error?.message,
|
|
74
|
-
}, data.error);
|
|
79
|
+
}, { error: data.error, logId });
|
|
75
80
|
return {
|
|
76
81
|
error: data.error,
|
|
77
82
|
files,
|
|
@@ -88,12 +93,17 @@ export class Backup extends Action {
|
|
|
88
93
|
const sqlDumps = [];
|
|
89
94
|
const backups = [];
|
|
90
95
|
let localRepositoryPaths = [];
|
|
96
|
+
const tags = {
|
|
97
|
+
dt: true,
|
|
98
|
+
id: randomString(8),
|
|
99
|
+
};
|
|
91
100
|
await createRunner(async () => {
|
|
92
101
|
const repositories = this.cm.filterRepositories(options.repositories);
|
|
93
102
|
const packages = this.cm.filterPackages(options.packages);
|
|
94
103
|
const packageNames = packages.map((p) => p.name);
|
|
95
104
|
const tasks = this.filterTasks(packageNames);
|
|
96
105
|
await this.ntfy.send(`Backup start`, {
|
|
106
|
+
Id: tags.id,
|
|
97
107
|
Repositories: repositories.length,
|
|
98
108
|
Packages: packageNames.length,
|
|
99
109
|
Tasks: tasks?.length,
|
|
@@ -101,10 +111,6 @@ export class Backup extends Action {
|
|
|
101
111
|
localRepositoryPaths = repositories
|
|
102
112
|
.filter((repo) => isLocalDir(repo.uri))
|
|
103
113
|
.map((repo) => repo.uri);
|
|
104
|
-
const tags = {
|
|
105
|
-
dt: true,
|
|
106
|
-
id: randomString(8),
|
|
107
|
-
};
|
|
108
114
|
for (const task of tasks ?? []) {
|
|
109
115
|
if (task.type === "mysql-dump") {
|
|
110
116
|
const mysqlDump = new MySQLDump({
|
|
@@ -139,8 +145,13 @@ export class Backup extends Action {
|
|
|
139
145
|
}
|
|
140
146
|
}
|
|
141
147
|
}).start(async (data) => {
|
|
142
|
-
|
|
143
|
-
|
|
148
|
+
try {
|
|
149
|
+
for (const sqlDump of sqlDumps)
|
|
150
|
+
await sqlDump.cleanup();
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error(error);
|
|
154
|
+
}
|
|
144
155
|
const summary = backups.reduce((acc, p) => {
|
|
145
156
|
if (!acc[p.pkgName])
|
|
146
157
|
acc[p.pkgName] = {
|
|
@@ -169,6 +180,7 @@ export class Backup extends Action {
|
|
|
169
180
|
if (diskStats.error)
|
|
170
181
|
console.error(diskStats.error);
|
|
171
182
|
await this.ntfy.send("Backup end", {
|
|
183
|
+
Id: tags.id,
|
|
172
184
|
Duration: data.duration,
|
|
173
185
|
Size: formatBytes(size),
|
|
174
186
|
Error: data.error?.message,
|
|
@@ -176,7 +188,7 @@ export class Backup extends Action {
|
|
|
176
188
|
!!diskStats.result?.length && { key: "Disk stats", value: "" },
|
|
177
189
|
...(diskStats.result?.map((p) => ({
|
|
178
190
|
key: p.name,
|
|
179
|
-
value: `${formatBytes(p.
|
|
191
|
+
value: `${formatBytes(p.occupied)}/${formatBytes(p.total)} (${progressPercent(p.total, p.occupied)}%)`,
|
|
180
192
|
level: 1,
|
|
181
193
|
})) || []),
|
|
182
194
|
!!sqlDumpProcesses.length && { key: "SQL Dumps", value: "" },
|
|
@@ -192,7 +204,7 @@ export class Backup extends Action {
|
|
|
192
204
|
level: 1,
|
|
193
205
|
})),
|
|
194
206
|
],
|
|
195
|
-
}, error);
|
|
207
|
+
}, { error });
|
|
196
208
|
if (options.prune) {
|
|
197
209
|
await new Prune(this.config, this.global).run({
|
|
198
210
|
packages: options.packages,
|
package/lib/actions/copy.js
CHANGED
|
@@ -40,8 +40,14 @@ export class Copy extends Action {
|
|
|
40
40
|
["GODEBUG"]: "http2client=0",
|
|
41
41
|
},
|
|
42
42
|
});
|
|
43
|
+
let logId;
|
|
43
44
|
let space;
|
|
44
45
|
await createRunner(async () => {
|
|
46
|
+
logId = await this.ntfy.send("Copy", {
|
|
47
|
+
Id: snapshot.short_id,
|
|
48
|
+
Source: sourceRepo.name,
|
|
49
|
+
Target: targetRepo.name,
|
|
50
|
+
});
|
|
45
51
|
if (!this.initializedRepos.has(targetRepo.name)) {
|
|
46
52
|
await target.tryInit();
|
|
47
53
|
this.initializedRepos.add(targetRepo.name);
|
|
@@ -66,7 +72,7 @@ export class Copy extends Action {
|
|
|
66
72
|
}),
|
|
67
73
|
Duration: data.duration,
|
|
68
74
|
Error: data.error?.message,
|
|
69
|
-
}, data.error);
|
|
75
|
+
}, { error: data.error, logId });
|
|
70
76
|
});
|
|
71
77
|
return space?.diff ?? 0;
|
|
72
78
|
}
|
|
@@ -99,7 +105,7 @@ export class Copy extends Action {
|
|
|
99
105
|
}),
|
|
100
106
|
Duration: data.duration,
|
|
101
107
|
Error: data.error?.message,
|
|
102
|
-
}, data.error);
|
|
108
|
+
}, { error: data.error });
|
|
103
109
|
if (options.prune) {
|
|
104
110
|
await new Prune(this.config, this.global).run({
|
|
105
111
|
packages: options.packages,
|
package/lib/actions/init.js
CHANGED
package/lib/actions/prune.js
CHANGED
|
@@ -8,10 +8,15 @@ import { progressPercent } from "@datatruck/cli/utils/math.js";
|
|
|
8
8
|
import { match } from "@datatruck/cli/utils/string.js";
|
|
9
9
|
export class Prune extends Action {
|
|
10
10
|
async runSingle(repoName, pkgName, policy) {
|
|
11
|
+
let logId;
|
|
11
12
|
let stats;
|
|
12
13
|
let space;
|
|
13
14
|
let removed;
|
|
14
15
|
await createRunner(async () => {
|
|
16
|
+
logId = await this.ntfy.send("Prune", {
|
|
17
|
+
Repository: repoName,
|
|
18
|
+
Package: pkgName,
|
|
19
|
+
});
|
|
15
20
|
const [restic, repo] = this.cm.createRestic(repoName, this.verbose);
|
|
16
21
|
const targetPath = isLocalDir(repo.uri) ? repo.uri : undefined;
|
|
17
22
|
space = await checkDiskSpace({
|
|
@@ -46,7 +51,7 @@ export class Prune extends Action {
|
|
|
46
51
|
}),
|
|
47
52
|
Duration: data.duration,
|
|
48
53
|
Error: data.error?.message,
|
|
49
|
-
}, data.error);
|
|
54
|
+
}, { error: data.error, logId });
|
|
50
55
|
});
|
|
51
56
|
return {
|
|
52
57
|
diffSize: space?.diff ?? 0,
|
|
@@ -107,7 +112,7 @@ export class Prune extends Action {
|
|
|
107
112
|
!!diskStats.result?.length && { key: "Disk stats", value: "" },
|
|
108
113
|
...(diskStats.result?.map((p) => ({
|
|
109
114
|
key: p.name,
|
|
110
|
-
value: `${formatBytes(p.
|
|
115
|
+
value: `${formatBytes(p.occupied)}/${formatBytes(p.total)} (${progressPercent(p.total, p.occupied)}%)`,
|
|
111
116
|
level: 1,
|
|
112
117
|
})) || []),
|
|
113
118
|
],
|
package/lib/utils/fs.d.ts
CHANGED
package/lib/utils/fs.js
CHANGED
package/lib/utils/mysql.js
CHANGED
|
@@ -54,6 +54,7 @@ import { runParallel } from "@datatruck/cli/utils/async.js";
|
|
|
54
54
|
import { formatBytes } from "@datatruck/cli/utils/bytes.js";
|
|
55
55
|
import { duration } from "@datatruck/cli/utils/date.js";
|
|
56
56
|
import { ensureFreeDiskSpace, fetchDiskStats, } from "@datatruck/cli/utils/fs.js";
|
|
57
|
+
import { progressPercent } from "@datatruck/cli/utils/math.js";
|
|
57
58
|
import { createMysqlCli } from "@datatruck/cli/utils/mysql.js";
|
|
58
59
|
import { match } from "@datatruck/cli/utils/string.js";
|
|
59
60
|
import { existsSync } from "fs";
|
|
@@ -101,7 +102,7 @@ export class MySQLDump {
|
|
|
101
102
|
for (const outPath of this.outPaths) {
|
|
102
103
|
const parentFolder = basename(dirname(outPath));
|
|
103
104
|
if (!parentFolder.startsWith("sql-dump"))
|
|
104
|
-
throw new Error(`sql-dump out dir must begins with 'sql-dump': ${outPath}`);
|
|
105
|
+
throw new Error(`sql-dump out dir base name must begins with 'sql-dump': ${outPath}`);
|
|
105
106
|
if (existsSync(outPath))
|
|
106
107
|
await rm(outPath, { recursive: true });
|
|
107
108
|
if (!init)
|
|
@@ -129,7 +130,20 @@ export class MySQLDump {
|
|
|
129
130
|
for (const item of items)
|
|
130
131
|
this.outPaths.add(dirname(item.out));
|
|
131
132
|
await this.cleanup(true);
|
|
133
|
+
let logId;
|
|
134
|
+
let logIntervalId;
|
|
132
135
|
try {
|
|
136
|
+
logId = await this.ntfy.send("SQL dump", {
|
|
137
|
+
Name: item.name,
|
|
138
|
+
});
|
|
139
|
+
logIntervalId = setInterval(async () => {
|
|
140
|
+
await this.ntfy.send("SQL dump", {
|
|
141
|
+
Name: item.name,
|
|
142
|
+
Size: formatBytes(stats.bytes),
|
|
143
|
+
Files: `${stats.files}/${items.length} (${progressPercent(items.length, stats.files)}%)`,
|
|
144
|
+
Duration: duration(Date.now() - now),
|
|
145
|
+
}, { logId });
|
|
146
|
+
}, 30_000);
|
|
133
147
|
await runParallel({
|
|
134
148
|
items,
|
|
135
149
|
concurrency: this.options.concurrency ?? 1,
|
|
@@ -153,17 +167,20 @@ export class MySQLDump {
|
|
|
153
167
|
catch (inError) {
|
|
154
168
|
error = inError;
|
|
155
169
|
}
|
|
170
|
+
finally {
|
|
171
|
+
clearInterval(logIntervalId);
|
|
172
|
+
}
|
|
156
173
|
this.processes.push({
|
|
157
174
|
name: item.name,
|
|
158
175
|
error,
|
|
159
176
|
stats,
|
|
160
177
|
});
|
|
161
178
|
await this.ntfy.send("SQL dump", {
|
|
162
|
-
|
|
179
|
+
Name: item.name,
|
|
163
180
|
Size: formatBytes(stats.bytes),
|
|
164
181
|
Files: stats.files,
|
|
165
182
|
Duration: duration(Date.now() - now),
|
|
166
183
|
Error: error?.message,
|
|
167
|
-
}, error);
|
|
184
|
+
}, { error, logId });
|
|
168
185
|
}
|
|
169
186
|
}
|
package/lib/utils/ntfy.d.ts
CHANGED
|
@@ -21,6 +21,9 @@ export declare class Ntfy {
|
|
|
21
21
|
private formatTitle;
|
|
22
22
|
private formatMessage;
|
|
23
23
|
private formatMessageObject;
|
|
24
|
-
send(inTitle: string, message: MessageObject,
|
|
24
|
+
send(inTitle: string, message: MessageObject, options?: {
|
|
25
|
+
error?: Error | boolean;
|
|
26
|
+
logId?: string;
|
|
27
|
+
}): Promise<string | undefined>;
|
|
25
28
|
}
|
|
26
29
|
export {};
|
package/lib/utils/ntfy.js
CHANGED
|
@@ -38,31 +38,37 @@ export class Ntfy {
|
|
|
38
38
|
})
|
|
39
39
|
.join("\n");
|
|
40
40
|
}
|
|
41
|
-
async send(inTitle, message,
|
|
41
|
+
async send(inTitle, message, options = {}) {
|
|
42
42
|
const title = this.formatTitle(inTitle);
|
|
43
43
|
const body = this.formatMessageObject(message);
|
|
44
44
|
const lines = [title, body].filter((v) => v.length);
|
|
45
45
|
if (lines.length)
|
|
46
46
|
console.info([...lines, ""].join("\n"));
|
|
47
|
-
const
|
|
48
|
-
priority: error ? "high" : "default",
|
|
49
|
-
tags: [error ? "red_circle" : "green_circle"],
|
|
47
|
+
const ntfyOptions = {
|
|
48
|
+
priority: options.error ? "high" : "default",
|
|
49
|
+
tags: [options.error ? "red_circle" : "green_circle"],
|
|
50
50
|
};
|
|
51
51
|
try {
|
|
52
|
-
if (this.options.token)
|
|
53
|
-
|
|
52
|
+
if (this.options.token) {
|
|
53
|
+
const token = options.logId
|
|
54
|
+
? `${this.options.token}/${options.logId}`
|
|
55
|
+
: this.options.token;
|
|
56
|
+
const response = await fetch(`https://ntfy.sh/${token}`, {
|
|
54
57
|
dispatcher: this.agent,
|
|
55
58
|
method: "POST",
|
|
56
59
|
body: unstyle(body),
|
|
57
60
|
headers: {
|
|
58
61
|
Markdown: "yes",
|
|
59
62
|
Title: unstyle(title),
|
|
60
|
-
Priority:
|
|
61
|
-
...(
|
|
62
|
-
Tags:
|
|
63
|
+
Priority: ntfyOptions.priority ?? "default",
|
|
64
|
+
...(ntfyOptions.tags && {
|
|
65
|
+
Tags: ntfyOptions.tags?.join(","),
|
|
63
66
|
}),
|
|
64
67
|
},
|
|
65
68
|
});
|
|
69
|
+
const json = (await response.json());
|
|
70
|
+
return json.id;
|
|
71
|
+
}
|
|
66
72
|
await setTimeout(this.options.delay ?? 800);
|
|
67
73
|
}
|
|
68
74
|
catch (error) {
|