@ps-aux/nodebup 0.11.1 → 0.12.1
Sign up to get free protection for your applications and to get access to all the features.
- package/lib/bup/aggregate/AggregateBackupController.js +9 -9
- package/lib/bup/dir/DirBackupController.js +4 -4
- package/lib/bup/pg/PgBackupController.js +88 -26
- package/lib/cli/app.js +29 -5
- package/lib/config/Config.js +6 -5
- package/lib/config/expandAndCreatePath.js +2 -2
- package/lib/ctx/Context.js +10 -10
- package/lib/fs/Fs.js +25 -5
- package/lib/fs/fssync/FsSyncer.js +1 -1
- package/lib/storage/BackupStorage.js +3 -3
- package/lib/storage/rclone/RcloneBackupBackend.js +3 -3
- package/lib/storage/rclone/RcloneClient.spec.js +6 -5
- package/lib/storage/restic/ResticBackupBackend.js +4 -4
- package/lib/storage/restic/ResticClient.js +14 -5
- package/lib/storage/restic/ResticClient.spec.js +4 -3
- package/lib/storage/restic/ResticController.js +12 -2
- package/lib/tools/shell/Shell.js +3 -3
- package/lib/tools/shell/Shell.test.js +7 -2
- package/lib/tools/ssh/SshKeyManager.js +4 -4
- package/package.json +5 -3
@@ -5,14 +5,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
});
|
6
6
|
exports.AggregateBackupController = void 0;
|
7
7
|
|
8
|
-
var _FsSyncer = require("../../fs/fssync/FsSyncer");
|
9
|
-
|
10
|
-
var _Gpg = require("../../tools/gpg/Gpg");
|
11
|
-
|
12
|
-
var _Fs = require("../../fs/Fs");
|
13
|
-
|
14
|
-
var _SshKeyManager = require("../../tools/ssh/SshKeyManager");
|
15
|
-
|
16
8
|
var _inversify = require("inversify");
|
17
9
|
|
18
10
|
var _ContextSymbols = require("../../ctx/ContextSymbols");
|
@@ -25,6 +17,14 @@ var _BackupStorageProvider = require("../../storage/BackupStorageProvider");
|
|
25
17
|
|
26
18
|
var _logging = require("../../log/logging");
|
27
19
|
|
20
|
+
var _SshKeyManager = require("../../tools/ssh/SshKeyManager");
|
21
|
+
|
22
|
+
var _Gpg = require("../../tools/gpg/Gpg");
|
23
|
+
|
24
|
+
var _Fs = require("../../fs/Fs");
|
25
|
+
|
26
|
+
var _FsSyncer = require("../../fs/fssync/FsSyncer");
|
27
|
+
|
28
28
|
var _dec, _dec2, _dec3, _dec4, _class;
|
29
29
|
|
30
30
|
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
@@ -53,7 +53,7 @@ let AggregateBackupController = (_dec = (0, _inversify.injectable)(), _dec2 = fu
|
|
53
53
|
} = aggr.sources;
|
54
54
|
|
55
55
|
try {
|
56
|
-
this.fs.ensureDir(aggrDir);
|
56
|
+
await this.fs.ensureDir(aggrDir);
|
57
57
|
file.forEach(f => this.runFileTask(f, aggrDir));
|
58
58
|
sshKey.forEach(k => this.runSshKeyTask(k, aggrDir));
|
59
59
|
gpgKey.forEach(k => this.runGpgKeyTask(k, aggrDir));
|
@@ -23,22 +23,22 @@ let DirBackupController = (_dec = (0, _inversify.injectable)(), _dec2 = Reflect.
|
|
23
23
|
|
24
24
|
_defineProperty(this, "log", (0, _logging.classObjLog)(this));
|
25
25
|
|
26
|
-
_defineProperty(this, "backup", inp => {
|
26
|
+
_defineProperty(this, "backup", async inp => {
|
27
27
|
const storage = this.storageProvider.provide();
|
28
28
|
|
29
29
|
const path = _Path.AbsPath.from(inp.path);
|
30
30
|
|
31
31
|
this.log.info(`Backing up from ${path} to '${storage}'`);
|
32
|
-
storage.store(path);
|
32
|
+
await storage.store(path);
|
33
33
|
});
|
34
34
|
|
35
|
-
_defineProperty(this, "restore", inp => {
|
35
|
+
_defineProperty(this, "restore", async inp => {
|
36
36
|
const storage = this.storageProvider.provide();
|
37
37
|
|
38
38
|
const path = _Path.AbsPath.from(inp.path);
|
39
39
|
|
40
40
|
this.log.info(`Restoring from '${storage}' to ${path}`);
|
41
|
-
storage.restore(path);
|
41
|
+
await storage.restore(path);
|
42
42
|
});
|
43
43
|
}
|
44
44
|
|
@@ -5,8 +5,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
});
|
6
6
|
exports.PgBackupController = void 0;
|
7
7
|
|
8
|
-
var _Fs = require("../../fs/Fs");
|
9
|
-
|
10
8
|
var _inversify = require("inversify");
|
11
9
|
|
12
10
|
var _Shell = require("../../tools/shell/Shell");
|
@@ -19,6 +17,8 @@ var _Zipper = require("../../tools/compression/Zipper");
|
|
19
17
|
|
20
18
|
var _logging = require("../../log/logging");
|
21
19
|
|
20
|
+
var _Fs = require("../../fs/Fs");
|
21
|
+
|
22
22
|
var _dec, _dec2, _dec3, _class, _class2, _temp;
|
23
23
|
|
24
24
|
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
@@ -59,61 +59,123 @@ let PgBackupController = (_dec = (0, _inversify.injectable)(), _dec2 = Reflect.m
|
|
59
59
|
|
60
60
|
_defineProperty(this, "getVersion", version => version || PgBackupController.defaultPgVersion);
|
61
61
|
|
62
|
-
_defineProperty(this, "outFileName", 'backup.sql');
|
63
|
-
|
64
62
|
_defineProperty(this, "backup", async ({
|
65
63
|
pgUrl,
|
66
|
-
|
64
|
+
pgBinDir,
|
65
|
+
backupName
|
67
66
|
}) => {
|
68
67
|
const storage = this.storageBackendProvider.provide();
|
69
|
-
const
|
68
|
+
const connParams = parseConnectionUrl(pgUrl); // TODO validate url
|
70
69
|
|
71
|
-
this.log.info(`Backing up Postgres database
|
72
|
-
const connParams = parseConnectionUrl(pgUrl);
|
73
|
-
const pass = connParams.password; // if (3 > 2)
|
74
|
-
// return
|
70
|
+
this.log.info(`Backing up Postgres database @${connParams.host}`);
|
75
71
|
|
76
|
-
|
77
|
-
const outputDir = _Path.AbsPath.from(dir);
|
72
|
+
const dir = _Path.AbsPath.from(`/var/backup/bup/${backupName}/postgres`); // const dir = AbsPath.from('/var/foo')
|
78
73
|
|
79
|
-
|
80
|
-
|
74
|
+
|
75
|
+
await this.fs.inNewDir(dir, async () => {
|
76
|
+
// const outputDir = AbsPath.from(dir)
|
77
|
+
this.log.info('Processing Postgres backup'); // Don't forget that this itself might be run in docker
|
81
78
|
// therefore volume mounts are not usable (will apply to the location at host)
|
82
79
|
|
83
|
-
const
|
84
|
-
this.log.info(`Dumping
|
85
|
-
|
80
|
+
const file = PgBackupController.dumpFileName;
|
81
|
+
this.log.info(`Dumping data into ${file}`);
|
82
|
+
let cmd = `pg_dumpall -d ${pgUrl} `;
|
83
|
+
|
84
|
+
if (pgBinDir) {
|
85
|
+
cmd = this.addPgBinDir(cmd, pgBinDir);
|
86
|
+
}
|
87
|
+
|
88
|
+
await this.sh.asyncExec(`${cmd} > ${file}`, {
|
89
|
+
env: {
|
90
|
+
PGPASSWORD: connParams.password
|
91
|
+
},
|
92
|
+
cwd: dir.str()
|
93
|
+
}, {
|
94
|
+
stdout: o => this.log.trace(o),
|
95
|
+
stderr: o => this.log.error(o)
|
96
|
+
});
|
97
|
+
this.log.info(`Compressing ${file}`);
|
98
|
+
await this.sh.asyncExec(`gzip -v ${file}`, {
|
99
|
+
cwd: dir.str()
|
100
|
+
}, {
|
101
|
+
stdout: o => this.log.trace('gzip: ' + o),
|
102
|
+
stderr: o => this.log.info('gzip: ' + o)
|
103
|
+
});
|
86
104
|
this.log.info('Uploading');
|
87
|
-
storage.store(
|
105
|
+
await storage.store(dir);
|
88
106
|
});
|
89
107
|
});
|
90
108
|
|
109
|
+
_defineProperty(this, "dumpFromDockerCmd", (pass, pgUrl, version) => {
|
110
|
+
const bashCmds = [`echo "*:*:*:*:${pass}" > ~/.pgpass`, `chmod 400 ~/.pgpass`, `pg_dumpall -d ${pgUrl}`]; // Restore docker command
|
111
|
+
// this.sh.exec(
|
112
|
+
// `gzip --decompress --stdout ${zipFile.str()} | docker run --network host -i ` +
|
113
|
+
// `-e PGPASSWORD=${con.password} ` +
|
114
|
+
// `postgres:${version} ` +
|
115
|
+
// `psql ${connectionArgs} -v ON_ERROR_STOP=0`
|
116
|
+
// )
|
117
|
+
// TODO consider pulling the docker images first so the Docke daremon info messages about container pulling are not logged as errors
|
118
|
+
|
119
|
+
return `docker run --rm --network host postgres:${version} ` + `bash -c '${bashCmds.join(' && ')}'`;
|
120
|
+
});
|
121
|
+
|
91
122
|
_defineProperty(this, "restore", async ({
|
92
123
|
pgUrl,
|
93
|
-
|
124
|
+
pgBinDir
|
94
125
|
}) => {
|
95
|
-
const version = this.getVersion(pgVersion)
|
126
|
+
// const version = this.getVersion(pgVersion)
|
96
127
|
const storage = this.storageBackendProvider.provide(); // To validate
|
97
128
|
|
98
129
|
const con = parseConnectionUrl(pgUrl);
|
99
|
-
this.log.info(`Restoring Postgres database
|
130
|
+
this.log.info(`Restoring Postgres database`);
|
100
131
|
await this.fs.inTmpDir('pg-restore', async dirStr => {
|
101
132
|
const dir = _Path.AbsPath.from(dirStr);
|
102
133
|
|
103
|
-
storage.restore(dir); // TODO check if the dir contains the with the expected name
|
134
|
+
await storage.restore(dir); // TODO check if the dir contains the with the expected name
|
104
135
|
// TODO add e2e test for docker
|
105
136
|
|
106
|
-
|
107
|
-
|
137
|
+
this.log.info('Backup dir restored');
|
138
|
+
const file = PgBackupController.dumpFileName;
|
139
|
+
const zipFile = file + '.gz';
|
140
|
+
const zipPath = this.fs.listFiles(dir).find(n => n.basename() === zipFile);
|
141
|
+
if (!zipPath) throw new Error(`Expected to find file ${zipFile} in the restore data`);
|
142
|
+
this.log.info(`Decompressing ${zipFile}`);
|
143
|
+
await this.sh.asyncExec(`gzip --decompress -v ${zipFile}`, {
|
144
|
+
cwd: dir.str()
|
145
|
+
}, {
|
146
|
+
stdout: o => this.log.trace('gzip: ' + o),
|
147
|
+
stderr: o => this.log.info('gzip: ' + o)
|
148
|
+
}); // Database is set to 'postgres' so that also users which don't have db created can use db-less URL
|
108
149
|
// Restoring user needs to have the admin access anyway
|
109
150
|
|
110
151
|
const connectionArgs = `-h ${con.host} -p ${con.port} -U ${con.username} -d postgres`; // Don't forget that this itself might be run in docker
|
111
152
|
// therefore volume mounts are not usable (will apply to the location at host)
|
112
153
|
|
113
|
-
|
154
|
+
let cmd = `psql ${connectionArgs} -v ON_ERROR_STOP=0 < ${file}`;
|
155
|
+
|
156
|
+
if (pgBinDir) {
|
157
|
+
cmd = this.addPgBinDir(cmd, pgBinDir);
|
158
|
+
}
|
159
|
+
|
160
|
+
await this.sh.asyncExec(cmd, {
|
161
|
+
cwd: dir.str(),
|
162
|
+
env: {
|
163
|
+
PGPASSWORD: con.password
|
164
|
+
}
|
165
|
+
}, {
|
166
|
+
stdout: o => this.log.trace(o),
|
167
|
+
stderr: o => this.log.error(o)
|
168
|
+
});
|
169
|
+
this.log.info(`Data successfully inserted into database @${con.host}`);
|
114
170
|
});
|
115
171
|
});
|
172
|
+
|
173
|
+
_defineProperty(this, "addPgBinDir", (cmd, pgBinDir) => {
|
174
|
+
this.log.info(`Using PG bin dir ${pgBinDir}`);
|
175
|
+
this.fs.ensureIsDir(_Path.AbsPath.from(pgBinDir));
|
176
|
+
return pgBinDir + '/' + cmd;
|
177
|
+
});
|
116
178
|
}
|
117
179
|
|
118
|
-
}, _defineProperty(_class2, "defaultPgVersion", '14.2'), _temp)) || _class) || _class) || _class);
|
180
|
+
}, _defineProperty(_class2, "defaultPgVersion", '14.2'), _defineProperty(_class2, "dumpFileName", 'pg-dump.sql'), _temp)) || _class) || _class) || _class);
|
119
181
|
exports.PgBackupController = PgBackupController;
|
package/lib/cli/app.js
CHANGED
@@ -45,6 +45,7 @@ const singleStorageOptions = [storageNameOpt, {
|
|
45
45
|
}];
|
46
46
|
const backupTagOption = {
|
47
47
|
name: 'backup-tag',
|
48
|
+
fromConfig: 'backup.tag',
|
48
49
|
convertCase: true
|
49
50
|
};
|
50
51
|
const backupOptions = [backupTagOption];
|
@@ -117,6 +118,17 @@ const restic = (0, _nclif.cmdGroup)({
|
|
117
118
|
snapshots: (0, _nclif.cmd)({
|
118
119
|
run: (_, c, p) => c.get(_ResticController.ResticController).listSnapshots(p.stdout)
|
119
120
|
}),
|
121
|
+
env: (0, _nclif.cmd)({
|
122
|
+
run: ({
|
123
|
+
export: doExport
|
124
|
+
}, c) => c.get(_ResticController.ResticController).printEnv(doExport),
|
125
|
+
options: [{
|
126
|
+
name: 'export',
|
127
|
+
type: 'boolean',
|
128
|
+
description: 'Add "export" statement to each env variable inoutput',
|
129
|
+
convertCase: true
|
130
|
+
}]
|
131
|
+
}),
|
120
132
|
cmd: (0, _nclif.cmd)({
|
121
133
|
positionals: [{
|
122
134
|
name: 'cmd',
|
@@ -132,18 +144,30 @@ const pg = (0, _nclif.cmdGroup)({
|
|
132
144
|
description: 'Postgres backup commands',
|
133
145
|
options: [...singleStorageOptions, {
|
134
146
|
name: 'pg-url',
|
147
|
+
description: 'Postgres URL',
|
135
148
|
convertCase: true,
|
136
149
|
fromConfig: 'pg.url',
|
137
150
|
required: true
|
138
151
|
}, {
|
139
|
-
name: 'pg-
|
152
|
+
name: 'pg-bin-dir',
|
153
|
+
description: 'A directory with Postgres binaries (if the ones on the path should not be used)',
|
140
154
|
convertCase: true,
|
141
|
-
fromConfig: 'pg.
|
142
|
-
|
143
|
-
|
155
|
+
fromConfig: 'pg.bin-dir'
|
156
|
+
} // {
|
157
|
+
// name: 'pg-version',
|
158
|
+
// convertCase: true,
|
159
|
+
// fromConfig: 'pg.version',
|
160
|
+
// description: `Postgres version - default is ${PgBackupController.defaultPgVersion}`
|
161
|
+
// }
|
162
|
+
],
|
144
163
|
commands: {
|
145
164
|
backup: (0, _nclif.cmd)({
|
146
|
-
options: backupOptions,
|
165
|
+
options: [...backupOptions, {
|
166
|
+
name: 'backup-name',
|
167
|
+
convertCase: true,
|
168
|
+
fromConfig: 'backup.name',
|
169
|
+
required: true
|
170
|
+
}],
|
147
171
|
run: (inp, c) => c.get(_PgBackupController.PgBackupController).backup(inp)
|
148
172
|
}),
|
149
173
|
restore: (0, _nclif.cmd)({
|
package/lib/config/Config.js
CHANGED
@@ -5,16 +5,16 @@ Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
});
|
6
6
|
exports.readConfig = void 0;
|
7
7
|
|
8
|
+
var _expandAndCreatePath = require("./expandAndCreatePath");
|
9
|
+
|
10
|
+
var _Fs = require("../fs/Fs");
|
11
|
+
|
8
12
|
var _Path = require("../fs/path/Path");
|
9
13
|
|
10
14
|
var _readYaml = require("../fs/readYaml");
|
11
15
|
|
12
16
|
var _validateConfigAgainstSchema = require("./validateConfigAgainstSchema");
|
13
17
|
|
14
|
-
var _expandAndCreatePath = require("./expandAndCreatePath");
|
15
|
-
|
16
|
-
var _Fs = require("../fs/Fs");
|
17
|
-
|
18
18
|
const expandPaths = (cfg, exp) => {
|
19
19
|
cfg.aggregates.forEach(a => {
|
20
20
|
a.sources.file.forEach(f => {
|
@@ -37,7 +37,8 @@ const readFromFile = cwd => {
|
|
37
37
|
const cfg = fs.exists(cfgPath) ? (0, _readYaml.readYaml)(cfgPath.str()) : {
|
38
38
|
aggregates: [],
|
39
39
|
storage: []
|
40
|
-
};
|
40
|
+
}; // eslint-disable-next-line no-undef
|
41
|
+
|
41
42
|
(0, _validateConfigAgainstSchema.validateConfigAgainstSchema)(cfg);
|
42
43
|
return cfg;
|
43
44
|
};
|
@@ -5,12 +5,12 @@ Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
});
|
6
6
|
exports.expandStrPath = exports.expandAndCreatePath = void 0;
|
7
7
|
|
8
|
-
var _Path = require("../fs/path/Path");
|
9
|
-
|
10
8
|
var _os = _interopRequireDefault(require("os"));
|
11
9
|
|
12
10
|
var _path = _interopRequireDefault(require("path"));
|
13
11
|
|
12
|
+
var _Path = require("../fs/path/Path");
|
13
|
+
|
14
14
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
15
15
|
|
16
16
|
const expandStrPath = (path, pwd) => {
|
package/lib/ctx/Context.js
CHANGED
@@ -5,16 +5,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
});
|
6
6
|
exports.createContext = void 0;
|
7
7
|
|
8
|
-
var _Shell = require("../tools/shell/Shell");
|
9
|
-
|
10
|
-
var _FsSyncer = require("../fs/fssync/FsSyncer");
|
11
|
-
|
12
|
-
var _Gpg = require("../tools/gpg/Gpg");
|
13
|
-
|
14
|
-
var _Fs = require("../fs/Fs");
|
15
|
-
|
16
|
-
var _SshKeyManager = require("../tools/ssh/SshKeyManager");
|
17
|
-
|
18
8
|
var _inversify = require("inversify");
|
19
9
|
|
20
10
|
var _ContextSymbols = require("./ContextSymbols");
|
@@ -39,6 +29,16 @@ var _PgBackupController = require("../bup/pg/PgBackupController");
|
|
39
29
|
|
40
30
|
var _Zipper = require("../tools/compression/Zipper");
|
41
31
|
|
32
|
+
var _FsSyncer = require("../fs/fssync/FsSyncer");
|
33
|
+
|
34
|
+
var _Gpg = require("../tools/gpg/Gpg");
|
35
|
+
|
36
|
+
var _Shell = require("../tools/shell/Shell");
|
37
|
+
|
38
|
+
var _Fs = require("../fs/Fs");
|
39
|
+
|
40
|
+
var _SshKeyManager = require("../tools/ssh/SshKeyManager");
|
41
|
+
|
42
42
|
const createContext = (cfg, inp) => {
|
43
43
|
const c = new _inversify.Container();
|
44
44
|
c.bind(_ContextSymbols.AppConfig_).toConstantValue(cfg);
|
package/lib/fs/Fs.js
CHANGED
@@ -7,9 +7,9 @@ exports.Fs = void 0;
|
|
7
7
|
|
8
8
|
var _fs = _interopRequireDefault(require("fs"));
|
9
9
|
|
10
|
-
var
|
10
|
+
var _promises = _interopRequireDefault(require("fs/promises"));
|
11
11
|
|
12
|
-
var
|
12
|
+
var _path = _interopRequireDefault(require("path"));
|
13
13
|
|
14
14
|
var _inversify = require("inversify");
|
15
15
|
|
@@ -19,6 +19,8 @@ var _os = _interopRequireDefault(require("os"));
|
|
19
19
|
|
20
20
|
var _logging = require("../log/logging");
|
21
21
|
|
22
|
+
var _Path = require("./path/Path");
|
23
|
+
|
22
24
|
var _dec, _dec2, _dec3, _class;
|
23
25
|
|
24
26
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
@@ -73,10 +75,23 @@ let Fs = (_dec = (0, _inversify.injectable)(), _dec2 = Reflect.metadata("design:
|
|
73
75
|
try {
|
74
76
|
await withDir(dir);
|
75
77
|
} finally {
|
78
|
+
// TODO handle if not exists?
|
76
79
|
this.rmDir(_Path.AbsPath.from(dir));
|
77
80
|
}
|
78
81
|
});
|
79
82
|
|
83
|
+
_defineProperty(this, "inNewDir", async (path, withDir) => {
|
84
|
+
let created = false;
|
85
|
+
|
86
|
+
try {
|
87
|
+
await this.ensureDir(path, true);
|
88
|
+
created = true;
|
89
|
+
await withDir();
|
90
|
+
} finally {
|
91
|
+
if (created) this.rmDir(path);
|
92
|
+
}
|
93
|
+
});
|
94
|
+
|
80
95
|
_defineProperty(this, "mkTmpDir", name => {
|
81
96
|
const dir = _fs.default.mkdtempSync(_path.default.resolve(_os.default.tmpdir(), name));
|
82
97
|
|
@@ -97,7 +112,7 @@ let Fs = (_dec = (0, _inversify.injectable)(), _dec2 = Reflect.metadata("design:
|
|
97
112
|
return JSON.parse(file);
|
98
113
|
});
|
99
114
|
|
100
|
-
_defineProperty(this, "ensureDir", path => {
|
115
|
+
_defineProperty(this, "ensureDir", async (path, mustBeEmpty = false) => {
|
101
116
|
if (!this.exists(path)) {
|
102
117
|
this.log.trace('Dir %s does not exist. Wil be created', path.str());
|
103
118
|
|
@@ -106,13 +121,18 @@ let Fs = (_dec = (0, _inversify.injectable)(), _dec2 = Reflect.metadata("design:
|
|
106
121
|
});
|
107
122
|
} else {
|
108
123
|
if (this.isFile(path)) throw new Error(`${path.str()} exists and is not a directory`);
|
124
|
+
|
125
|
+
if (mustBeEmpty) {
|
126
|
+
const content = await _promises.default.readdir(path.str());
|
127
|
+
if (content.length) throw new Error(`${path.str()} exists but is not empty`);
|
128
|
+
}
|
109
129
|
}
|
110
130
|
});
|
111
131
|
|
112
|
-
_defineProperty(this, "ensureParentDir", path => {
|
132
|
+
_defineProperty(this, "ensureParentDir", async path => {
|
113
133
|
const parent = path.parent();
|
114
134
|
if (this.exists(parent)) return;
|
115
|
-
this.ensureDir(parent);
|
135
|
+
await this.ensureDir(parent);
|
116
136
|
});
|
117
137
|
|
118
138
|
_defineProperty(this, "exists", path => _fs.default.existsSync(path.str()));
|
@@ -34,7 +34,7 @@ let FsSyncer = (_dec = (0, _inversify.injectable)(), _dec2 = Reflect.metadata("d
|
|
34
34
|
if (this.fs.isFile(from)) this.syncFile(from, to);else this.syncDirs(from, to);
|
35
35
|
});
|
36
36
|
|
37
|
-
_defineProperty(this, "syncDirs", (from, to) => {
|
37
|
+
_defineProperty(this, "syncDirs", async (from, to) => {
|
38
38
|
this.fs.ensureIsDir(from);
|
39
39
|
this.log.info('Syncing dir', from.str(), '==>', to.str());
|
40
40
|
const fromPath = toUnixDirString(from);
|
@@ -12,15 +12,15 @@ class BackupStorage {
|
|
12
12
|
this.backend = backend;
|
13
13
|
this.props = props;
|
14
14
|
|
15
|
-
_defineProperty(this, "store", from => {
|
15
|
+
_defineProperty(this, "store", async from => {
|
16
16
|
const {
|
17
17
|
tag
|
18
18
|
} = this.props;
|
19
19
|
const tags = tag ? [tag] : undefined;
|
20
|
-
this.backend.backup(from, tags);
|
20
|
+
await this.backend.backup(from, tags);
|
21
21
|
});
|
22
22
|
|
23
|
-
_defineProperty(this, "restore", to => {
|
23
|
+
_defineProperty(this, "restore", async to => {
|
24
24
|
const {
|
25
25
|
snapshotId
|
26
26
|
} = this.props;
|
@@ -17,7 +17,7 @@ class RcloneBackupBackend {
|
|
17
17
|
|
18
18
|
_defineProperty(this, "log", (0, _logging.classObjLog)(this));
|
19
19
|
|
20
|
-
_defineProperty(this, "backup", (from, tags) => {
|
20
|
+
_defineProperty(this, "backup", async (from, tags) => {
|
21
21
|
if (tags) throw new _nclif.InvalidInputError(`Rclone does not support tags`);
|
22
22
|
this.log.info('Performing backup', {
|
23
23
|
from
|
@@ -25,14 +25,14 @@ class RcloneBackupBackend {
|
|
25
25
|
this.rclone.backup(from);
|
26
26
|
});
|
27
27
|
|
28
|
-
_defineProperty(this, "restoreLatest", to => {
|
28
|
+
_defineProperty(this, "restoreLatest", async to => {
|
29
29
|
this.log.info('Restoring', {
|
30
30
|
to
|
31
31
|
});
|
32
32
|
this.rclone.restore(to);
|
33
33
|
});
|
34
34
|
|
35
|
-
_defineProperty(this, "restoreSnapshot", (to, snapshotId) => {
|
35
|
+
_defineProperty(this, "restoreSnapshot", async (to, snapshotId) => {
|
36
36
|
throw new _nclif.InvalidInputError(`Rclone does not support snaphosts`);
|
37
37
|
});
|
38
38
|
}
|
@@ -15,11 +15,12 @@ var _b2TestConfig = require("../../../test/b2TestConfig");
|
|
15
15
|
var _testHelper = require("../../../test/testHelper");
|
16
16
|
|
17
17
|
describe('RcloneClient', () => {
|
18
|
-
|
18
|
+
// TODO clean up dir locations more
|
19
|
+
const testData = (0, _test.testdataDirBuilder)('rclone');
|
19
20
|
describe('local', () => {
|
20
21
|
const backupDir = (0, _test.testDir)('backup/1');
|
21
|
-
const restoreDir =
|
22
|
-
const storeDir = (
|
22
|
+
const restoreDir = testData('local');
|
23
|
+
const storeDir = testData('store');
|
23
24
|
const cfg = {
|
24
25
|
type: 'local',
|
25
26
|
location: (0, _test.testdataDir)(storeDir),
|
@@ -68,11 +69,11 @@ describe('RcloneClient', () => {
|
|
68
69
|
password: 'foo123'
|
69
70
|
}
|
70
71
|
}, new _Shell.Shell());
|
71
|
-
const restoreDir =
|
72
|
+
const restoreDir = testData('b2');
|
72
73
|
afterAll(() => {
|
73
74
|
(0, _testHelper.cleanDir)(restoreDir);
|
74
75
|
});
|
75
|
-
it('push
|
76
|
+
it('push and restore data', () => {
|
76
77
|
sut.backup(_Path.AbsPath.from(backupDir));
|
77
78
|
sut.restore(_Path.AbsPath.from(restoreDir));
|
78
79
|
const res = (0, _dirCompare.compareSync)(backupDir, restoreDir);
|
@@ -17,18 +17,18 @@ class ResticBackupBackend {
|
|
17
17
|
|
18
18
|
_defineProperty(this, "log", (0, _logging.classObjLog)(this));
|
19
19
|
|
20
|
-
_defineProperty(this, "backup", (from, tags = []) => {
|
20
|
+
_defineProperty(this, "backup", async (from, tags = []) => {
|
21
21
|
this.log.info(`Performing backup from=${from}, tags=${tags}`);
|
22
|
-
this.restic.backup(from, _Path.RelativePath.from('.'), tags);
|
22
|
+
await this.restic.backup(from, _Path.RelativePath.from('.'), tags);
|
23
23
|
this.restic.forget();
|
24
24
|
});
|
25
25
|
|
26
|
-
_defineProperty(this, "restoreLatest", to => {
|
26
|
+
_defineProperty(this, "restoreLatest", async to => {
|
27
27
|
this.log.info(`Restoring latest snapshot to=${to}`);
|
28
28
|
this.restic.restore(to);
|
29
29
|
});
|
30
30
|
|
31
|
-
_defineProperty(this, "restoreSnapshot", (to, snapshotId) => {
|
31
|
+
_defineProperty(this, "restoreSnapshot", async (to, snapshotId) => {
|
32
32
|
this.log.info(`Restoring snapshot '${snapshotId}'`, {
|
33
33
|
to
|
34
34
|
});
|
@@ -30,10 +30,13 @@ class ResticClient {
|
|
30
30
|
|
31
31
|
_defineProperty(this, "log", (0, _logging.classObjLog)(this));
|
32
32
|
|
33
|
-
_defineProperty(this, "prepareRepo", () => {
|
33
|
+
_defineProperty(this, "prepareRepo", async () => {
|
34
34
|
this.log.info('Preparing a restic repo at %s', this.url);
|
35
|
-
this.shell.
|
35
|
+
await this.shell.asyncExec(`restic init`, {
|
36
36
|
env: this.env()
|
37
|
+
}, {
|
38
|
+
stdout: o => this.log.trace(o),
|
39
|
+
stderr: o => this.log.error(o)
|
37
40
|
});
|
38
41
|
});
|
39
42
|
|
@@ -43,15 +46,18 @@ class ResticClient {
|
|
43
46
|
});
|
44
47
|
});
|
45
48
|
|
46
|
-
_defineProperty(this, "backup", (cwd, from, tags = []) => {
|
49
|
+
_defineProperty(this, "backup", async (cwd, from, tags = []) => {
|
47
50
|
this.log.debug('Running backup for repo=%s', this.url);
|
48
51
|
let cmd = `restic backup ${from.str()}`;
|
49
52
|
tags.forEach(t => {
|
50
53
|
cmd += ` --tag=${t}`;
|
51
54
|
});
|
52
|
-
this.shell.
|
55
|
+
await this.shell.asyncExec(cmd, {
|
53
56
|
cwd: cwd.str(),
|
54
57
|
env: this.env()
|
58
|
+
}, {
|
59
|
+
stdout: o => this.log.trace(o),
|
60
|
+
stderr: o => this.log.error(o)
|
55
61
|
});
|
56
62
|
});
|
57
63
|
|
@@ -77,14 +83,17 @@ class ResticClient {
|
|
77
83
|
});
|
78
84
|
|
79
85
|
_defineProperty(this, "snapshots", () => {
|
80
|
-
this.log.debug(
|
86
|
+
this.log.debug(`Listing snapshots of repo=${this.url}`);
|
81
87
|
const out = this.shell.execAndReturnString(`restic snapshots --json`, {
|
82
88
|
env: this.env()
|
83
89
|
});
|
84
90
|
const res = JSON.parse(out);
|
85
91
|
return res.map(r => ({
|
86
92
|
id: r.id,
|
93
|
+
short_id: r.short_id,
|
87
94
|
time: new Date(r.time),
|
95
|
+
paths: r.paths,
|
96
|
+
hostname: r.hostname,
|
88
97
|
tags: r.tags
|
89
98
|
}));
|
90
99
|
});
|
@@ -30,8 +30,9 @@ describe('ResticClient', () => {
|
|
30
30
|
(0, _testHelper.cleanDir)(repoDir);
|
31
31
|
(0, _testHelper.cleanDir)(restoreDir);
|
32
32
|
});
|
33
|
-
it('create repo', () => {
|
34
|
-
sut.prepareRepo();
|
33
|
+
it('create repo', async () => {
|
34
|
+
await sut.prepareRepo();
|
35
|
+
console.log('repo created');
|
35
36
|
});
|
36
37
|
it('push data & forget', () => {
|
37
38
|
const from = _Path.AbsPath.from(backupDir);
|
@@ -76,7 +77,7 @@ describe('ResticClient', () => {
|
|
76
77
|
afterAll(() => {
|
77
78
|
(0, _testHelper.cleanDir)(restoreDir);
|
78
79
|
});
|
79
|
-
it('push
|
80
|
+
it('push and restore data', () => {
|
80
81
|
// sut.prepareRepo()
|
81
82
|
sut.backup(_Path.AbsPath.from(backupDir), _Path.RelativePath.from('.'));
|
82
83
|
sut.restore(_Path.AbsPath.from(restoreDir));
|
@@ -39,10 +39,11 @@ let ResticController = (_dec = (0, _inversify.injectable)(), _dec2 = function (t
|
|
39
39
|
return this.restFact.createClient(props);
|
40
40
|
});
|
41
41
|
|
42
|
-
_defineProperty(this, "initRepo", () => {
|
42
|
+
_defineProperty(this, "initRepo", async () => {
|
43
43
|
const props = this.storageConfigProvider.provide();
|
44
44
|
this.log.info(`Initializing Restic repo ${props.repo}`);
|
45
|
-
this.client().prepareRepo();
|
45
|
+
await this.client().prepareRepo();
|
46
|
+
this.log.info('Repo initialized');
|
46
47
|
});
|
47
48
|
|
48
49
|
_defineProperty(this, "listSnapshots", print => {
|
@@ -54,6 +55,15 @@ let ResticController = (_dec = (0, _inversify.injectable)(), _dec2 = function (t
|
|
54
55
|
const res = this.client().runCmd(cmd);
|
55
56
|
print(res.toString());
|
56
57
|
});
|
58
|
+
|
59
|
+
_defineProperty(this, "printEnv", (doExport = false) => {
|
60
|
+
const env = this.client().env();
|
61
|
+
const out = Object.entries(env).map(([k, v]) => `${k}=${v}`).map(line => {
|
62
|
+
if (!doExport) return line;
|
63
|
+
return `export ${line}`;
|
64
|
+
}).join('\n');
|
65
|
+
console.log(out);
|
66
|
+
});
|
57
67
|
}
|
58
68
|
|
59
69
|
}) || _class) || _class) || _class) || _class);
|
package/lib/tools/shell/Shell.js
CHANGED
@@ -48,7 +48,7 @@ let Shell = (_dec = (0, _inversify.injectable)(), _dec(_class = class Shell {
|
|
48
48
|
});
|
49
49
|
});
|
50
50
|
|
51
|
-
_defineProperty(this, "asyncExec", (cmd,
|
51
|
+
_defineProperty(this, "asyncExec", (cmd, ops = {}, on) => {
|
52
52
|
let onDone;
|
53
53
|
let onError;
|
54
54
|
const p = new Promise((res, rej) => {
|
@@ -60,8 +60,8 @@ let Shell = (_dec = (0, _inversify.injectable)(), _dec(_class = class Shell {
|
|
60
60
|
cwd: ops.cwd,
|
61
61
|
env: ops.env
|
62
62
|
});
|
63
|
-
r.stdout.on('data',
|
64
|
-
r.stderr.on('data',
|
63
|
+
r.stdout.on('data', o => on.stdout(o.toString()));
|
64
|
+
r.stderr.on('data', o => on.stderr(o.toString()));
|
65
65
|
r.on('exit', status => {
|
66
66
|
if (status === 0) onDone();else onError(new Error('Exited with non zero return code: ' + status));
|
67
67
|
});
|
@@ -14,8 +14,13 @@ it.skip('execAndReturnStream', async () => {
|
|
14
14
|
expect(() => sh.execAndReturnString(bigStdOutCme())).toThrow('spawnSync /bin/sh ENOBUFS'); //
|
15
15
|
|
16
16
|
let out = '';
|
17
|
-
await sh.asyncExec(bigStdOutCme(),
|
18
|
-
|
17
|
+
await sh.asyncExec(bigStdOutCme(), {}, {
|
18
|
+
stdout: data => {
|
19
|
+
out = data.toString().trim();
|
20
|
+
},
|
21
|
+
stderr: data => {
|
22
|
+
console.error(data);
|
23
|
+
}
|
19
24
|
});
|
20
25
|
expect(out).toEndWith('Done');
|
21
26
|
}, 5000_000);
|
@@ -5,14 +5,14 @@ Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
});
|
6
6
|
exports.SshKeyManager = void 0;
|
7
7
|
|
8
|
-
var _Fs = require("../../fs/Fs");
|
9
|
-
|
10
|
-
var _Path = require("../../fs/path/Path");
|
11
|
-
|
12
8
|
var _os = _interopRequireDefault(require("os"));
|
13
9
|
|
14
10
|
var _inversify = require("inversify");
|
15
11
|
|
12
|
+
var _Fs = require("../../fs/Fs");
|
13
|
+
|
14
|
+
var _Path = require("../../fs/path/Path");
|
15
|
+
|
16
16
|
var _dec, _dec2, _dec3, _class;
|
17
17
|
|
18
18
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
package/package.json
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
{
|
2
2
|
"name": "@ps-aux/nodebup",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.12.1",
|
4
4
|
"description": "",
|
5
5
|
"module": "lib/index.js",
|
6
6
|
"main": "lib/index.js",
|
7
7
|
"scripts": {
|
8
|
+
"run": "ts-node ./src/cli/bin.ts",
|
8
9
|
"build": "rm -rf build && babel --extensions '.ts,.js,.md' src -d lib src",
|
9
10
|
"publish:local": "npm run build && chmod +x lib/cli/bin.js && npm link",
|
10
11
|
"pub": "npm publish --access public",
|
11
|
-
"test": "jest",
|
12
|
-
"test:ci": "jest --group=-no-ci",
|
12
|
+
"test": "jest --runInBand",
|
13
|
+
"test:ci": "jest --group=-no-ci --runInBand",
|
13
14
|
"tc": "tsc --noEmit",
|
14
15
|
"format": "prettier \"**/*.{js,ts,tsx}\" --write",
|
15
16
|
"build-n-run": "npm run build && ",
|
@@ -68,6 +69,7 @@
|
|
68
69
|
"pg": "^8.7.3",
|
69
70
|
"prettier": "^2.5.1",
|
70
71
|
"ts-jest": "^27.1.2",
|
72
|
+
"ts-node": "^10.9.2",
|
71
73
|
"typescript": "^4.5.4"
|
72
74
|
},
|
73
75
|
"lint-staged": {
|