pg-backup-sdk 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/.env.example ADDED
@@ -0,0 +1,29 @@
1
+ # PostgreSQL connection used only to list databases
2
+ PG_HOST=localhost
3
+ PG_PORT=5432
4
+ PG_USER=postgres
5
+ PG_PASSWORD=postgres
6
+ PG_ADMIN_DATABASE=postgres
7
+
8
+ # Backup output
9
+ BACKUP_DIR=./backups
10
+
11
+ # Daily at 02:00
12
+ BACKUP_CRON=0 2 * * *
13
+
14
+ # Remove .dump files older than this amount of days
15
+ BACKUP_RETENTION_DAYS=7
16
+
17
+ # pg_dump executable path
18
+ # Linux/macOS: pg_dump
19
+ # Windows example: C:\Program Files\PostgreSQL\16\bin\pg_dump.exe
20
+ PG_DUMP_BIN=pg_dump
21
+
22
+ # Optional: comma-separated database names to ignore
23
+ # Example: postgres,template0,template1,my_test_db
24
+ IGNORE_DATABASES=postgres,template0,template1
25
+
26
+ # Optional: comma-separated database names to include exclusively
27
+ # Empty means all non-template databases except ignored ones
28
+ # Example: sistema_escolar,financeiro,pmgestao
29
+ ONLY_DATABASES=
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,213 @@
1
+ # pg-backup-sdk
2
+
3
+ Módulo open source em Node.js + TypeScript para fazer backups automáticos de bancos PostgreSQL usando `pg_dump`.
4
+
5
+ Ele cria uma pasta separada para cada banco e salva os arquivos `.dump` no formato custom do PostgreSQL.
6
+
7
+ ## Recursos
8
+
9
+ * Backup automático com CRON
10
+ * Backup manual via CLI
11
+ * Uma pasta separada por banco
12
+ * Retenção automática de backups antigos
13
+ * Ignora bancos internos por padrão
14
+ * Permite filtrar bancos específicos
15
+ * Usa `pg_dump --format custom`
16
+ * Pronto para rodar com Node.js, Docker ou systemd
17
+
18
+ ## Estrutura gerada
19
+
20
+ ```txt
21
+ backups/
22
+ ├─ financeiro/
23
+ │ └─ financeiro_2026-06-25_02-00-00.dump
24
+ ├─ pmgestao/
25
+ │ └─ pmgestao_2026-06-25_02-00-00.dump
26
+ └─ sistema_escolar/
27
+ └─ sistema_escolar_2026-06-25_02-00-00.dump
28
+ ```
29
+
30
+ ## Requisitos
31
+
32
+ * Node.js 20+
33
+ * PostgreSQL client instalado na máquina
34
+ * Binário `pg_dump` disponível no PATH ou configurado no `.env`
35
+
36
+ Ubuntu/Debian:
37
+
38
+ ```bash
39
+ sudo apt install postgresql-client
40
+ ```
41
+
42
+ Windows:
43
+
44
+ ```env
45
+ PG_DUMP_BIN=C:\Program Files\PostgreSQL\16\bin\pg_dump.exe
46
+ ```
47
+
48
+ ## Instalação
49
+
50
+ ```bash
51
+ git clone https://github.com/guio11221/pg-backup-sdk.git
52
+ cd pg-backup-sdk
53
+ npm install
54
+ cp .env.example .env
55
+ ```
56
+
57
+ Configure o `.env`:
58
+
59
+ ```env
60
+ PG_HOST=localhost
61
+ PG_PORT=5432
62
+ PG_USER=postgres
63
+ PG_PASSWORD=postgres
64
+ PG_ADMIN_DATABASE=postgres
65
+ BACKUP_DIR=./backups
66
+ BACKUP_CRON=0 2 * * *
67
+ BACKUP_RETENTION_DAYS=7
68
+ PG_DUMP_BIN=pg_dump
69
+ IGNORE_DATABASES=postgres,template0,template1
70
+ ONLY_DATABASES=
71
+ ```
72
+
73
+ ## Rodar backup manual
74
+
75
+ ```bash
76
+ npm run backup
77
+ ```
78
+
79
+ Ou:
80
+
81
+ ```bash
82
+ npm run dev -- --now
83
+ ```
84
+
85
+ ## Rodar scheduler
86
+
87
+ ```bash
88
+ npm run dev
89
+ ```
90
+
91
+ Build de produção:
92
+
93
+ ```bash
94
+ npm run build
95
+ npm start
96
+ ```
97
+
98
+ ## Variáveis de ambiente
99
+
100
+ | Variável | Descrição | Padrão |
101
+ | ----------------------- | --------------------------------------- | ------------------------------ |
102
+ | `PG_HOST` | Host do PostgreSQL | obrigatório |
103
+ | `PG_PORT` | Porta do PostgreSQL | `5432` |
104
+ | `PG_USER` | Usuário PostgreSQL | obrigatório |
105
+ | `PG_PASSWORD` | Senha PostgreSQL | obrigatório |
106
+ | `PG_ADMIN_DATABASE` | Banco usado para listar databases | `postgres` |
107
+ | `BACKUP_DIR` | Pasta raiz dos backups | `./backups` |
108
+ | `BACKUP_CRON` | Expressão CRON do scheduler | `0 2 * * *` |
109
+ | `BACKUP_RETENTION_DAYS` | Dias de retenção | `7` |
110
+ | `PG_DUMP_BIN` | Caminho do binário pg_dump | `pg_dump` |
111
+ | `IGNORE_DATABASES` | Bancos ignorados separados por vírgula | `postgres,template0,template1` |
112
+ | `ONLY_DATABASES` | Bancos permitidos separados por vírgula | vazio |
113
+
114
+ ## Exemplos de filtro
115
+
116
+ Backup de todos os bancos, exceto internos:
117
+
118
+ ```env
119
+ IGNORE_DATABASES=postgres,template0,template1
120
+ ONLY_DATABASES=
121
+ ```
122
+
123
+ Backup apenas de alguns bancos:
124
+
125
+ ```env
126
+ ONLY_DATABASES=sistema_escolar,financeiro,pmgestao
127
+ ```
128
+
129
+ Ignorar banco de teste:
130
+
131
+ ```env
132
+ IGNORE_DATABASES=postgres,template0,template1,test_db
133
+ ```
134
+
135
+ ## Restaurar backup
136
+
137
+ Criar novo banco e restaurar:
138
+
139
+ ```bash
140
+ createdb -U postgres nome_banco_restaurado
141
+ pg_restore -U postgres -d nome_banco_restaurado ./backups/pmgestao/pmgestao_2026-06-25_02-00-00.dump
142
+ ```
143
+
144
+ Restaurar limpando objetos existentes:
145
+
146
+ ```bash
147
+ pg_restore -U postgres --clean --if-exists -d nome_banco ./backups/pmgestao/pmgestao_2026-06-25_02-00-00.dump
148
+ ```
149
+
150
+ ## Docker
151
+
152
+ Build:
153
+
154
+ ```bash
155
+ docker build -t pg-backup-sdk .
156
+ ```
157
+
158
+ Run:
159
+
160
+ ```bash
161
+ docker run --rm \
162
+ --env-file .env \
163
+ -v $(pwd)/backups:/app/backups \
164
+ pg-backup-sdk
165
+ ```
166
+
167
+ Com Compose:
168
+
169
+ ```bash
170
+ cp docker-compose.example.yml docker-compose.yml
171
+ docker compose up -d
172
+ ```
173
+
174
+ ## systemd
175
+
176
+ Copie o projeto para `/opt/pg-backup-sdk`.
177
+
178
+ ```bash
179
+ npm install
180
+ npm run build
181
+ sudo cp systemd.example.service /etc/systemd/system/pg-backup-sdk.service
182
+ sudo systemctl daemon-reload
183
+ sudo systemctl enable pg-backup-sdk
184
+ sudo systemctl start pg-backup-sdk
185
+ ```
186
+
187
+ Logs:
188
+
189
+ ```bash
190
+ journalctl -u pg-backup-sdk -f
191
+ ```
192
+
193
+ ## Como publicar no GitHub
194
+
195
+ ```bash
196
+ git init
197
+ git add .
198
+ git commit -m "Initial open source PostgreSQL backup module"
199
+ git branch -M main
200
+ git remote add origin https://github.com/guio11221/pg-backup-sdk.git
201
+ git push -u origin main
202
+ ```
203
+
204
+ ## Segurança
205
+
206
+ * Não suba o arquivo `.env` para o GitHub.
207
+ * Use usuário PostgreSQL com permissões adequadas para backup.
208
+ * Salve backups em disco seguro.
209
+ * Para produção, considere copiar os backups para outro servidor, S3, NAS ou storage externo.
210
+
211
+ ## Licença
212
+
213
+ MIT.
@@ -0,0 +1,7 @@
1
+ export type BackupResult = {
2
+ database: string;
3
+ outputPath: string;
4
+ success: boolean;
5
+ error?: unknown;
6
+ };
7
+ export declare function runBackupJob(): Promise<BackupResult[]>;
package/dist/backup.js ADDED
@@ -0,0 +1,132 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createWriteStream } from 'node:fs';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import { config } from './config.js';
6
+ import { listDatabases } from './postgres.js';
7
+ import { Logger } from './logger.js';
8
+ import { pathExists, safeFolderName, timestampForFile } from './fs-utils.js';
9
+ async function dumpDatabase(databaseName) {
10
+ const databaseFolder = path.join(config.backup.dir, safeFolderName(databaseName));
11
+ await fs.mkdir(databaseFolder, { recursive: true });
12
+ const outputPath = path.join(databaseFolder, `${safeFolderName(databaseName)}_${timestampForFile()}.dump`);
13
+ const outputStream = createWriteStream(outputPath);
14
+ const args = [
15
+ '--host',
16
+ config.pg.host,
17
+ '--port',
18
+ String(config.pg.port),
19
+ '--username',
20
+ config.pg.user,
21
+ '--dbname',
22
+ databaseName,
23
+ '--format',
24
+ 'custom',
25
+ '--blobs',
26
+ '--verbose',
27
+ '--no-password'
28
+ ];
29
+ Logger.info(`Backup started: ${databaseName}`);
30
+ await new Promise((resolve, reject) => {
31
+ const child = spawn(config.binaries.pgDump, args, {
32
+ env: {
33
+ ...process.env,
34
+ PGPASSWORD: config.pg.password
35
+ },
36
+ stdio: ['ignore', 'pipe', 'pipe']
37
+ });
38
+ let stderr = '';
39
+ child.stdout.pipe(outputStream);
40
+ child.stderr.on('data', (data) => {
41
+ const text = data.toString();
42
+ stderr += text;
43
+ process.stdout.write(`[pg_dump:${databaseName}] ${text}`);
44
+ });
45
+ child.on('error', async (error) => {
46
+ outputStream.close();
47
+ if (await pathExists(outputPath)) {
48
+ await fs.unlink(outputPath).catch(() => undefined);
49
+ }
50
+ reject(error);
51
+ });
52
+ child.on('close', async (code) => {
53
+ outputStream.close();
54
+ if (code === 0) {
55
+ resolve();
56
+ return;
57
+ }
58
+ if (await pathExists(outputPath)) {
59
+ await fs.unlink(outputPath).catch(() => undefined);
60
+ }
61
+ reject(new Error(`pg_dump failed for database ${databaseName}. Exit code: ${code}. Details: ${stderr}`));
62
+ });
63
+ });
64
+ Logger.info(`Backup finished: ${outputPath}`);
65
+ return outputPath;
66
+ }
67
+ async function removeOldBackups() {
68
+ const retentionMs = config.backup.retentionDays * 24 * 60 * 60 * 1000;
69
+ const now = Date.now();
70
+ await fs.mkdir(config.backup.dir, { recursive: true });
71
+ const databaseFolders = await fs.readdir(config.backup.dir, {
72
+ withFileTypes: true
73
+ });
74
+ for (const folder of databaseFolders) {
75
+ if (!folder.isDirectory()) {
76
+ continue;
77
+ }
78
+ const folderPath = path.join(config.backup.dir, folder.name);
79
+ const files = await fs.readdir(folderPath, { withFileTypes: true });
80
+ for (const file of files) {
81
+ if (!file.isFile() || !file.name.endsWith('.dump')) {
82
+ continue;
83
+ }
84
+ const filePath = path.join(folderPath, file.name);
85
+ const stat = await fs.stat(filePath);
86
+ const ageMs = now - stat.mtime.getTime();
87
+ if (ageMs > retentionMs) {
88
+ await fs.unlink(filePath);
89
+ Logger.info(`Old backup removed: ${filePath}`);
90
+ }
91
+ }
92
+ }
93
+ }
94
+ export async function runBackupJob() {
95
+ Logger.info('Backup job started');
96
+ await fs.mkdir(config.backup.dir, { recursive: true });
97
+ const databases = await listDatabases();
98
+ if (databases.length === 0) {
99
+ Logger.warn('No database found for backup');
100
+ return [];
101
+ }
102
+ Logger.info(`Databases selected: ${databases.join(', ')}`);
103
+ const results = [];
104
+ for (const database of databases) {
105
+ try {
106
+ const outputPath = await dumpDatabase(database);
107
+ results.push({
108
+ database,
109
+ outputPath,
110
+ success: true
111
+ });
112
+ }
113
+ catch (error) {
114
+ Logger.error(`Backup failed: ${database}`, error);
115
+ results.push({
116
+ database,
117
+ outputPath: '',
118
+ success: false,
119
+ error
120
+ });
121
+ }
122
+ }
123
+ await removeOldBackups();
124
+ const failures = results.filter((result) => !result.success);
125
+ if (failures.length > 0) {
126
+ Logger.warn(`Backup job finished with failures: ${failures.length}`);
127
+ return results;
128
+ }
129
+ Logger.info('Backup job finished successfully');
130
+ return results;
131
+ }
132
+ //# sourceMappingURL=backup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup.js","sourceRoot":"","sources":["../src/backup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import 'dotenv/config';
2
+ export declare const config: {
3
+ readonly pg: {
4
+ readonly host: string;
5
+ readonly port: number;
6
+ readonly user: string;
7
+ readonly password: string;
8
+ readonly adminDatabase: string;
9
+ };
10
+ readonly backup: {
11
+ readonly dir: string;
12
+ readonly cron: string;
13
+ readonly retentionDays: number;
14
+ readonly ignoreDatabases: string[];
15
+ readonly onlyDatabases: string[];
16
+ };
17
+ readonly binaries: {
18
+ readonly pgDump: string;
19
+ };
20
+ };
package/dist/config.js ADDED
@@ -0,0 +1,57 @@
1
+ import 'dotenv/config';
2
+ import path from 'node:path';
3
+ function requiredEnv(name) {
4
+ const value = process.env[name];
5
+ if (!value || value.trim().length === 0) {
6
+ throw new Error(`Required environment variable not defined: ${name}`);
7
+ }
8
+ return value.trim();
9
+ }
10
+ function optionalEnv(name, fallback) {
11
+ const value = process.env[name];
12
+ if (!value || value.trim().length === 0) {
13
+ return fallback;
14
+ }
15
+ return value.trim();
16
+ }
17
+ function numberEnv(name, fallback) {
18
+ const value = process.env[name];
19
+ if (!value || value.trim().length === 0) {
20
+ return fallback;
21
+ }
22
+ const parsed = Number(value);
23
+ if (!Number.isFinite(parsed)) {
24
+ throw new Error(`Invalid number environment variable: ${name}`);
25
+ }
26
+ return parsed;
27
+ }
28
+ function csvEnv(name, fallback) {
29
+ const value = process.env[name];
30
+ if (!value || value.trim().length === 0) {
31
+ return fallback;
32
+ }
33
+ return value
34
+ .split(',')
35
+ .map((item) => item.trim())
36
+ .filter((item) => item.length > 0);
37
+ }
38
+ export const config = {
39
+ pg: {
40
+ host: requiredEnv('PG_HOST'),
41
+ port: numberEnv('PG_PORT', 5432),
42
+ user: requiredEnv('PG_USER'),
43
+ password: requiredEnv('PG_PASSWORD'),
44
+ adminDatabase: optionalEnv('PG_ADMIN_DATABASE', 'postgres')
45
+ },
46
+ backup: {
47
+ dir: path.resolve(optionalEnv('BACKUP_DIR', './backups')),
48
+ cron: optionalEnv('BACKUP_CRON', '0 2 * * *'),
49
+ retentionDays: numberEnv('BACKUP_RETENTION_DAYS', 7),
50
+ ignoreDatabases: csvEnv('IGNORE_DATABASES', ['postgres', 'template0', 'template1']),
51
+ onlyDatabases: csvEnv('ONLY_DATABASES', [])
52
+ },
53
+ binaries: {
54
+ pgDump: optionalEnv('PG_DUMP_BIN', 'pg_dump')
55
+ }
56
+ };
57
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AACvB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,SAAS,WAAW,CAAC,IAAY;IAC/B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAEhC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,8CAA8C,IAAI,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,QAAgB;IACjD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAEhC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,SAAS,SAAS,CAAC,IAAY,EAAE,QAAgB;IAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAEhC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,wCAAwC,IAAI,EAAE,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,MAAM,CAAC,IAAY,EAAE,QAAkB;IAC9C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAEhC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,EAAE,EAAE;QACF,IAAI,EAAE,WAAW,CAAC,SAAS,CAAC;QAC5B,IAAI,EAAE,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC;QAChC,IAAI,EAAE,WAAW,CAAC,SAAS,CAAC;QAC5B,QAAQ,EAAE,WAAW,CAAC,aAAa,CAAC;QACpC,aAAa,EAAE,WAAW,CAAC,mBAAmB,EAAE,UAAU,CAAC;KAC5D;IAED,MAAM,EAAE;QACN,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;QACzD,IAAI,EAAE,WAAW,CAAC,aAAa,EAAE,WAAW,CAAC;QAC7C,aAAa,EAAE,SAAS,CAAC,uBAAuB,EAAE,CAAC,CAAC;QACpD,eAAe,EAAE,MAAM,CAAC,kBAAkB,EAAE,CAAC,UAAU,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;QACnF,aAAa,EAAE,MAAM,CAAC,gBAAgB,EAAE,EAAE,CAAC;KAC5C;IAED,QAAQ,EAAE;QACR,MAAM,EAAE,WAAW,CAAC,aAAa,EAAE,SAAS,CAAC;KAC9C;CACO,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function pathExists(filePath: string): Promise<boolean>;
2
+ export declare function safeFolderName(value: string): string;
3
+ export declare function timestampForFile(): string;
@@ -0,0 +1,24 @@
1
+ import fs from 'node:fs/promises';
2
+ export async function pathExists(filePath) {
3
+ try {
4
+ await fs.access(filePath);
5
+ return true;
6
+ }
7
+ catch {
8
+ return false;
9
+ }
10
+ }
11
+ export function safeFolderName(value) {
12
+ return value.replace(/[^a-zA-Z0-9_-]/g, '_');
13
+ }
14
+ export function timestampForFile() {
15
+ const now = new Date();
16
+ const yyyy = now.getFullYear();
17
+ const mm = String(now.getMonth() + 1).padStart(2, '0');
18
+ const dd = String(now.getDate()).padStart(2, '0');
19
+ const hh = String(now.getHours()).padStart(2, '0');
20
+ const mi = String(now.getMinutes()).padStart(2, '0');
21
+ const ss = String(now.getSeconds()).padStart(2, '0');
22
+ return `${yyyy}-${mm}-${dd}_${hh}-${mi}-${ss}`;
23
+ }
24
+ //# sourceMappingURL=fs-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-utils.js","sourceRoot":"","sources":["../src/fs-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAElC,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,KAAK,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IAEvB,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAC/B,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvD,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClD,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACnD,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACrD,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAErD,OAAO,GAAG,IAAI,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC;AACjD,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export { startBackupScheduler } from './scheduler.js';
3
+ export { runBackupJob } from './backup.js';
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from 'node:url';
3
+ import { resolve } from 'node:path';
4
+ import { startBackupScheduler } from './scheduler.js';
5
+ import { runBackupJob } from './backup.js';
6
+ export { startBackupScheduler } from './scheduler.js';
7
+ export { runBackupJob } from './backup.js';
8
+ function isCliExecution() {
9
+ const currentFile = fileURLToPath(import.meta.url);
10
+ const executedFile = process.argv[1];
11
+ if (!executedFile) {
12
+ return false;
13
+ }
14
+ return resolve(executedFile) === resolve(currentFile);
15
+ }
16
+ async function main() {
17
+ if (!isCliExecution()) {
18
+ return;
19
+ }
20
+ const shouldRunNow = process.argv.includes('--now');
21
+ if (shouldRunNow) {
22
+ await runBackupJob();
23
+ return;
24
+ }
25
+ startBackupScheduler();
26
+ process.stdin.resume();
27
+ }
28
+ main().catch((error) => {
29
+ console.error('[BKP] Falha fatal:', error);
30
+ process.exit(1);
31
+ });
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,SAAS,cAAc;IACrB,MAAM,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnD,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAErC,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,OAAO,CAAC,YAAY,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;AACxD,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;QACtB,OAAO;IACT,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAEpD,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,YAAY,EAAE,CAAC;QACrB,OAAO;IACT,CAAC;IAED,oBAAoB,EAAE,CAAC;IACvB,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;AACzB,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;IAC3C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare class Logger {
2
+ private static format;
3
+ static info(message: string): void;
4
+ static warn(message: string): void;
5
+ static error(message: string, error?: unknown): void;
6
+ }
package/dist/logger.js ADDED
@@ -0,0 +1,22 @@
1
+ export class Logger {
2
+ static format(level, message) {
3
+ return `[${new Date().toISOString()}] [${level}] ${message}`;
4
+ }
5
+ static info(message) {
6
+ console.log(Logger.format('INFO', message));
7
+ }
8
+ static warn(message) {
9
+ console.warn(Logger.format('WARN', message));
10
+ }
11
+ static error(message, error) {
12
+ console.error(Logger.format('ERROR', message));
13
+ if (error instanceof Error) {
14
+ console.error(error.stack ?? error.message);
15
+ return;
16
+ }
17
+ if (error !== undefined) {
18
+ console.error(error);
19
+ }
20
+ }
21
+ }
22
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,MAAM;IACT,MAAM,CAAC,MAAM,CAAC,KAAa,EAAE,OAAe;QAClD,OAAO,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,KAAK,OAAO,EAAE,CAAC;IAC/D,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,OAAe;QACzB,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,OAAe;QACzB,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,CAAC,KAAK,CAAC,OAAe,EAAE,KAAe;QAC3C,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QAE/C,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;YAC3B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;YAC5C,OAAO;QACT,CAAC;QAED,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1 @@
1
+ export declare function listDatabases(): Promise<string[]>;
@@ -0,0 +1,31 @@
1
+ import { Client } from 'pg';
2
+ import { config } from './config.js';
3
+ export async function listDatabases() {
4
+ const client = new Client({
5
+ host: config.pg.host,
6
+ port: config.pg.port,
7
+ user: config.pg.user,
8
+ password: config.pg.password,
9
+ database: config.pg.adminDatabase
10
+ });
11
+ await client.connect();
12
+ try {
13
+ const result = await client.query(`
14
+ SELECT datname
15
+ FROM pg_database
16
+ WHERE datistemplate = false
17
+ AND datallowconn = true
18
+ ORDER BY datname ASC
19
+ `);
20
+ const ignored = new Set(config.backup.ignoreDatabases);
21
+ const only = new Set(config.backup.onlyDatabases);
22
+ return result.rows
23
+ .map((row) => row.datname)
24
+ .filter((databaseName) => !ignored.has(databaseName))
25
+ .filter((databaseName) => only.size === 0 || only.has(databaseName));
26
+ }
27
+ finally {
28
+ await client.end();
29
+ }
30
+ }
31
+ //# sourceMappingURL=postgres.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres.js","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAMrC,MAAM,CAAC,KAAK,UAAU,aAAa;IACjC,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;QACxB,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,IAAI;QACpB,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,IAAI;QACpB,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,IAAI;QACpB,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ;QAC5B,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,aAAa;KAClC,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IAEvB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAc;;;;;;KAM9C,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;QACvD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;QAElD,OAAO,MAAM,CAAC,IAAI;aACf,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC;aACzB,MAAM,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;aACpD,MAAM,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC;IACzE,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC;IACrB,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function startBackupScheduler(): void;
@@ -0,0 +1,30 @@
1
+ import cron from 'node-cron';
2
+ import { config } from './config.js';
3
+ import { runBackupJob } from './backup.js';
4
+ import { Logger } from './logger.js';
5
+ let isRunning = false;
6
+ export function startBackupScheduler() {
7
+ if (!cron.validate(config.backup.cron)) {
8
+ throw new Error(`Invalid CRON expression: ${config.backup.cron}`);
9
+ }
10
+ Logger.info(`Scheduler started with CRON: ${config.backup.cron}`);
11
+ Logger.info(`Backup directory: ${config.backup.dir}`);
12
+ Logger.info(`Retention: ${config.backup.retentionDays} days`);
13
+ cron.schedule(config.backup.cron, async () => {
14
+ if (isRunning) {
15
+ Logger.warn('Backup ignored because another job is still running');
16
+ return;
17
+ }
18
+ isRunning = true;
19
+ try {
20
+ await runBackupJob();
21
+ }
22
+ catch (error) {
23
+ Logger.error('Unexpected scheduler error', error);
24
+ }
25
+ finally {
26
+ isRunning = false;
27
+ }
28
+ });
29
+ }
30
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../src/scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,IAAI,SAAS,GAAG,KAAK,CAAC;AAEtB,MAAM,UAAU,oBAAoB;IAClC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,4BAA4B,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,gCAAgC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IAClE,MAAM,CAAC,IAAI,CAAC,qBAAqB,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;IACtD,MAAM,CAAC,IAAI,CAAC,cAAc,MAAM,CAAC,MAAM,CAAC,aAAa,OAAO,CAAC,CAAC;IAE9D,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE;QAC3C,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC;YACnE,OAAO;QACT,CAAC;QAED,SAAS,GAAG,IAAI,CAAC;QAEjB,IAAI,CAAC;YACH,MAAM,YAAY,EAAE,CAAC;QACvB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;QACpD,CAAC;gBAAS,CAAC;YACT,SAAS,GAAG,KAAK,CAAC;QACpB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "pg-backup-sdk",
3
+ "version": "1.0.0",
4
+ "description": "PostgreSQL backup scheduler with pg_dump, retention policy and per-database folders.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "bin": {
15
+ "pg-backup-sdk": "dist/index.js"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md",
20
+ "LICENSE",
21
+ ".env.example"
22
+ ],
23
+ "scripts": {
24
+ "dev": "tsx src/index.ts",
25
+ "backup": "tsx src/index.ts --now",
26
+ "build": "tsc",
27
+ "start": "node dist/index.js",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "keywords": [
31
+ "postgresql",
32
+ "postgres",
33
+ "backup",
34
+ "pg-dump",
35
+ "database",
36
+ "scheduler",
37
+ "cron",
38
+ "typescript",
39
+ "nodejs"
40
+ ],
41
+ "author": "GUILHERME DE OLIVEIRA",
42
+ "license": "MIT",
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "dependencies": {
47
+ "dotenv": "^16.4.7",
48
+ "node-cron": "^3.0.3",
49
+ "pg": "^8.13.1"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^22.10.2",
53
+ "@types/node-cron": "^3.0.11",
54
+ "@types/pg": "^8.11.10",
55
+ "tsx": "^4.19.2",
56
+ "typescript": "^5.7.2"
57
+ },
58
+ "engines": {
59
+ "node": ">=20"
60
+ }
61
+ }