@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.
@@ -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
- for (const sqlDump of sqlDumps)
143
- await sqlDump.cleanup();
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.free)}/${formatBytes(p.total)} (${progressPercent(p.total, p.total - p.free)}%)`,
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,
@@ -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,
@@ -20,7 +20,7 @@ export class Init extends Action {
20
20
  Exists: exists ? "yes" : "no",
21
21
  Duration: data.duration,
22
22
  Error: data.error?.message,
23
- }, data.error);
23
+ }, { error: data.error });
24
24
  });
25
25
  }
26
26
  async run(options) {
@@ -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.free)}/${formatBytes(p.total)} (${progressPercent(p.total, p.total - p.free)}%)`,
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
@@ -12,5 +12,6 @@ export declare function getDiskName(inPath: string): Promise<string>;
12
12
  export declare function fetchMultipleDiskStats(paths: string[]): Promise<{
13
13
  name: string;
14
14
  free: number;
15
+ occupied: number;
15
16
  total: number;
16
17
  }[]>;
package/lib/utils/fs.js CHANGED
@@ -54,6 +54,7 @@ export async function fetchMultipleDiskStats(paths) {
54
54
  result.push({
55
55
  name: await getDiskName(path),
56
56
  ...stats,
57
+ occupied: stats.total - stats.free,
57
58
  });
58
59
  }
59
60
  return result;
@@ -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
- Package: item.name,
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
  }
@@ -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, error?: Error | boolean): Promise<void>;
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, error) {
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 options = {
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
- await fetch(`https://ntfy.sh/${this.options.token}`, {
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: options.priority ?? "default",
61
- ...(options.tags && {
62
- Tags: options.tags?.join(","),
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datatruck/restic",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Tool for creating and managing backups",
5
5
  "homepage": "https://github.com/swordev/datatruck#readme",
6
6
  "bugs": {