genbox 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/dist/api.js +38 -0
- package/dist/commands/balance.js +21 -0
- package/dist/commands/connect.js +96 -0
- package/dist/commands/create.js +219 -0
- package/dist/commands/destroy.js +62 -0
- package/dist/commands/forward.js +166 -0
- package/dist/commands/help.js +404 -0
- package/dist/commands/init.js +488 -0
- package/dist/commands/list.js +36 -0
- package/dist/commands/login.js +56 -0
- package/dist/commands/push.js +191 -0
- package/dist/commands/restore-db.js +273 -0
- package/dist/commands/status.js +294 -0
- package/dist/commands/urls.js +74 -0
- package/dist/config-store.js +65 -0
- package/dist/config.js +162 -0
- package/dist/importer.js +103 -0
- package/dist/index.js +39 -0
- package/dist/scan.js +247 -0
- package/dist/schema.js +2 -0
- package/dist/ssh-config.js +216 -0
- package/package.json +63 -0
package/dist/api.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fetchApi = fetchApi;
|
|
4
|
+
const config_store_1 = require("./config-store");
|
|
5
|
+
const API_URL = process.env.GENBOX_API_URL || 'https://api.genbox.dev';
|
|
6
|
+
async function fetchApi(endpoint, options = {}) {
|
|
7
|
+
const url = `${API_URL}${endpoint}`;
|
|
8
|
+
const token = config_store_1.ConfigStore.getToken();
|
|
9
|
+
const headers = {
|
|
10
|
+
'Content-Type': 'application/json',
|
|
11
|
+
...options.headers,
|
|
12
|
+
};
|
|
13
|
+
if (token) {
|
|
14
|
+
headers['Authorization'] = `Bearer ${token}`;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
// Fallback for prototype (optional, or remove if we want forced auth)
|
|
18
|
+
headers['X-User-ID'] = 'user-1 (prototype)';
|
|
19
|
+
}
|
|
20
|
+
const response = await fetch(url, {
|
|
21
|
+
...options,
|
|
22
|
+
headers,
|
|
23
|
+
});
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
const text = await response.text();
|
|
26
|
+
throw new Error(`API Error (${response.status}): ${text}`);
|
|
27
|
+
}
|
|
28
|
+
// Handle 204 No Content
|
|
29
|
+
if (response.status === 204) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
// Handle empty bodies
|
|
33
|
+
const contentLength = response.headers.get('content-length');
|
|
34
|
+
if (contentLength === '0') {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return response.json();
|
|
38
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
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.balanceCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const api_1 = require("../api");
|
|
10
|
+
exports.balanceCommand = new commander_1.Command('balance')
|
|
11
|
+
.description('Show current credit balance')
|
|
12
|
+
.action(async () => {
|
|
13
|
+
try {
|
|
14
|
+
const user = await (0, api_1.fetchApi)('/users/me');
|
|
15
|
+
console.log(chalk_1.default.bold('User ID:'), user.externalId);
|
|
16
|
+
console.log(chalk_1.default.bold('Credits:'), chalk_1.default.green(user.credits));
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
console.error(chalk_1.default.red('Failed to fetch balance:'), error.message);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
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.connectCommand = void 0;
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const api_1 = require("../api");
|
|
43
|
+
const os = __importStar(require("os"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const child_process_1 = require("child_process");
|
|
47
|
+
function getPrivateSshKey() {
|
|
48
|
+
const home = os.homedir();
|
|
49
|
+
const potentialKeys = [
|
|
50
|
+
path.join(home, '.ssh', 'id_rsa'),
|
|
51
|
+
path.join(home, '.ssh', 'id_ed25519'),
|
|
52
|
+
];
|
|
53
|
+
for (const keyPath of potentialKeys) {
|
|
54
|
+
if (fs.existsSync(keyPath)) {
|
|
55
|
+
return keyPath;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
throw new Error('No SSH private key found in ~/.ssh/');
|
|
59
|
+
}
|
|
60
|
+
exports.connectCommand = new commander_1.Command('connect')
|
|
61
|
+
.description('SSH into a Genbox')
|
|
62
|
+
.argument('<name>', 'Name of the Genbox')
|
|
63
|
+
.action(async (name) => {
|
|
64
|
+
try {
|
|
65
|
+
// 1. Find Genbox
|
|
66
|
+
const genboxes = await (0, api_1.fetchApi)('/genboxes');
|
|
67
|
+
const target = genboxes.find((a) => a.name === name);
|
|
68
|
+
if (!target) {
|
|
69
|
+
console.error(chalk_1.default.red(`Genbox '${name}' not found.`));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!target.ipAddress) {
|
|
73
|
+
console.error(chalk_1.default.yellow(`Genbox '${name}' is still provisioning (no IP). Please wait.`));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
// 2. Get Key
|
|
77
|
+
const keyPath = getPrivateSshKey();
|
|
78
|
+
// 3. Connect
|
|
79
|
+
console.log(chalk_1.default.dim(`Connecting to ${chalk_1.default.bold(name)} (${target.ipAddress})...`));
|
|
80
|
+
const sshArgs = [
|
|
81
|
+
'-i', keyPath,
|
|
82
|
+
'-o', 'StrictHostKeyChecking=no',
|
|
83
|
+
'-o', 'UserKnownHostsFile=/dev/null',
|
|
84
|
+
`dev@${target.ipAddress}`
|
|
85
|
+
];
|
|
86
|
+
const ssh = (0, child_process_1.spawn)('ssh', sshArgs, { stdio: 'inherit' });
|
|
87
|
+
ssh.on('close', (code) => {
|
|
88
|
+
if (code !== 0) {
|
|
89
|
+
console.log(chalk_1.default.dim(`Connection closed with code ${code}`));
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
95
|
+
}
|
|
96
|
+
});
|
|
@@ -0,0 +1,219 @@
|
|
|
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.createCommand = void 0;
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
42
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
43
|
+
const ora_1 = __importDefault(require("ora"));
|
|
44
|
+
const config_1 = require("../config");
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const os = __importStar(require("os"));
|
|
48
|
+
const api_1 = require("../api");
|
|
49
|
+
const ssh_config_1 = require("../ssh-config");
|
|
50
|
+
// Real API Client
|
|
51
|
+
async function provisionGenbox(options) {
|
|
52
|
+
return (0, api_1.fetchApi)('/genboxes', {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
body: JSON.stringify(options),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function getPublicSshKey() {
|
|
58
|
+
const home = os.homedir();
|
|
59
|
+
const potentialKeys = [
|
|
60
|
+
path.join(home, '.ssh', 'id_rsa.pub'),
|
|
61
|
+
path.join(home, '.ssh', 'id_ed25519.pub'),
|
|
62
|
+
];
|
|
63
|
+
for (const keyPath of potentialKeys) {
|
|
64
|
+
if (fs.existsSync(keyPath)) {
|
|
65
|
+
const content = fs.readFileSync(keyPath, 'utf-8').trim();
|
|
66
|
+
if (content)
|
|
67
|
+
return content;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
throw new Error('No public SSH key found in ~/.ssh/ (checked id_rsa.pub and id_ed25519.pub)');
|
|
71
|
+
}
|
|
72
|
+
function getPrivateSshKey() {
|
|
73
|
+
const home = os.homedir();
|
|
74
|
+
const potentialKeys = [
|
|
75
|
+
path.join(home, '.ssh', 'id_rsa'),
|
|
76
|
+
path.join(home, '.ssh', 'id_ed25519'),
|
|
77
|
+
];
|
|
78
|
+
for (const keyPath of potentialKeys) {
|
|
79
|
+
if (fs.existsSync(keyPath)) {
|
|
80
|
+
return keyPath;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
throw new Error('No SSH private key found in ~/.ssh/');
|
|
84
|
+
}
|
|
85
|
+
exports.createCommand = new commander_1.Command('create')
|
|
86
|
+
.description('Create a new Genbox')
|
|
87
|
+
.argument('<name>', 'Name of the Genbox')
|
|
88
|
+
.option('--size <size>', 'Size of the Genbox (small, medium, large, xl)')
|
|
89
|
+
.action(async (name, options) => {
|
|
90
|
+
try {
|
|
91
|
+
const config = (0, config_1.loadConfig)();
|
|
92
|
+
const size = options.size || 'small';
|
|
93
|
+
const publicKey = getPublicSshKey();
|
|
94
|
+
// Check if SSH auth is needed for git repos (only ask if using SSH, not PAT)
|
|
95
|
+
let privateKeyContent;
|
|
96
|
+
const usesSSHAuth = config.git_auth?.method === 'ssh' ||
|
|
97
|
+
(config.repos && Object.values(config.repos).some(r => r.auth === 'ssh'));
|
|
98
|
+
if (usesSSHAuth) {
|
|
99
|
+
const { injectKey } = await inquirer_1.default.prompt([{
|
|
100
|
+
type: 'confirm',
|
|
101
|
+
name: 'injectKey',
|
|
102
|
+
message: 'Inject local SSH Private Key for git cloning?',
|
|
103
|
+
default: true
|
|
104
|
+
}]);
|
|
105
|
+
if (injectKey) {
|
|
106
|
+
const home = os.homedir();
|
|
107
|
+
const privKeyPath = [path.join(home, '.ssh', 'id_rsa'), path.join(home, '.ssh', 'id_ed25519')].find(kv => fs.existsSync(kv));
|
|
108
|
+
if (privKeyPath) {
|
|
109
|
+
privateKeyContent = fs.readFileSync(privKeyPath, 'utf-8');
|
|
110
|
+
console.log(chalk_1.default.dim(` Using private key: ${privKeyPath}`));
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
console.warn(chalk_1.default.yellow('No private key found to inject!'));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const spinner = (0, ora_1.default)(`Creating Genbox '${name}' for project '${config.project_name}'...`).start();
|
|
118
|
+
try {
|
|
119
|
+
// Prepare files bundle
|
|
120
|
+
const filesBundle = [];
|
|
121
|
+
if (config.files) {
|
|
122
|
+
for (const f of config.files) {
|
|
123
|
+
const sourcePath = path.resolve(process.cwd(), f.source);
|
|
124
|
+
if (fs.existsSync(sourcePath)) {
|
|
125
|
+
const content = fs.readFileSync(sourcePath, 'utf-8');
|
|
126
|
+
filesBundle.push({
|
|
127
|
+
path: f.target,
|
|
128
|
+
content: content,
|
|
129
|
+
permissions: f.permissions || '0644'
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
console.warn(chalk_1.default.yellow(`Warning: File ${f.source} not found. Skipping.`));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Scripts handling (simplistic: upload and run)
|
|
138
|
+
const postDetails = [];
|
|
139
|
+
if (config.scripts) {
|
|
140
|
+
for (const s of config.scripts) {
|
|
141
|
+
const scriptName = path.basename(s);
|
|
142
|
+
const targetPath = `/home/dev/${scriptName}`;
|
|
143
|
+
const sourcePath = path.resolve(process.cwd(), s);
|
|
144
|
+
if (fs.existsSync(sourcePath)) {
|
|
145
|
+
const content = fs.readFileSync(sourcePath, 'utf-8');
|
|
146
|
+
filesBundle.push({
|
|
147
|
+
path: targetPath,
|
|
148
|
+
content: content,
|
|
149
|
+
permissions: '0755'
|
|
150
|
+
});
|
|
151
|
+
postDetails.push(`su - dev -c "${targetPath}"`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// project_name from config becomes 'workspace' in the API
|
|
156
|
+
const workspace = config.project_name || 'default';
|
|
157
|
+
// Load environment variables (including GIT_TOKEN for PAT auth)
|
|
158
|
+
const envVars = (0, config_1.loadEnvVars)();
|
|
159
|
+
const gitToken = envVars.GIT_TOKEN;
|
|
160
|
+
const genbox = await provisionGenbox({
|
|
161
|
+
name,
|
|
162
|
+
size,
|
|
163
|
+
publicKey,
|
|
164
|
+
workspace,
|
|
165
|
+
services: config.services,
|
|
166
|
+
files: filesBundle,
|
|
167
|
+
postDetails,
|
|
168
|
+
repos: config.repos,
|
|
169
|
+
privateKey: privateKeyContent,
|
|
170
|
+
gitToken,
|
|
171
|
+
});
|
|
172
|
+
spinner.succeed(chalk_1.default.green(`Genbox '${name}' created successfully!`));
|
|
173
|
+
// Add SSH config entry for easy access
|
|
174
|
+
if (genbox.ipAddress) {
|
|
175
|
+
const sshConfigAdded = (0, ssh_config_1.addSshConfigEntry)({
|
|
176
|
+
name,
|
|
177
|
+
ipAddress: genbox.ipAddress,
|
|
178
|
+
});
|
|
179
|
+
if (sshConfigAdded) {
|
|
180
|
+
console.log(chalk_1.default.dim(` SSH config added: ssh ${(0, ssh_config_1.getSshAlias)(name)}`));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
184
|
+
console.log(` ${chalk_1.default.bold('Environment:')} ${name}`);
|
|
185
|
+
console.log(` ${chalk_1.default.bold('Workspace:')} ${genbox.workspace || workspace}`);
|
|
186
|
+
console.log(` ${chalk_1.default.bold('Size:')} ${genbox.size}`);
|
|
187
|
+
console.log(` ${chalk_1.default.bold('Status:')} ${chalk_1.default.yellow(genbox.status)}`);
|
|
188
|
+
if (genbox.ipAddress) {
|
|
189
|
+
console.log(` ${chalk_1.default.bold('IP:')} ${genbox.ipAddress}`);
|
|
190
|
+
}
|
|
191
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
192
|
+
// Display URLs
|
|
193
|
+
if (genbox.urls && Object.keys(genbox.urls).length > 0) {
|
|
194
|
+
console.log('\n' + chalk_1.default.bold('Access URLs:'));
|
|
195
|
+
for (const [service, url] of Object.entries(genbox.urls)) {
|
|
196
|
+
console.log(` ${chalk_1.default.cyan(service.padEnd(10))} ${url}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
200
|
+
console.log(`\n${chalk_1.default.bold('Quick Commands:')}`);
|
|
201
|
+
console.log(` SSH: ${chalk_1.default.cyan(`genbox connect ${name}`)}`);
|
|
202
|
+
console.log(` Status: ${chalk_1.default.cyan(`genbox status ${name}`)}`);
|
|
203
|
+
console.log(` URLs: ${chalk_1.default.cyan(`genbox urls ${name}`)}`);
|
|
204
|
+
console.log(` Destroy: ${chalk_1.default.cyan(`genbox destroy ${name}`)}`);
|
|
205
|
+
}
|
|
206
|
+
catch (innerError) {
|
|
207
|
+
spinner.fail(chalk_1.default.red(`Failed to create Genbox '${name}': ${innerError.message}`));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
// Handle user cancellation (Ctrl+C) gracefully
|
|
212
|
+
if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
|
|
213
|
+
console.log('');
|
|
214
|
+
console.log(chalk_1.default.dim('Cancelled.'));
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
218
|
+
}
|
|
219
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
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.destroyCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
10
|
+
const ora_1 = __importDefault(require("ora"));
|
|
11
|
+
const api_1 = require("../api");
|
|
12
|
+
const ssh_config_1 = require("../ssh-config");
|
|
13
|
+
exports.destroyCommand = new commander_1.Command('destroy')
|
|
14
|
+
.description('Destroy a Genbox')
|
|
15
|
+
.argument('<name>', 'Name of the Genbox to destroy')
|
|
16
|
+
.option('-y, --yes', 'Skip confirmation')
|
|
17
|
+
.action(async (name, options) => {
|
|
18
|
+
try {
|
|
19
|
+
// 1. Find ID by name
|
|
20
|
+
const genboxes = await (0, api_1.fetchApi)('/genboxes');
|
|
21
|
+
const target = genboxes.find((a) => a.name === name);
|
|
22
|
+
if (!target) {
|
|
23
|
+
console.error(chalk_1.default.red(`Genbox '${name}' not found.`));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// 2. Confirm
|
|
27
|
+
let confirmed = options.yes;
|
|
28
|
+
if (!confirmed) {
|
|
29
|
+
const answer = await inquirer_1.default.prompt([
|
|
30
|
+
{
|
|
31
|
+
type: 'confirm',
|
|
32
|
+
name: 'confirmed',
|
|
33
|
+
message: `Are you sure you want to PERMANENTLY destroy '${name}' (${target.ipAddress})?`,
|
|
34
|
+
default: false,
|
|
35
|
+
},
|
|
36
|
+
]);
|
|
37
|
+
confirmed = answer.confirmed;
|
|
38
|
+
}
|
|
39
|
+
if (!confirmed) {
|
|
40
|
+
console.log('Operation cancelled.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// 3. Delete
|
|
44
|
+
const spinner = (0, ora_1.default)(`Destroying ${name}...`).start();
|
|
45
|
+
await (0, api_1.fetchApi)(`/genboxes/${target._id}`, {
|
|
46
|
+
method: 'DELETE',
|
|
47
|
+
});
|
|
48
|
+
// Remove SSH config entry
|
|
49
|
+
(0, ssh_config_1.removeSshConfigEntry)(name);
|
|
50
|
+
spinner.succeed(chalk_1.default.green(`Genbox '${name}' destroyed successfully.`));
|
|
51
|
+
console.log(chalk_1.default.dim(' SSH config entry removed'));
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// Handle user cancellation (Ctrl+C) gracefully
|
|
55
|
+
if (error.name === 'ExitPromptError' || error.message?.includes('force closed')) {
|
|
56
|
+
console.log('');
|
|
57
|
+
console.log(chalk_1.default.dim('Cancelled.'));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
@@ -0,0 +1,166 @@
|
|
|
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.forwardCommand = void 0;
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const api_1 = require("../api");
|
|
43
|
+
const os = __importStar(require("os"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const child_process_1 = require("child_process");
|
|
47
|
+
const config_1 = require("../config");
|
|
48
|
+
function getPrivateSshKey() {
|
|
49
|
+
const home = os.homedir();
|
|
50
|
+
const potentialKeys = [
|
|
51
|
+
path.join(home, '.ssh', 'id_rsa'),
|
|
52
|
+
path.join(home, '.ssh', 'id_ed25519'),
|
|
53
|
+
];
|
|
54
|
+
for (const keyPath of potentialKeys) {
|
|
55
|
+
if (fs.existsSync(keyPath)) {
|
|
56
|
+
return keyPath;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
throw new Error('No SSH private key found in ~/.ssh/');
|
|
60
|
+
}
|
|
61
|
+
exports.forwardCommand = new commander_1.Command('forward')
|
|
62
|
+
.description('Set up SSH port forwarding to a Genbox for local debugging')
|
|
63
|
+
.argument('<name>', 'Name of the Genbox')
|
|
64
|
+
.option('-p, --ports <ports>', 'Additional ports to forward (comma-separated, e.g., "8080,9000")')
|
|
65
|
+
.action(async (name, options) => {
|
|
66
|
+
try {
|
|
67
|
+
// 1. Find Genbox
|
|
68
|
+
const genboxes = await (0, api_1.fetchApi)('/genboxes');
|
|
69
|
+
const target = genboxes.find((a) => a.name === name);
|
|
70
|
+
if (!target) {
|
|
71
|
+
console.error(chalk_1.default.red(`Genbox '${name}' not found.`));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (!target.ipAddress) {
|
|
75
|
+
console.error(chalk_1.default.yellow(`Genbox '${name}' is still provisioning (no IP). Please wait.`));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// 2. Get SSH key
|
|
79
|
+
let keyPath;
|
|
80
|
+
try {
|
|
81
|
+
keyPath = getPrivateSshKey();
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
console.error(chalk_1.default.red(error.message));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// 3. Build port mappings from config or defaults
|
|
88
|
+
const portMappings = [];
|
|
89
|
+
// Try to load config for service ports
|
|
90
|
+
try {
|
|
91
|
+
const config = (0, config_1.loadConfig)();
|
|
92
|
+
if (config.services) {
|
|
93
|
+
for (const [serviceName, serviceConfig] of Object.entries(config.services)) {
|
|
94
|
+
portMappings.push({
|
|
95
|
+
name: serviceName,
|
|
96
|
+
localPort: serviceConfig.port,
|
|
97
|
+
remotePort: serviceConfig.port,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// No config, use default GoodPass ports
|
|
104
|
+
portMappings.push({ name: 'Web App', localPort: 3000, remotePort: 3000 }, { name: 'Gateway', localPort: 3050, remotePort: 3050 }, { name: 'Auth', localPort: 3051, remotePort: 3051 }, { name: 'Notifications', localPort: 3052, remotePort: 3052 }, { name: 'Products', localPort: 3053, remotePort: 3053 }, { name: 'Partner API', localPort: 3054, remotePort: 3054 }, { name: 'Admin UI', localPort: 5173, remotePort: 5173 });
|
|
105
|
+
}
|
|
106
|
+
// Add custom ports if specified
|
|
107
|
+
if (options.ports) {
|
|
108
|
+
const customPorts = options.ports.split(',').map((p) => parseInt(p.trim(), 10));
|
|
109
|
+
for (const port of customPorts) {
|
|
110
|
+
if (!isNaN(port) && !portMappings.find(m => m.localPort === port)) {
|
|
111
|
+
portMappings.push({
|
|
112
|
+
name: `Custom:${port}`,
|
|
113
|
+
localPort: port,
|
|
114
|
+
remotePort: port,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// 4. Display what will be forwarded
|
|
120
|
+
console.log(chalk_1.default.blue(`[INFO] Setting up port forwarding to ${chalk_1.default.bold(name)}...`));
|
|
121
|
+
console.log('');
|
|
122
|
+
console.log(chalk_1.default.dim('Forwarded ports:'));
|
|
123
|
+
for (const mapping of portMappings) {
|
|
124
|
+
console.log(` ${chalk_1.default.cyan(mapping.name.padEnd(15))} localhost:${mapping.localPort}`);
|
|
125
|
+
}
|
|
126
|
+
console.log('');
|
|
127
|
+
console.log(chalk_1.default.yellow('Press Ctrl+C to stop forwarding'));
|
|
128
|
+
console.log('');
|
|
129
|
+
// 5. Build SSH args with port forwarding
|
|
130
|
+
const sshArgs = [
|
|
131
|
+
'-N', // No command, just forwarding
|
|
132
|
+
'-i', keyPath,
|
|
133
|
+
'-o', 'IdentitiesOnly=yes',
|
|
134
|
+
'-o', 'StrictHostKeyChecking=no',
|
|
135
|
+
'-o', 'UserKnownHostsFile=/dev/null',
|
|
136
|
+
'-o', 'LogLevel=ERROR',
|
|
137
|
+
];
|
|
138
|
+
// Add port forwards
|
|
139
|
+
for (const mapping of portMappings) {
|
|
140
|
+
sshArgs.push('-L', `${mapping.localPort}:localhost:${mapping.remotePort}`);
|
|
141
|
+
}
|
|
142
|
+
sshArgs.push(`dev@${target.ipAddress}`);
|
|
143
|
+
// 6. Start SSH tunnel
|
|
144
|
+
const ssh = (0, child_process_1.spawn)('ssh', sshArgs, { stdio: 'inherit' });
|
|
145
|
+
ssh.on('error', (error) => {
|
|
146
|
+
console.error(chalk_1.default.red(`Failed to start SSH: ${error.message}`));
|
|
147
|
+
});
|
|
148
|
+
ssh.on('close', (code) => {
|
|
149
|
+
if (code !== 0 && code !== null) {
|
|
150
|
+
console.log(chalk_1.default.dim(`Port forwarding ended with code ${code}`));
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.log(chalk_1.default.dim('Port forwarding stopped.'));
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
// Handle SIGINT gracefully
|
|
157
|
+
process.on('SIGINT', () => {
|
|
158
|
+
console.log('');
|
|
159
|
+
console.log(chalk_1.default.dim('Stopping port forwarding...'));
|
|
160
|
+
ssh.kill('SIGINT');
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
165
|
+
}
|
|
166
|
+
});
|