oopsdb 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/LICENSE +21 -0
- package/README.md +108 -0
- package/dist/commands/activate.d.ts +3 -0
- package/dist/commands/activate.js +89 -0
- package/dist/commands/clean.d.ts +3 -0
- package/dist/commands/clean.js +93 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +317 -0
- package/dist/commands/restore.d.ts +1 -0
- package/dist/commands/restore.js +130 -0
- package/dist/commands/secure.d.ts +4 -0
- package/dist/commands/secure.js +18 -0
- package/dist/commands/snapshot.d.ts +1 -0
- package/dist/commands/snapshot.js +73 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +100 -0
- package/dist/commands/watch.d.ts +3 -0
- package/dist/commands/watch.js +59 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +69 -0
- package/dist/utils/config.d.ts +23 -0
- package/dist/utils/config.js +114 -0
- package/dist/utils/dumper.d.ts +23 -0
- package/dist/utils/dumper.js +251 -0
- package/dist/utils/license.d.ts +29 -0
- package/dist/utils/license.js +205 -0
- package/dist/utils/preflight.d.ts +2 -0
- package/dist/utils/preflight.js +101 -0
- package/package.json +58 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.restoreCommand = restoreCommand;
|
|
40
|
+
const inquirer = __importStar(require("inquirer"));
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const ora_1 = __importDefault(require("ora"));
|
|
43
|
+
const config_1 = require("../utils/config");
|
|
44
|
+
const dumper_1 = require("../utils/dumper");
|
|
45
|
+
const preflight_1 = require("../utils/preflight");
|
|
46
|
+
function formatSize(bytes) {
|
|
47
|
+
if (bytes < 1024)
|
|
48
|
+
return `${bytes} B`;
|
|
49
|
+
if (bytes < 1024 * 1024)
|
|
50
|
+
return `${Math.round(bytes / 1024)} KB`;
|
|
51
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
52
|
+
}
|
|
53
|
+
function timeAgo(date) {
|
|
54
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
55
|
+
if (seconds < 60)
|
|
56
|
+
return `${seconds}s ago`;
|
|
57
|
+
if (seconds < 3600)
|
|
58
|
+
return `${Math.floor(seconds / 60)}m ago`;
|
|
59
|
+
if (seconds < 86400)
|
|
60
|
+
return `${Math.floor(seconds / 3600)}h ago`;
|
|
61
|
+
return `${Math.floor(seconds / 86400)}d ago`;
|
|
62
|
+
}
|
|
63
|
+
async function restoreCommand() {
|
|
64
|
+
const config = (0, config_1.loadConfig)();
|
|
65
|
+
if (!config) {
|
|
66
|
+
console.log(chalk_1.default.red('\n No config found. Run `oopsdb init` first.\n'));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
const snapshots = (0, dumper_1.listSnapshots)();
|
|
70
|
+
if (snapshots.length === 0) {
|
|
71
|
+
console.log(chalk_1.default.yellow('\n No snapshots found. Run `oopsdb snapshot` or `oopsdb watch` first.\n'));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
console.log(chalk_1.default.bold('\n OopsDB Restore\n'));
|
|
75
|
+
console.log(chalk_1.default.gray(` Database: ${config.db.type} - ${config.db.database}\n`));
|
|
76
|
+
// Pre-flight: check that restore tool is available
|
|
77
|
+
const toolsOk = await (0, preflight_1.preflightCheck)(config.db.type, 'restore');
|
|
78
|
+
if (!toolsOk) {
|
|
79
|
+
console.log(chalk_1.default.red('\n Missing required database tools. Install them and try again.\n'));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
console.log();
|
|
83
|
+
// Show the last 10 snapshots
|
|
84
|
+
const recentSnapshots = snapshots.slice(0, 10);
|
|
85
|
+
const { selectedSnapshot } = await inquirer.prompt([
|
|
86
|
+
{
|
|
87
|
+
type: 'list',
|
|
88
|
+
name: 'selectedSnapshot',
|
|
89
|
+
message: 'Which snapshot do you want to restore?',
|
|
90
|
+
choices: recentSnapshots.map((s, i) => ({
|
|
91
|
+
name: `${i === 0 ? '(latest) ' : ''}${s.time.toLocaleString()} - ${timeAgo(s.time)} - ${formatSize(s.size)}`,
|
|
92
|
+
value: s.file,
|
|
93
|
+
})),
|
|
94
|
+
},
|
|
95
|
+
]);
|
|
96
|
+
const { confirm } = await inquirer.prompt([
|
|
97
|
+
{
|
|
98
|
+
type: 'confirm',
|
|
99
|
+
name: 'confirm',
|
|
100
|
+
message: chalk_1.default.yellow('This will overwrite your current database. Are you sure?'),
|
|
101
|
+
default: false,
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
if (!confirm) {
|
|
105
|
+
console.log(chalk_1.default.gray('\n Restore cancelled.\n'));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Take a safety snapshot before restoring
|
|
109
|
+
const safetySpinner = (0, ora_1.default)('Taking safety snapshot of current state...').start();
|
|
110
|
+
try {
|
|
111
|
+
await (0, dumper_1.createSnapshot)(config.db);
|
|
112
|
+
safetySpinner.succeed('Safety snapshot saved (just in case)');
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
safetySpinner.warn('Could not take safety snapshot, proceeding anyway');
|
|
116
|
+
}
|
|
117
|
+
// Restore
|
|
118
|
+
const restoreSpinner = (0, ora_1.default)('Restoring database...').start();
|
|
119
|
+
try {
|
|
120
|
+
await (0, dumper_1.restoreSnapshot)(config.db, selectedSnapshot);
|
|
121
|
+
restoreSpinner.succeed('Database restored successfully!');
|
|
122
|
+
console.log(chalk_1.default.green('\n Your database has been rolled back. Crisis averted!\n'));
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
restoreSpinner.fail(`Restore failed: ${err.message}`);
|
|
126
|
+
console.log(chalk_1.default.red('\n The restore did not complete. Your database may be in a partial state.'));
|
|
127
|
+
console.log(chalk_1.default.yellow(' A safety snapshot was taken before the restore attempt.\n'));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.secureCommand = secureCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
async function secureCommand(options) {
|
|
9
|
+
console.log(chalk_1.default.bold('\n OopsDB Secure'));
|
|
10
|
+
console.log(chalk_1.default.gray(' Immutable cloud backups that even a rogue AI can\'t delete.\n'));
|
|
11
|
+
console.log(chalk_1.default.yellow(' Coming Soon!'));
|
|
12
|
+
console.log(chalk_1.default.white(' We are currently putting the finishing touches on our secure cloud backup infrastructure.'));
|
|
13
|
+
console.log(chalk_1.default.gray(' Follow us on GitHub for updates: ') + chalk_1.default.cyan('https://github.com/pintayo/oopsdb\n'));
|
|
14
|
+
}
|
|
15
|
+
// ─── Utilities ──────────────────────────────────────────────────────────────
|
|
16
|
+
function delay(ms) {
|
|
17
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function snapshotCommand(): Promise<void>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.snapshotCommand = snapshotCommand;
|
|
40
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
+
const ora_1 = __importDefault(require("ora"));
|
|
42
|
+
const config_1 = require("../utils/config");
|
|
43
|
+
const dumper_1 = require("../utils/dumper");
|
|
44
|
+
const preflight_1 = require("../utils/preflight");
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
async function snapshotCommand() {
|
|
47
|
+
const config = (0, config_1.loadConfig)();
|
|
48
|
+
if (!config) {
|
|
49
|
+
console.log(chalk_1.default.red('\n No config found. Run `oopsdb init` first.\n'));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
console.log(chalk_1.default.bold('\n OopsDB Manual Snapshot\n'));
|
|
53
|
+
console.log(chalk_1.default.gray(` Database: ${config.db.type} - ${config.db.database}\n`));
|
|
54
|
+
// Pre-flight: check that dump tool is available
|
|
55
|
+
const toolsOk = await (0, preflight_1.preflightCheck)(config.db.type, 'dump');
|
|
56
|
+
if (!toolsOk) {
|
|
57
|
+
console.log(chalk_1.default.red('\n Missing required database tools. Install them and try again.\n'));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
console.log();
|
|
61
|
+
const spinner = (0, ora_1.default)('Taking snapshot...').start();
|
|
62
|
+
try {
|
|
63
|
+
const file = await (0, dumper_1.createSnapshot)(config.db);
|
|
64
|
+
const sizeKB = Math.round(fs.statSync(file).size / 1024);
|
|
65
|
+
spinner.succeed(`Snapshot saved: ${file} (${sizeKB} KB)`);
|
|
66
|
+
console.log(chalk_1.default.green('\n Done! Run `oopsdb restore` if you need to roll back.\n'));
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
spinner.fail(`Snapshot failed: ${err.message}`);
|
|
70
|
+
console.log(chalk_1.default.yellow('\n Tip: Make sure your database tools are installed and the database is running.\n'));
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function statusCommand(): Promise<void>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.statusCommand = statusCommand;
|
|
40
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
+
const config_1 = require("../utils/config");
|
|
42
|
+
const dumper_1 = require("../utils/dumper");
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
function formatSize(bytes) {
|
|
45
|
+
if (bytes < 1024)
|
|
46
|
+
return `${bytes} B`;
|
|
47
|
+
if (bytes < 1024 * 1024)
|
|
48
|
+
return `${Math.round(bytes / 1024)} KB`;
|
|
49
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
50
|
+
}
|
|
51
|
+
function timeAgo(date) {
|
|
52
|
+
const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
|
|
53
|
+
if (seconds < 60)
|
|
54
|
+
return `${seconds}s ago`;
|
|
55
|
+
if (seconds < 3600)
|
|
56
|
+
return `${Math.floor(seconds / 60)}m ago`;
|
|
57
|
+
if (seconds < 86400)
|
|
58
|
+
return `${Math.floor(seconds / 3600)}h ago`;
|
|
59
|
+
return `${Math.floor(seconds / 86400)}d ago`;
|
|
60
|
+
}
|
|
61
|
+
async function statusCommand() {
|
|
62
|
+
const config = (0, config_1.loadConfig)();
|
|
63
|
+
if (!config) {
|
|
64
|
+
console.log(chalk_1.default.red('\n No config found. Run `oopsdb init` first.\n'));
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const snapshots = (0, dumper_1.listSnapshots)();
|
|
68
|
+
console.log(chalk_1.default.bold('\n OopsDB Status\n'));
|
|
69
|
+
console.log(chalk_1.default.gray(' Database'));
|
|
70
|
+
const typeLabel = config.db.supabase ? 'supabase (postgres)' : config.db.type;
|
|
71
|
+
console.log(` Type: ${chalk_1.default.cyan(typeLabel)}`);
|
|
72
|
+
console.log(` Name: ${chalk_1.default.cyan(config.db.database)}`);
|
|
73
|
+
if (config.db.host) {
|
|
74
|
+
console.log(` Host: ${chalk_1.default.cyan(config.db.host + ':' + config.db.port)}`);
|
|
75
|
+
}
|
|
76
|
+
console.log(` Since: ${chalk_1.default.cyan(new Date(config.createdAt).toLocaleDateString())}`);
|
|
77
|
+
console.log();
|
|
78
|
+
console.log(chalk_1.default.gray(' Backups'));
|
|
79
|
+
console.log(` Location: ${chalk_1.default.cyan((0, config_1.getBackupsDir)())}`);
|
|
80
|
+
console.log(` Total: ${chalk_1.default.cyan(String(snapshots.length))} snapshot(s)`);
|
|
81
|
+
if (snapshots.length > 0) {
|
|
82
|
+
const totalSize = snapshots.reduce((sum, s) => sum + s.size, 0);
|
|
83
|
+
console.log(` Size: ${chalk_1.default.cyan(formatSize(totalSize))}`);
|
|
84
|
+
console.log(` Latest: ${chalk_1.default.cyan(timeAgo(snapshots[0].time))} (${snapshots[0].time.toLocaleString()})`);
|
|
85
|
+
console.log(` Oldest: ${chalk_1.default.cyan(timeAgo(snapshots[snapshots.length - 1].time))} (${snapshots[snapshots.length - 1].time.toLocaleString()})`);
|
|
86
|
+
console.log(chalk_1.default.gray('\n Recent Snapshots'));
|
|
87
|
+
snapshots.slice(0, 5).forEach((s, i) => {
|
|
88
|
+
const name = path.basename(s.file);
|
|
89
|
+
const marker = i === 0 ? chalk_1.default.green(' (latest)') : '';
|
|
90
|
+
console.log(` ${chalk_1.default.gray(`${i + 1}.`)} ${name} - ${formatSize(s.size)} - ${timeAgo(s.time)}${marker}`);
|
|
91
|
+
});
|
|
92
|
+
if (snapshots.length > 5) {
|
|
93
|
+
console.log(chalk_1.default.gray(` ... and ${snapshots.length - 5} more`));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
console.log(chalk_1.default.yellow('\n No snapshots yet. Run `oopsdb watch` or `oopsdb snapshot` to start.'));
|
|
98
|
+
}
|
|
99
|
+
console.log();
|
|
100
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.watchCommand = watchCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const config_1 = require("../utils/config");
|
|
10
|
+
const dumper_1 = require("../utils/dumper");
|
|
11
|
+
const preflight_1 = require("../utils/preflight");
|
|
12
|
+
async function watchCommand(options) {
|
|
13
|
+
const config = (0, config_1.loadConfig)();
|
|
14
|
+
if (!config) {
|
|
15
|
+
console.log(chalk_1.default.red('\n No config found. Run `oopsdb init` first.\n'));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
// Pre-flight: check that dump tool is available
|
|
19
|
+
console.log(chalk_1.default.gray('\n Checking for required tools...\n'));
|
|
20
|
+
const toolsOk = await (0, preflight_1.preflightCheck)(config.db.type, 'dump');
|
|
21
|
+
if (!toolsOk) {
|
|
22
|
+
console.log(chalk_1.default.red('\n Missing required database tools. Install them and try again.\n'));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const intervalMinutes = parseInt(options.interval, 10);
|
|
26
|
+
if (isNaN(intervalMinutes) || intervalMinutes < 1) {
|
|
27
|
+
console.log(chalk_1.default.red('\n Interval must be at least 1 minute.\n'));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const intervalMs = intervalMinutes * 60 * 1000;
|
|
31
|
+
console.log(chalk_1.default.bold('\n OopsDB Watch Mode\n'));
|
|
32
|
+
console.log(chalk_1.default.gray(` Database: ${config.db.type} - ${config.db.database}`));
|
|
33
|
+
console.log(chalk_1.default.gray(` Interval: every ${intervalMinutes} minute(s)`));
|
|
34
|
+
console.log(chalk_1.default.gray(' Press Ctrl+C to stop.\n'));
|
|
35
|
+
// Take an immediate snapshot on start
|
|
36
|
+
await takeSnapshotWithLog(config.db);
|
|
37
|
+
// Set up the interval
|
|
38
|
+
const timer = setInterval(async () => {
|
|
39
|
+
await takeSnapshotWithLog(config.db);
|
|
40
|
+
}, intervalMs);
|
|
41
|
+
// Clean exit on Ctrl+C
|
|
42
|
+
process.on('SIGINT', () => {
|
|
43
|
+
clearInterval(timer);
|
|
44
|
+
const snapshots = (0, dumper_1.listSnapshots)();
|
|
45
|
+
console.log(chalk_1.default.bold(`\n\n Watch stopped. ${snapshots.length} total snapshot(s) saved.\n`));
|
|
46
|
+
process.exit(0);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function takeSnapshotWithLog(dbConfig) {
|
|
50
|
+
const spinner = (0, ora_1.default)(`[${new Date().toLocaleTimeString()}] Taking snapshot...`).start();
|
|
51
|
+
try {
|
|
52
|
+
const file = await (0, dumper_1.createSnapshot)(dbConfig);
|
|
53
|
+
const sizeKB = Math.round(require('fs').statSync(file).size / 1024);
|
|
54
|
+
spinner.succeed(`[${new Date().toLocaleTimeString()}] Snapshot saved (${sizeKB} KB)`);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
spinner.fail(`[${new Date().toLocaleTimeString()}] Snapshot failed: ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const init_1 = require("./commands/init");
|
|
6
|
+
const watch_1 = require("./commands/watch");
|
|
7
|
+
const restore_1 = require("./commands/restore");
|
|
8
|
+
const status_1 = require("./commands/status");
|
|
9
|
+
const snapshot_1 = require("./commands/snapshot");
|
|
10
|
+
const secure_1 = require("./commands/secure");
|
|
11
|
+
const clean_1 = require("./commands/clean");
|
|
12
|
+
const activate_1 = require("./commands/activate");
|
|
13
|
+
// Read version from package.json at runtime
|
|
14
|
+
const pkg = require('../package.json');
|
|
15
|
+
const program = new commander_1.Command();
|
|
16
|
+
program
|
|
17
|
+
.name('oopsdb')
|
|
18
|
+
.description('Don\'t let AI nuke your database. Auto-backup and 1-click restore.')
|
|
19
|
+
.version(pkg.version);
|
|
20
|
+
program
|
|
21
|
+
.command('init')
|
|
22
|
+
.description('Set up database connection for backups')
|
|
23
|
+
.action(init_1.initCommand);
|
|
24
|
+
program
|
|
25
|
+
.command('watch')
|
|
26
|
+
.description('Start watching and backing up your database at intervals')
|
|
27
|
+
.option('-i, --interval <minutes>', 'Backup interval in minutes', '5')
|
|
28
|
+
.action(watch_1.watchCommand);
|
|
29
|
+
program
|
|
30
|
+
.command('snapshot')
|
|
31
|
+
.description('Take a one-time snapshot right now')
|
|
32
|
+
.action(snapshot_1.snapshotCommand);
|
|
33
|
+
program
|
|
34
|
+
.command('restore')
|
|
35
|
+
.description('Restore your database from a backup')
|
|
36
|
+
.action(restore_1.restoreCommand);
|
|
37
|
+
program
|
|
38
|
+
.command('status')
|
|
39
|
+
.description('Show backup status and recent snapshots')
|
|
40
|
+
.action(status_1.statusCommand);
|
|
41
|
+
program
|
|
42
|
+
.command('secure')
|
|
43
|
+
.description('Immutable cloud backups — tamper-proof, AI-proof')
|
|
44
|
+
.option('--push', 'Push latest snapshot to immutable cloud storage')
|
|
45
|
+
.option('--status', 'Show Secure activation status')
|
|
46
|
+
.action(secure_1.secureCommand);
|
|
47
|
+
program
|
|
48
|
+
.command('activate <license-key>')
|
|
49
|
+
.description('Activate a Pro or Secure license key')
|
|
50
|
+
.action(activate_1.activateCommand);
|
|
51
|
+
program
|
|
52
|
+
.command('deactivate')
|
|
53
|
+
.description('Deactivate your license on this machine')
|
|
54
|
+
.action(activate_1.deactivateCommand);
|
|
55
|
+
program
|
|
56
|
+
.command('license')
|
|
57
|
+
.description('Show current license status and plan')
|
|
58
|
+
.action(activate_1.licenseStatusCommand);
|
|
59
|
+
program
|
|
60
|
+
.command('clean')
|
|
61
|
+
.description('Remove all OopsDB data (.oopsdb/) from current project')
|
|
62
|
+
.option('--yes', 'Skip confirmation prompt')
|
|
63
|
+
.action(clean_1.cleanCommand);
|
|
64
|
+
program.parse(process.argv);
|
|
65
|
+
if (!process.argv.slice(2).length) {
|
|
66
|
+
program.outputHelp();
|
|
67
|
+
console.log('\n \x1b[35mNew:\x1b[0m \x1b[1moopsdb secure\x1b[0m — Immutable cloud backups that even a rogue AI can\'t delete.');
|
|
68
|
+
console.log(' \x1b[90mLearn more:\x1b[0m \x1b[36mhttps://oopsdb.com/secure\x1b[0m\n');
|
|
69
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface DbConfig {
|
|
2
|
+
type: 'postgres' | 'mysql' | 'sqlite';
|
|
3
|
+
host?: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
user?: string;
|
|
6
|
+
password?: string;
|
|
7
|
+
database: string;
|
|
8
|
+
connectionString?: string;
|
|
9
|
+
/** When true, uses Supabase-specific pg_dump flags (--no-owner, --no-privileges, --no-subscriptions) */
|
|
10
|
+
supabase?: boolean;
|
|
11
|
+
/** SSL mode for Postgres connections (e.g., 'require', 'verify-full') */
|
|
12
|
+
sslmode?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface OopsConfig {
|
|
15
|
+
db: DbConfig;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function ensureConfigDir(): void;
|
|
19
|
+
export declare function saveConfig(config: OopsConfig): void;
|
|
20
|
+
export declare function loadConfig(): OopsConfig | null;
|
|
21
|
+
export declare function getBackupsDir(): string;
|
|
22
|
+
export declare function getConfigDir(): string;
|
|
23
|
+
export declare function getEncryptionKey(): Buffer;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ensureConfigDir = ensureConfigDir;
|
|
37
|
+
exports.saveConfig = saveConfig;
|
|
38
|
+
exports.loadConfig = loadConfig;
|
|
39
|
+
exports.getBackupsDir = getBackupsDir;
|
|
40
|
+
exports.getConfigDir = getConfigDir;
|
|
41
|
+
exports.getEncryptionKey = getEncryptionKey;
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const crypto = __importStar(require("crypto"));
|
|
45
|
+
const CONFIG_DIR = path.join(process.cwd(), '.oopsdb');
|
|
46
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
47
|
+
const BACKUPS_DIR = path.join(CONFIG_DIR, 'backups');
|
|
48
|
+
// Simple encryption using a machine-local key derived from hostname + username
|
|
49
|
+
const ENCRYPTION_KEY = crypto
|
|
50
|
+
.createHash('sha256')
|
|
51
|
+
.update(`oopsdb-${process.env.USER || 'default'}-${require('os').hostname()}`)
|
|
52
|
+
.digest();
|
|
53
|
+
function encrypt(text) {
|
|
54
|
+
const iv = crypto.randomBytes(16);
|
|
55
|
+
const cipher = crypto.createCipheriv('aes-256-cbc', ENCRYPTION_KEY, iv);
|
|
56
|
+
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
57
|
+
encrypted += cipher.final('hex');
|
|
58
|
+
return iv.toString('hex') + ':' + encrypted;
|
|
59
|
+
}
|
|
60
|
+
function decrypt(text) {
|
|
61
|
+
const [ivHex, encrypted] = text.split(':');
|
|
62
|
+
const iv = Buffer.from(ivHex, 'hex');
|
|
63
|
+
const decipher = crypto.createDecipheriv('aes-256-cbc', ENCRYPTION_KEY, iv);
|
|
64
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
65
|
+
decrypted += decipher.final('utf8');
|
|
66
|
+
return decrypted;
|
|
67
|
+
}
|
|
68
|
+
function ensureConfigDir() {
|
|
69
|
+
try {
|
|
70
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
71
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
if (!fs.existsSync(BACKUPS_DIR)) {
|
|
74
|
+
fs.mkdirSync(BACKUPS_DIR, { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
if (err.code === 'EACCES') {
|
|
79
|
+
throw new Error(`Permission denied creating ${CONFIG_DIR}. Check directory permissions.`);
|
|
80
|
+
}
|
|
81
|
+
if (err.code === 'ENOSPC') {
|
|
82
|
+
throw new Error('Disk full. Free up space and try again.');
|
|
83
|
+
}
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function saveConfig(config) {
|
|
88
|
+
ensureConfigDir();
|
|
89
|
+
const encrypted = encrypt(JSON.stringify(config));
|
|
90
|
+
fs.writeFileSync(CONFIG_FILE, encrypted, 'utf8');
|
|
91
|
+
}
|
|
92
|
+
function loadConfig() {
|
|
93
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const encrypted = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
98
|
+
const decrypted = decrypt(encrypted);
|
|
99
|
+
return JSON.parse(decrypted);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function getBackupsDir() {
|
|
106
|
+
ensureConfigDir();
|
|
107
|
+
return BACKUPS_DIR;
|
|
108
|
+
}
|
|
109
|
+
function getConfigDir() {
|
|
110
|
+
return CONFIG_DIR;
|
|
111
|
+
}
|
|
112
|
+
function getEncryptionKey() {
|
|
113
|
+
return ENCRYPTION_KEY;
|
|
114
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DbConfig } from './config';
|
|
2
|
+
/**
|
|
3
|
+
* Creates an encrypted, streamed snapshot of the database.
|
|
4
|
+
*
|
|
5
|
+
* For Postgres/MySQL: spawns pg_dump/mysqldump and pipes stdout → cipher → file.
|
|
6
|
+
* For SQLite: uses the native .backup command (always writes to a file), then
|
|
7
|
+
* streams that temp file through the cipher into the final encrypted output.
|
|
8
|
+
*
|
|
9
|
+
* Memory footprint stays near-zero regardless of database size.
|
|
10
|
+
*/
|
|
11
|
+
export declare function createSnapshot(config: DbConfig): Promise<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Restores a database from an encrypted snapshot file by streaming:
|
|
14
|
+
* file → decipher → psql/mysql stdin.
|
|
15
|
+
*
|
|
16
|
+
* For SQLite: deciphers to a temp file, then copies over the original.
|
|
17
|
+
*/
|
|
18
|
+
export declare function restoreSnapshot(config: DbConfig, snapshotPath: string): Promise<void>;
|
|
19
|
+
export declare function listSnapshots(): {
|
|
20
|
+
file: string;
|
|
21
|
+
time: Date;
|
|
22
|
+
size: number;
|
|
23
|
+
}[];
|