@heronlabs/terminal-cli 0.0.3 â 1.0.0
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/README.md +24 -6
- package/bin/src/core/interfaces/backup-service.js +1 -1
- package/bin/src/core/interfaces/rollup-service.js +1 -1
- package/bin/src/core/services/mysql-backup-service.js +2 -2
- package/bin/src/core/services/mysql-rollup-service.js +2 -2
- package/bin/src/core/services/psql-backup-service.js +2 -2
- package/bin/src/core/services/psql-rollup-service.js +2 -2
- package/bin/src/infrastructure/environment/environment-module.js +2 -1
- package/bin/src/infrastructure/environment/services/environment-service.js +26 -9
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# ðŠķ terminal-cli â Heron CLI (`hcli`)
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@heronlabs/terminal-cli)
|
|
4
|
+
[](./LICENSE)
|
|
3
5
|
[](https://github.com/heronlabs/terminal-cli/actions/workflows/ci-cli.yml)
|
|
4
6
|
[](https://www.typescriptlang.org/)
|
|
5
7
|
[](https://nestjs.com/)
|
|
@@ -14,6 +16,7 @@ AWS S3, and Luxon. Designed to run as a scheduled cron container on EasyPanel.
|
|
|
14
16
|
## Table of Contents
|
|
15
17
|
|
|
16
18
|
- [Why This Project](#why-this-project)
|
|
19
|
+
- [Install](#install)
|
|
17
20
|
- [Quick Start](#quick-start)
|
|
18
21
|
- [Commands](#commands)
|
|
19
22
|
- [Configuration](#configuration)
|
|
@@ -32,6 +35,25 @@ Scheduled, type-safe database backups with a single binary:
|
|
|
32
35
|
- **Cron-ready containers** â purpose-built Dockerfiles run a backup on start and every 12 hours.
|
|
33
36
|
- **100% tested** â v8 coverage + Stryker mutation testing, both at 100% thresholds.
|
|
34
37
|
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
`hcli` is a global CLI binary. Install it globally to put `hcli` on your `PATH`:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm i -g @heronlabs/terminal-cli
|
|
44
|
+
# or: pnpm add -g @heronlabs/terminal-cli
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Then run any command:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
hcli version
|
|
51
|
+
hcli psql-backup
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
You also need the database client tools for the engine you back up: `pg_dump` /
|
|
55
|
+
`psql` for PostgreSQL, `mysqldump` / `mysql` for MySQL.
|
|
56
|
+
|
|
35
57
|
## Quick Start
|
|
36
58
|
|
|
37
59
|
### Prerequisites
|
|
@@ -43,7 +65,7 @@ Scheduled, type-safe database backups with a single binary:
|
|
|
43
65
|
| `pg_dump` / `psql` | â | PostgreSQL backup/restore |
|
|
44
66
|
| `mysqldump` / `mysql` | â | MySQL backup/restore |
|
|
45
67
|
|
|
46
|
-
###
|
|
68
|
+
### From source (development)
|
|
47
69
|
|
|
48
70
|
```bash
|
|
49
71
|
git clone https://github.com/heronlabs/terminal-cli.git
|
|
@@ -104,11 +126,7 @@ All configuration comes from environment variables (see [.env.example](./.env.ex
|
|
|
104
126
|
|
|
105
127
|
| Variable | Required | Description |
|
|
106
128
|
|---|---|---|
|
|
107
|
-
| `
|
|
108
|
-
| `DB_PORT` | â
| Database port |
|
|
109
|
-
| `DB_NAME` | â
| Database name |
|
|
110
|
-
| `DB_USER` | â
| Database user |
|
|
111
|
-
| `DB_PASSWORD` | â
| Database password |
|
|
129
|
+
| `DB_URL` | â
| Connection URL (`postgres://`/`mysql://user:pass@host:port/dbname`) or an AWS SSM Parameter Store ARN resolved via [`@heronlabs/env-ssm`](https://www.npmjs.com/package/@heronlabs/env-ssm) |
|
|
112
130
|
| `AWS_S3_BUCKET_NAME` | for S3 | Destination bucket for remote backups |
|
|
113
131
|
| `AWS_REGION` | for S3 | AWS region |
|
|
114
132
|
| `AWS_ACCESS_KEY_ID` | for S3 | AWS credentials (or use an instance role) |
|
|
@@ -20,8 +20,8 @@ let MysqlBackupService = class MysqlBackupService extends backup_service_1.Backu
|
|
|
20
20
|
logger;
|
|
21
21
|
environmentService;
|
|
22
22
|
s3StorageService;
|
|
23
|
-
dump(filename) {
|
|
24
|
-
const { host, port, name, user, password } = this.environmentService.database;
|
|
23
|
+
async dump(filename) {
|
|
24
|
+
const { host, port, name, user, password } = await this.environmentService.database();
|
|
25
25
|
const timestamp = luxon_1.DateTime.utc().toFormat("yyyy-MM-dd'T'HH-mm-ss'Z'");
|
|
26
26
|
const backupFileName = filename ?? `${name}-${timestamp}.sql.gz`;
|
|
27
27
|
try {
|
|
@@ -19,8 +19,8 @@ let MysqlRollupService = class MysqlRollupService extends rollup_service_1.Rollu
|
|
|
19
19
|
logger;
|
|
20
20
|
environmentService;
|
|
21
21
|
s3StorageService;
|
|
22
|
-
restore(backupFileName) {
|
|
23
|
-
const { host, port, name, user, password } = this.environmentService.database;
|
|
22
|
+
async restore(backupFileName) {
|
|
23
|
+
const { host, port, name, user, password } = await this.environmentService.database();
|
|
24
24
|
try {
|
|
25
25
|
(0, child_process_1.execSync)('set -o pipefail; gunzip -c "$BACKUP_FILE" | mariadb -u "$DB_USER" -h "$DB_HOST" -P "$DB_PORT" "$DB_NAME"', {
|
|
26
26
|
env: {
|
|
@@ -20,8 +20,8 @@ let PsqlBackupService = class PsqlBackupService extends backup_service_1.BackupS
|
|
|
20
20
|
logger;
|
|
21
21
|
environmentService;
|
|
22
22
|
s3StorageService;
|
|
23
|
-
dump(filename) {
|
|
24
|
-
const { host, port, name, user, password } = this.environmentService.database;
|
|
23
|
+
async dump(filename) {
|
|
24
|
+
const { host, port, name, user, password } = await this.environmentService.database();
|
|
25
25
|
const timestamp = luxon_1.DateTime.utc().toFormat("yyyy-MM-dd'T'HH-mm-ss'Z'");
|
|
26
26
|
const backupFileName = filename ?? `${name}-${timestamp}.sql.gz`;
|
|
27
27
|
try {
|
|
@@ -19,8 +19,8 @@ let PsqlRollupService = class PsqlRollupService extends rollup_service_1.RollupS
|
|
|
19
19
|
logger;
|
|
20
20
|
environmentService;
|
|
21
21
|
s3StorageService;
|
|
22
|
-
restore(backupFileName) {
|
|
23
|
-
const { host, port, name, user, password } = this.environmentService.database;
|
|
22
|
+
async restore(backupFileName) {
|
|
23
|
+
const { host, port, name, user, password } = await this.environmentService.database();
|
|
24
24
|
try {
|
|
25
25
|
(0, child_process_1.execSync)('set -o pipefail; gunzip -c "$BACKUP_FILE" | psql', {
|
|
26
26
|
env: {
|
|
@@ -7,13 +7,14 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
};
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.EnvironmentModule = exports.environmentModule = void 0;
|
|
10
|
+
const env_ssm_1 = require("@heronlabs/env-ssm");
|
|
10
11
|
const common_1 = require("@nestjs/common");
|
|
11
12
|
const config_1 = require("@nestjs/config");
|
|
12
13
|
const environment_service_1 = require("./services/environment-service");
|
|
13
14
|
exports.environmentModule = {
|
|
14
15
|
providers: [environment_service_1.EnvironmentService],
|
|
15
16
|
exports: [environment_service_1.EnvironmentService],
|
|
16
|
-
imports: [config_1.ConfigModule],
|
|
17
|
+
imports: [config_1.ConfigModule, env_ssm_1.SsmConfigModule],
|
|
17
18
|
};
|
|
18
19
|
let EnvironmentModule = class EnvironmentModule {
|
|
19
20
|
};
|
|
@@ -10,30 +10,47 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.EnvironmentService = void 0;
|
|
13
|
+
const env_ssm_1 = require("@heronlabs/env-ssm");
|
|
13
14
|
const common_1 = require("@nestjs/common");
|
|
14
15
|
const config_1 = require("@nestjs/config");
|
|
15
16
|
let EnvironmentService = class EnvironmentService {
|
|
16
17
|
configService;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
ssmConfigService;
|
|
19
|
+
async database() {
|
|
20
|
+
const databaseUrl = await this.ssmConfigService.getOrThrow('DB_URL');
|
|
21
|
+
let url;
|
|
22
|
+
try {
|
|
23
|
+
url = new URL(databaseUrl);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new Error('Invalid DB_URL');
|
|
27
|
+
}
|
|
28
|
+
const connection = {
|
|
29
|
+
host: url.hostname,
|
|
30
|
+
port: url.port,
|
|
31
|
+
name: url.pathname.slice(1),
|
|
32
|
+
user: decodeURIComponent(url.username),
|
|
33
|
+
password: decodeURIComponent(url.password),
|
|
24
34
|
};
|
|
35
|
+
const missing = ['host', 'name', 'user'].filter(field => connection[field] === '');
|
|
36
|
+
if (missing.length > 0) {
|
|
37
|
+
throw new Error(`Invalid DB_URL: missing ${missing.join(', ')}`);
|
|
38
|
+
}
|
|
39
|
+
return connection;
|
|
25
40
|
}
|
|
26
41
|
get storage() {
|
|
27
42
|
return {
|
|
28
43
|
bucketName: this.configService.getOrThrow('AWS_S3_BUCKET_NAME'),
|
|
29
44
|
};
|
|
30
45
|
}
|
|
31
|
-
constructor(configService) {
|
|
46
|
+
constructor(configService, ssmConfigService) {
|
|
32
47
|
this.configService = configService;
|
|
48
|
+
this.ssmConfigService = ssmConfigService;
|
|
33
49
|
}
|
|
34
50
|
};
|
|
35
51
|
exports.EnvironmentService = EnvironmentService;
|
|
36
52
|
exports.EnvironmentService = EnvironmentService = __decorate([
|
|
37
53
|
(0, common_1.Injectable)(),
|
|
38
|
-
__metadata("design:paramtypes", [config_1.ConfigService
|
|
54
|
+
__metadata("design:paramtypes", [config_1.ConfigService,
|
|
55
|
+
env_ssm_1.SsmConfigService])
|
|
39
56
|
], EnvironmentService);
|
package/package.json
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@aws-sdk/client-s3": "^3.1045.0",
|
|
11
|
+
"@heronlabs/env-ssm": "^1.0.0",
|
|
11
12
|
"@nestjs/common": "^11.1.18",
|
|
12
13
|
"@nestjs/config": "^4.0.4",
|
|
13
14
|
"@nestjs/core": "^11.1.18",
|
|
@@ -71,6 +72,7 @@
|
|
|
71
72
|
"@swc/core"
|
|
72
73
|
],
|
|
73
74
|
"overrides": {
|
|
75
|
+
"qs@<6.15.2": ">=6.15.2",
|
|
74
76
|
"tmp@<0.2.6": ">=0.2.6"
|
|
75
77
|
}
|
|
76
78
|
},
|
|
@@ -92,5 +94,5 @@
|
|
|
92
94
|
"test:mutation": "stryker run stryker.conf.json",
|
|
93
95
|
"test:unit": "vitest run"
|
|
94
96
|
},
|
|
95
|
-
"version": "0.0
|
|
97
|
+
"version": "1.0.0"
|
|
96
98
|
}
|