@datatruck/restic 0.0.1 → 0.0.3
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 +179 -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/create.d.ts +10 -0
- package/lib/actions/create.js +49 -0
- package/lib/actions/init.d.ts +3 -8
- package/lib/actions/init.js +34 -49
- package/lib/actions/prune.d.ts +14 -0
- package/lib/actions/prune.js +123 -0
- package/lib/bin.js +2 -57
- package/lib/config.d.ts +49 -6
- package/lib/config.js +47 -0
- package/lib/create-bin.d.ts +3 -0
- package/lib/create-bin.js +83 -0
- package/lib/index.d.ts +5 -3
- package/lib/index.js +2 -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/lib/config.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { MySQLDumpOptions } from "./utils/mysql.js";
|
|
2
|
+
import { Restic } from "@datatruck/cli/utils/restic.js";
|
|
1
3
|
export type GlobalConfig = {
|
|
2
4
|
config?: string;
|
|
3
5
|
verbose?: boolean;
|
|
@@ -8,6 +10,7 @@ export type Config = {
|
|
|
8
10
|
ntfyToken?: string;
|
|
9
11
|
minFreeSpace?: string;
|
|
10
12
|
verbose?: boolean;
|
|
13
|
+
prunePolicy?: PrunePolicy;
|
|
11
14
|
tasks?: {
|
|
12
15
|
type: "mysql-dump";
|
|
13
16
|
packages: string[];
|
|
@@ -20,26 +23,66 @@ export type Config = {
|
|
|
20
23
|
path: string | false;
|
|
21
24
|
}[] | string;
|
|
22
25
|
concurrency?: number;
|
|
23
|
-
connection:
|
|
24
|
-
hostname: string;
|
|
25
|
-
username: string;
|
|
26
|
-
password: string;
|
|
27
|
-
database?: string;
|
|
28
|
-
};
|
|
26
|
+
connection: MySQLDumpOptions["connection"];
|
|
29
27
|
};
|
|
30
28
|
}[];
|
|
31
29
|
packages: {
|
|
32
30
|
name: string;
|
|
33
31
|
path: string;
|
|
34
32
|
exclude?: string[];
|
|
33
|
+
prunePolicy?: PrunePolicy;
|
|
35
34
|
}[];
|
|
36
35
|
repositories: {
|
|
37
36
|
name: string;
|
|
38
37
|
password: string;
|
|
39
38
|
uri: string;
|
|
39
|
+
prunePolicy?: PrunePolicy;
|
|
40
40
|
}[];
|
|
41
41
|
};
|
|
42
|
+
export type PrunePolicy = {
|
|
43
|
+
keepMinutely?: number;
|
|
44
|
+
keepDaily?: number;
|
|
45
|
+
keepHourly?: number;
|
|
46
|
+
keepLast?: number;
|
|
47
|
+
keepMonthly?: number;
|
|
48
|
+
keepWeekly?: number;
|
|
49
|
+
keepYearly?: number;
|
|
50
|
+
};
|
|
42
51
|
export declare function defineConfig(config: Config): Config;
|
|
43
52
|
export declare function validateConfig(config: unknown): Promise<void>;
|
|
44
53
|
export declare function readConfigSchemaFile(): Promise<unknown>;
|
|
45
54
|
export declare function parseConfigFile(path?: string): Promise<Config>;
|
|
55
|
+
export declare class ConfigManager {
|
|
56
|
+
readonly config: Config;
|
|
57
|
+
constructor(config: Config);
|
|
58
|
+
filterRepositories(filter: string[] | undefined): {
|
|
59
|
+
name: string;
|
|
60
|
+
password: string;
|
|
61
|
+
uri: string;
|
|
62
|
+
prunePolicy?: PrunePolicy;
|
|
63
|
+
}[];
|
|
64
|
+
filterPackages(filter: string[] | undefined): {
|
|
65
|
+
name: string;
|
|
66
|
+
path: string;
|
|
67
|
+
exclude?: string[];
|
|
68
|
+
prunePolicy?: PrunePolicy;
|
|
69
|
+
}[];
|
|
70
|
+
findRepository(name: string): {
|
|
71
|
+
name: string;
|
|
72
|
+
password: string;
|
|
73
|
+
uri: string;
|
|
74
|
+
prunePolicy?: PrunePolicy;
|
|
75
|
+
};
|
|
76
|
+
findPackage(name: string): {
|
|
77
|
+
name: string;
|
|
78
|
+
path: string;
|
|
79
|
+
exclude?: string[];
|
|
80
|
+
prunePolicy?: PrunePolicy;
|
|
81
|
+
};
|
|
82
|
+
createRestic(repoName: string, verbose: boolean | undefined): readonly [Restic, {
|
|
83
|
+
name: string;
|
|
84
|
+
password: string;
|
|
85
|
+
uri: string;
|
|
86
|
+
prunePolicy?: PrunePolicy;
|
|
87
|
+
}];
|
|
88
|
+
}
|
package/lib/config.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { parseJSONFile } from "./utils/fs.js";
|
|
2
|
+
import { Restic } from "@datatruck/cli/utils/restic.js";
|
|
3
|
+
import { match } from "@datatruck/cli/utils/string.js";
|
|
2
4
|
import { Ajv } from "ajv";
|
|
3
5
|
export function defineConfig(config) {
|
|
4
6
|
return config;
|
|
@@ -20,3 +22,48 @@ export async function parseConfigFile(path = "datatruck.restic.json") {
|
|
|
20
22
|
await validateConfig(config);
|
|
21
23
|
return config;
|
|
22
24
|
}
|
|
25
|
+
export class ConfigManager {
|
|
26
|
+
config;
|
|
27
|
+
constructor(config) {
|
|
28
|
+
this.config = config;
|
|
29
|
+
}
|
|
30
|
+
filterRepositories(filter) {
|
|
31
|
+
const repositories = this.config.repositories.filter((v) => filter ? match(v.name, filter) : true);
|
|
32
|
+
if (!repositories.length)
|
|
33
|
+
throw new Error(filter
|
|
34
|
+
? `No repositories found for filter: ${filter.join(", ")}`
|
|
35
|
+
: `No repositories found`);
|
|
36
|
+
return repositories;
|
|
37
|
+
}
|
|
38
|
+
filterPackages(filter) {
|
|
39
|
+
const packages = this.config.packages.filter((v) => filter ? match(v.name, filter) : true);
|
|
40
|
+
if (!packages.length)
|
|
41
|
+
throw new Error(filter
|
|
42
|
+
? `No packages found for filter: ${filter.join(", ")}`
|
|
43
|
+
: `No packages found`);
|
|
44
|
+
return packages;
|
|
45
|
+
}
|
|
46
|
+
findRepository(name) {
|
|
47
|
+
const repo = this.config.repositories.find((repo) => repo.name === name);
|
|
48
|
+
if (!repo)
|
|
49
|
+
throw new Error(`Repository '${name}' not found`);
|
|
50
|
+
return repo;
|
|
51
|
+
}
|
|
52
|
+
findPackage(name) {
|
|
53
|
+
const pkg = this.config.packages.find((pkg) => pkg.name === name);
|
|
54
|
+
if (!pkg)
|
|
55
|
+
throw new Error(`Package '${name}' not found`);
|
|
56
|
+
return pkg;
|
|
57
|
+
}
|
|
58
|
+
createRestic(repoName, verbose) {
|
|
59
|
+
const repo = this.findRepository(repoName);
|
|
60
|
+
const restic = new Restic({
|
|
61
|
+
log: verbose,
|
|
62
|
+
env: {
|
|
63
|
+
RESTIC_REPOSITORY: repo.uri,
|
|
64
|
+
RESTIC_PASSWORD: repo.password,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
return [restic, repo];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Backup } from "./actions/backup.js";
|
|
2
|
+
import { Copy } from "./actions/copy.js";
|
|
3
|
+
import { Create } from "./actions/create.js";
|
|
4
|
+
import { Init } from "./actions/init.js";
|
|
5
|
+
import { Prune } from "./actions/prune.js";
|
|
6
|
+
import { parseConfigFile } from "./config.js";
|
|
7
|
+
import { parseStringList } from "@datatruck/cli/utils/string.js";
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
import { resolve } from "path";
|
|
10
|
+
export function createBin(inConfig) {
|
|
11
|
+
async function load() {
|
|
12
|
+
const globalOptions = { ...program.opts() };
|
|
13
|
+
if (globalOptions.config)
|
|
14
|
+
globalOptions.config = resolve(globalOptions.config);
|
|
15
|
+
const config = inConfig ?? (await parseConfigFile(globalOptions.config));
|
|
16
|
+
globalOptions.verbose = process.env.DEBUG
|
|
17
|
+
? true
|
|
18
|
+
: globalOptions.verbose || config.verbose;
|
|
19
|
+
return { config, globalOptions };
|
|
20
|
+
}
|
|
21
|
+
const program = new Command();
|
|
22
|
+
program.option("-v, --verbose");
|
|
23
|
+
if (!inConfig)
|
|
24
|
+
program.option("-c, --config <path>", "Path to config file", "datatruck.restic.json");
|
|
25
|
+
program
|
|
26
|
+
.command("create")
|
|
27
|
+
.description("Create config file")
|
|
28
|
+
.option("--cwd <path>", "Current working directory to create config file in", ".")
|
|
29
|
+
.option("-c,--config <path>", "Output path for config file", "datatruck.restic.json")
|
|
30
|
+
.option("-f,--force", "Force overwrite if config file already exists")
|
|
31
|
+
.action(async (options) => {
|
|
32
|
+
const create = new Create();
|
|
33
|
+
await create.run(options);
|
|
34
|
+
});
|
|
35
|
+
program
|
|
36
|
+
.command("init")
|
|
37
|
+
.alias("i")
|
|
38
|
+
.description("Run init action")
|
|
39
|
+
.option("-r, --repositories <names>", "Repository names", (v) => parseStringList(v))
|
|
40
|
+
.action(async (options) => {
|
|
41
|
+
const { config, globalOptions } = await load();
|
|
42
|
+
const init = new Init(config, globalOptions);
|
|
43
|
+
await init.run(options);
|
|
44
|
+
});
|
|
45
|
+
program
|
|
46
|
+
.command("backup")
|
|
47
|
+
.alias("b")
|
|
48
|
+
.description("Run backup action")
|
|
49
|
+
.option("-r, --repositories <names>", "Repository names", (v) => parseStringList(v))
|
|
50
|
+
.option("-p, --packages <packages>", "Package names", (v) => parseStringList(v))
|
|
51
|
+
.option("--prune", "Prune after backup")
|
|
52
|
+
.action(async (options) => {
|
|
53
|
+
const { config, globalOptions } = await load();
|
|
54
|
+
const backup = new Backup(config, globalOptions);
|
|
55
|
+
await backup.run(options);
|
|
56
|
+
});
|
|
57
|
+
program
|
|
58
|
+
.command("copy")
|
|
59
|
+
.alias("c")
|
|
60
|
+
.description("Run copy action")
|
|
61
|
+
.option("-p, --packages <packages>", "Package names", (v) => parseStringList(v))
|
|
62
|
+
.requiredOption("-s, --source <name>", "Source repository name")
|
|
63
|
+
.requiredOption("-t, --targets <names>", "Target repository names", (v) => parseStringList(v))
|
|
64
|
+
.option("--prune", "Prune after copy")
|
|
65
|
+
.action(async (options) => {
|
|
66
|
+
const { config, globalOptions } = await load();
|
|
67
|
+
const copy = new Copy(config, globalOptions);
|
|
68
|
+
await copy.run(options);
|
|
69
|
+
});
|
|
70
|
+
program
|
|
71
|
+
.command("prune")
|
|
72
|
+
.alias("p")
|
|
73
|
+
.description("Run prune action")
|
|
74
|
+
.option("-r, --repositories <names>", "Repository names", (v) => parseStringList(v))
|
|
75
|
+
.option("-p, --packages <packages>", "Package names", (v) => parseStringList(v))
|
|
76
|
+
.option("--prune", "Prune after copy")
|
|
77
|
+
.action(async (options) => {
|
|
78
|
+
const { config, globalOptions } = await load();
|
|
79
|
+
const prune = new Prune(config, globalOptions);
|
|
80
|
+
await prune.run(options);
|
|
81
|
+
});
|
|
82
|
+
return program;
|
|
83
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export { Backup, type
|
|
2
|
-
export { Copy, type
|
|
1
|
+
export { Backup, type BackupOptions } from "./actions/backup.js";
|
|
2
|
+
export { Copy, type CopyOptions } from "./actions/copy.js";
|
|
3
3
|
export { Init, type InitOptions } from "./actions/init.js";
|
|
4
|
-
export {
|
|
4
|
+
export { Prune, type PruneOptions } from "./actions/prune.js";
|
|
5
|
+
export { type Config, type GlobalConfig, type PrunePolicy, parseConfigFile, defineConfig, validateConfig, } from "./config.js";
|
|
6
|
+
export { createBin } from "./create-bin.js";
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { Backup } from "./actions/backup.js";
|
|
2
2
|
export { Copy } from "./actions/copy.js";
|
|
3
3
|
export { Init } from "./actions/init.js";
|
|
4
|
+
export { Prune } from "./actions/prune.js";
|
|
4
5
|
export { parseConfigFile, defineConfig, validateConfig, } from "./config.js";
|
|
6
|
+
export { createBin } from "./create-bin.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function createRunner(rutine: () => any): {
|
|
2
|
+
start: <R = any>(cb: (data: {
|
|
3
|
+
error: Error | undefined;
|
|
4
|
+
duration: string;
|
|
5
|
+
}) => Promise<R>) => Promise<R>;
|
|
6
|
+
};
|
|
7
|
+
export declare function safeRun<T>(cb: () => Promise<T>): Promise<{
|
|
8
|
+
error: Error | undefined;
|
|
9
|
+
result: T | undefined;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { duration } from "@datatruck/cli/utils/date.js";
|
|
2
|
+
export function createRunner(rutine) {
|
|
3
|
+
return {
|
|
4
|
+
start: async (cb) => {
|
|
5
|
+
const now = Date.now();
|
|
6
|
+
let error;
|
|
7
|
+
try {
|
|
8
|
+
await rutine();
|
|
9
|
+
}
|
|
10
|
+
catch (inError) {
|
|
11
|
+
error = inError;
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
return await cb({
|
|
15
|
+
duration: duration(Date.now() - now),
|
|
16
|
+
error,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
if (error)
|
|
21
|
+
console.error(error, "\n");
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export async function safeRun(cb) {
|
|
27
|
+
try {
|
|
28
|
+
return {
|
|
29
|
+
error: undefined,
|
|
30
|
+
result: await cb(),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return { error: error, result: undefined };
|
|
35
|
+
}
|
|
36
|
+
}
|
package/lib/utils/fs.d.ts
CHANGED
|
@@ -4,4 +4,13 @@ export declare function checkDiskSpace(options: {
|
|
|
4
4
|
minFreeSpacePath?: string | undefined;
|
|
5
5
|
targetPath?: string | undefined;
|
|
6
6
|
rutine: () => any;
|
|
7
|
-
}): Promise<
|
|
7
|
+
}): Promise<{
|
|
8
|
+
diff: number;
|
|
9
|
+
size: number;
|
|
10
|
+
} | undefined>;
|
|
11
|
+
export declare function getDiskName(inPath: string): Promise<string>;
|
|
12
|
+
export declare function fetchMultipleDiskStats(paths: string[]): Promise<{
|
|
13
|
+
name: string;
|
|
14
|
+
free: number;
|
|
15
|
+
total: number;
|
|
16
|
+
}[]>;
|
package/lib/utils/fs.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { ensureFreeDiskSpace, fastFolderSizeAsync, fetchDiskStats, } from "@datatruck/cli/utils/fs.js";
|
|
2
|
-
import {
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { mkdir, readFile, stat } from "fs/promises";
|
|
4
|
+
import { platform } from "os";
|
|
5
|
+
import { parse, resolve } from "path";
|
|
3
6
|
export async function parseJSONFile(path) {
|
|
4
7
|
const buffer = await readFile(path);
|
|
5
8
|
return JSON.parse(buffer.toString());
|
|
@@ -19,5 +22,39 @@ export async function checkDiskSpace(options) {
|
|
|
19
22
|
const prev = await fastFolderSizeAsync(options.targetPath);
|
|
20
23
|
await options.rutine();
|
|
21
24
|
const next = await fastFolderSizeAsync(options.targetPath);
|
|
22
|
-
return
|
|
25
|
+
return {
|
|
26
|
+
diff: next - prev,
|
|
27
|
+
size: next,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export async function getDiskName(inPath) {
|
|
31
|
+
const path = resolve(inPath);
|
|
32
|
+
if (platform() === "win32") {
|
|
33
|
+
return parse(path).root[0];
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
return execSync(`df "${path}"`).toString().split("\n")[1].split(/\s+/)[0];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export async function fetchMultipleDiskStats(paths) {
|
|
40
|
+
for (const path of paths) {
|
|
41
|
+
await mkdir(path, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
const devices = {};
|
|
44
|
+
for (const inPath of paths) {
|
|
45
|
+
const path = resolve(inPath);
|
|
46
|
+
const info = await stat(path);
|
|
47
|
+
if (!devices[info.dev])
|
|
48
|
+
devices[info.dev] = path;
|
|
49
|
+
}
|
|
50
|
+
const result = [];
|
|
51
|
+
for (const dev in devices) {
|
|
52
|
+
const path = devices[dev];
|
|
53
|
+
const stats = await fetchDiskStats(path);
|
|
54
|
+
result.push({
|
|
55
|
+
name: await getDiskName(path),
|
|
56
|
+
...stats,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
23
60
|
}
|
package/lib/utils/mysql.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Ntfy } from "./ntfy.js";
|
|
2
|
-
import { createMysqlCli } from "@datatruck/cli/utils/mysql.js";
|
|
2
|
+
import { createMysqlCli, MysqlCliOptions } from "@datatruck/cli/utils/mysql.js";
|
|
3
3
|
export type MySQLDumpItem = {
|
|
4
4
|
name: string;
|
|
5
5
|
database: string;
|
|
@@ -13,13 +13,7 @@ export type MySQLDumpOptions = {
|
|
|
13
13
|
verbose?: boolean;
|
|
14
14
|
minFreeSpace?: string;
|
|
15
15
|
concurrency?: number;
|
|
16
|
-
connection:
|
|
17
|
-
};
|
|
18
|
-
export type MySQLDumpConnection = {
|
|
19
|
-
hostname: string;
|
|
20
|
-
password: string;
|
|
21
|
-
username: string;
|
|
22
|
-
port?: number;
|
|
16
|
+
connection: Omit<MysqlCliOptions, "verbose">;
|
|
23
17
|
};
|
|
24
18
|
export type MySQLDumpStats = {
|
|
25
19
|
bytes: number;
|
package/lib/utils/mysql.js
CHANGED
|
@@ -158,15 +158,12 @@ export class MySQLDump {
|
|
|
158
158
|
error,
|
|
159
159
|
stats,
|
|
160
160
|
});
|
|
161
|
-
await this.ntfy.send(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
},
|
|
168
|
-
priority: error ? "high" : "default",
|
|
169
|
-
tags: [error ? "red_circle" : "green_circle"],
|
|
170
|
-
});
|
|
161
|
+
await this.ntfy.send("SQL dump", {
|
|
162
|
+
Package: item.name,
|
|
163
|
+
Size: formatBytes(stats.bytes),
|
|
164
|
+
Files: stats.files,
|
|
165
|
+
Duration: duration(Date.now() - now),
|
|
166
|
+
Error: error?.message,
|
|
167
|
+
}, error);
|
|
171
168
|
}
|
|
172
169
|
}
|
package/lib/utils/ntfy.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { Agent } from "undici";
|
|
2
|
+
interface MessageObject {
|
|
3
|
+
[key: string]: string | number | undefined | ({
|
|
4
|
+
key: string;
|
|
5
|
+
value: string | number;
|
|
6
|
+
level?: number;
|
|
7
|
+
} | false)[];
|
|
8
|
+
}
|
|
2
9
|
export declare class Ntfy {
|
|
3
10
|
readonly options: {
|
|
4
11
|
token?: string;
|
|
@@ -11,8 +18,9 @@ export declare class Ntfy {
|
|
|
11
18
|
titlePrefix?: string;
|
|
12
19
|
delay?: number;
|
|
13
20
|
});
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
private formatTitle;
|
|
22
|
+
private formatMessage;
|
|
23
|
+
private formatMessageObject;
|
|
24
|
+
send(inTitle: string, message: MessageObject, error?: Error | boolean): Promise<void>;
|
|
18
25
|
}
|
|
26
|
+
export {};
|
package/lib/utils/ntfy.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { unstyle } from "./string.js";
|
|
1
2
|
import { setTimeout } from "timers/promises";
|
|
2
3
|
import { Agent, fetch } from "undici";
|
|
4
|
+
import { styleText } from "util";
|
|
3
5
|
export class Ntfy {
|
|
4
6
|
options;
|
|
5
7
|
agent;
|
|
@@ -11,32 +13,50 @@ export class Ntfy {
|
|
|
11
13
|
connections: 1,
|
|
12
14
|
});
|
|
13
15
|
}
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
formatTitle(title) {
|
|
17
|
+
const text = styleText("cyan", title);
|
|
18
|
+
const prefix = this.options.titlePrefix;
|
|
19
|
+
return prefix ? `[${styleText("magenta", prefix)}] ${text}` : text;
|
|
20
|
+
}
|
|
21
|
+
formatMessage(name, value, level = 0) {
|
|
22
|
+
const pad = " ".repeat(level);
|
|
23
|
+
return `${pad}- ${name}: ${styleText("gray", value.toString())}`;
|
|
24
|
+
}
|
|
25
|
+
formatMessageObject(object, level = 0) {
|
|
26
|
+
return Object.entries(object)
|
|
27
|
+
.filter(([, value]) => value !== undefined)
|
|
28
|
+
.map(([name, value]) => {
|
|
29
|
+
if (Array.isArray(value)) {
|
|
30
|
+
return value
|
|
31
|
+
.filter((item) => item !== false)
|
|
32
|
+
.map((item) => this.formatMessage(item.key, item.value.toString(), item.level))
|
|
33
|
+
.join("\n");
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
return this.formatMessage(name, value.toString(), level);
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
.join("\n");
|
|
40
|
+
}
|
|
41
|
+
async send(inTitle, message, error) {
|
|
42
|
+
const title = this.formatTitle(inTitle);
|
|
43
|
+
const body = this.formatMessageObject(message);
|
|
28
44
|
const lines = [title, body].filter((v) => v.length);
|
|
29
45
|
if (lines.length)
|
|
30
46
|
console.info([...lines, ""].join("\n"));
|
|
47
|
+
const options = {
|
|
48
|
+
priority: error ? "high" : "default",
|
|
49
|
+
tags: [error ? "red_circle" : "green_circle"],
|
|
50
|
+
};
|
|
31
51
|
try {
|
|
32
52
|
if (this.options.token)
|
|
33
53
|
await fetch(`https://ntfy.sh/${this.options.token}`, {
|
|
34
54
|
dispatcher: this.agent,
|
|
35
55
|
method: "POST",
|
|
36
|
-
body,
|
|
56
|
+
body: unstyle(body),
|
|
37
57
|
headers: {
|
|
38
58
|
Markdown: "yes",
|
|
39
|
-
Title: title,
|
|
59
|
+
Title: unstyle(title),
|
|
40
60
|
Priority: options.priority ?? "default",
|
|
41
61
|
...(options.tags && {
|
|
42
62
|
Tags: options.tags?.join(","),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function unstyle(str: string): string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datatruck/restic",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Tool for creating and managing backups",
|
|
5
5
|
"homepage": "https://github.com/swordev/datatruck#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"ajv": "^8.17.1",
|
|
35
35
|
"commander": "^14.0.2",
|
|
36
36
|
"undici": "^7.18.2",
|
|
37
|
-
"@datatruck/cli": "0.41.
|
|
37
|
+
"@datatruck/cli": "0.41.6"
|
|
38
38
|
},
|
|
39
39
|
"engine": {
|
|
40
40
|
"node": ">=20.0.0"
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { Ntfy } from "./ntfy.js";
|
|
2
|
-
import { Restic } from "@datatruck/cli/utils/restic.js";
|
|
3
|
-
export type CommonResticBackupTags = {
|
|
4
|
-
id: string;
|
|
5
|
-
shortId: string;
|
|
6
|
-
hostname: string;
|
|
7
|
-
date: string;
|
|
8
|
-
vendor: string;
|
|
9
|
-
version: string;
|
|
10
|
-
};
|
|
11
|
-
export type ResticBackupTags = CommonResticBackupTags & {
|
|
12
|
-
package: string;
|
|
13
|
-
tags?: string[];
|
|
14
|
-
};
|
|
15
|
-
export type ResticBackupPackage = {
|
|
16
|
-
name: string;
|
|
17
|
-
tags?: string[];
|
|
18
|
-
path: string;
|
|
19
|
-
exclude?: string[];
|
|
20
|
-
};
|
|
21
|
-
export type ResticBackupStats = {
|
|
22
|
-
files: number;
|
|
23
|
-
bytes: number;
|
|
24
|
-
diffBytes: number | undefined;
|
|
25
|
-
};
|
|
26
|
-
export type ResticOptions = {
|
|
27
|
-
name: string;
|
|
28
|
-
tags: CommonResticBackupTags;
|
|
29
|
-
minFreeSpace?: string;
|
|
30
|
-
connection: {
|
|
31
|
-
password: string;
|
|
32
|
-
uri: string;
|
|
33
|
-
};
|
|
34
|
-
};
|
|
35
|
-
export declare class ResticBackup {
|
|
36
|
-
readonly options: ResticOptions;
|
|
37
|
-
protected ntfy: Ntfy;
|
|
38
|
-
protected log: boolean | undefined;
|
|
39
|
-
readonly processes: {
|
|
40
|
-
name: string;
|
|
41
|
-
error?: Error;
|
|
42
|
-
stats: ResticBackupStats;
|
|
43
|
-
}[];
|
|
44
|
-
protected startTime: number;
|
|
45
|
-
readonly restic: Restic;
|
|
46
|
-
constructor(options: ResticOptions, ntfy: Ntfy, log: boolean | undefined);
|
|
47
|
-
run(input: ResticBackupPackage | ResticBackupPackage[]): Promise<void>;
|
|
48
|
-
protected runSingle(item: ResticBackupPackage): Promise<void>;
|
|
49
|
-
}
|