ante-erp-cli 1.11.30 → 1.11.31
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/bin/ante-cli.js +6 -3
- package/package.json +1 -1
- package/src/commands/database-ports.js +30 -7
- package/src/utils/database-ports.js +172 -50
package/bin/ante-cli.js
CHANGED
|
@@ -241,10 +241,13 @@ dbCmd
|
|
|
241
241
|
dbCmd
|
|
242
242
|
.command('expose')
|
|
243
243
|
.description('Expose database ports for external access (DEVELOPMENT ONLY)')
|
|
244
|
-
.option('--postgres', 'Expose only PostgreSQL
|
|
245
|
-
.option('--redis', 'Expose only Redis
|
|
246
|
-
.option('--mongodb', 'Expose only MongoDB
|
|
244
|
+
.option('--postgres', 'Expose only PostgreSQL')
|
|
245
|
+
.option('--redis', 'Expose only Redis')
|
|
246
|
+
.option('--mongodb', 'Expose only MongoDB')
|
|
247
247
|
.option('--all', 'Expose all databases (default)', true)
|
|
248
|
+
.option('--postgres-port <port>', 'Custom host port for PostgreSQL (default: random)')
|
|
249
|
+
.option('--redis-port <port>', 'Custom host port for Redis (default: random)')
|
|
250
|
+
.option('--mongodb-port <port>', 'Custom host port for MongoDB (default: random)')
|
|
248
251
|
.option('--force', 'Skip confirmation prompts')
|
|
249
252
|
.option('--no-restart', 'Don\'t restart services automatically')
|
|
250
253
|
.action(exposeDbPorts);
|
package/package.json
CHANGED
|
@@ -169,11 +169,14 @@ export async function exposeDbPorts(options) {
|
|
|
169
169
|
);
|
|
170
170
|
|
|
171
171
|
// Add port bindings
|
|
172
|
-
spinner.start('
|
|
173
|
-
const { exposed } = await exposeDatabasePorts(composeFile, {
|
|
172
|
+
spinner.start('Finding available ports and adding bindings...');
|
|
173
|
+
const { exposed, portMappings } = await exposeDatabasePorts(composeFile, {
|
|
174
174
|
postgres: databases.includes('postgres'),
|
|
175
175
|
redis: databases.includes('redis'),
|
|
176
176
|
mongodb: databases.includes('mongodb'),
|
|
177
|
+
postgresPort: options.postgresPort ? parseInt(options.postgresPort) : null,
|
|
178
|
+
redisPort: options.redisPort ? parseInt(options.redisPort) : null,
|
|
179
|
+
mongodbPort: options.mongodbPort ? parseInt(options.mongodbPort) : null,
|
|
177
180
|
});
|
|
178
181
|
spinner.succeed(chalk.green('Port bindings added'));
|
|
179
182
|
|
|
@@ -195,10 +198,26 @@ export async function exposeDbPorts(options) {
|
|
|
195
198
|
console.log(chalk.green.bold('✓ Database ports exposed!'));
|
|
196
199
|
console.log();
|
|
197
200
|
|
|
201
|
+
// Display assigned ports
|
|
202
|
+
console.log(chalk.cyan.bold('Assigned Ports:'));
|
|
203
|
+
Object.entries(portMappings).forEach(([db, mapping]) => {
|
|
204
|
+
const dbInfo = {
|
|
205
|
+
postgres: 'PostgreSQL',
|
|
206
|
+
redis: 'Redis',
|
|
207
|
+
mongodb: 'MongoDB',
|
|
208
|
+
};
|
|
209
|
+
console.log(
|
|
210
|
+
chalk.cyan(
|
|
211
|
+
` ${dbInfo[db]}: Host Port ${chalk.bold(mapping.hostPort)} → Container Port ${mapping.containerPort}`
|
|
212
|
+
)
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
console.log();
|
|
216
|
+
|
|
198
217
|
// Display connection strings
|
|
199
218
|
const serverIP = getServerIP();
|
|
200
219
|
const credentials = getCredentials(installDir);
|
|
201
|
-
const examples = getConnectionExamples(
|
|
220
|
+
const examples = getConnectionExamples(portMappings, serverIP, credentials);
|
|
202
221
|
console.log(chalk.cyan.bold('Connection strings:'));
|
|
203
222
|
Object.values(examples).forEach((ex) => {
|
|
204
223
|
console.log(chalk.cyan(` ${ex.name}: ${ex.example}`));
|
|
@@ -206,7 +225,7 @@ export async function exposeDbPorts(options) {
|
|
|
206
225
|
console.log();
|
|
207
226
|
|
|
208
227
|
// Display firewall configuration guide
|
|
209
|
-
const rules = getFirewallRules(
|
|
228
|
+
const rules = getFirewallRules(portMappings);
|
|
210
229
|
console.log(chalk.yellow.bold('💡 Configure firewall for security:'));
|
|
211
230
|
rules.forEach((rule) => {
|
|
212
231
|
console.log(chalk.yellow(` ${rule}`));
|
|
@@ -250,7 +269,9 @@ export async function secureDbPorts(options) {
|
|
|
250
269
|
console.log(chalk.cyan('Currently exposed database ports:'));
|
|
251
270
|
exposedDbs.forEach((db) => {
|
|
252
271
|
console.log(
|
|
253
|
-
chalk.cyan(
|
|
272
|
+
chalk.cyan(
|
|
273
|
+
` • ${status[db].name}: ${status[db].mapping} (Host: ${status[db].hostPort})`
|
|
274
|
+
)
|
|
254
275
|
);
|
|
255
276
|
});
|
|
256
277
|
console.log();
|
|
@@ -332,12 +353,14 @@ export async function dbPortStatus() {
|
|
|
332
353
|
|
|
333
354
|
let anyExposed = false;
|
|
334
355
|
|
|
335
|
-
Object.entries(status).forEach(([
|
|
356
|
+
Object.entries(status).forEach(([_db, info]) => {
|
|
336
357
|
const statusIcon = info.exposed ? chalk.yellow('🔓') : chalk.green('🔒');
|
|
337
358
|
const statusText = info.exposed
|
|
338
359
|
? chalk.yellow('EXPOSED')
|
|
339
360
|
: chalk.green('SECURED');
|
|
340
|
-
const portText = info.exposed
|
|
361
|
+
const portText = info.exposed
|
|
362
|
+
? chalk.cyan(`Port Mapping: ${info.mapping} (Host: ${info.hostPort})`)
|
|
363
|
+
: '';
|
|
341
364
|
|
|
342
365
|
console.log(
|
|
343
366
|
`${statusIcon} ${chalk.bold(info.name)}: ${statusText} ${portText}`
|
|
@@ -1,7 +1,57 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import yaml from 'js-yaml';
|
|
3
|
-
import
|
|
4
|
-
|
|
3
|
+
import net from 'net';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check if a port is available
|
|
7
|
+
* @param {number} port - Port number to check
|
|
8
|
+
* @returns {Promise<boolean>} True if port is available
|
|
9
|
+
*/
|
|
10
|
+
async function isPortAvailable(port) {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
const server = net.createServer();
|
|
13
|
+
|
|
14
|
+
server.once('error', (err) => {
|
|
15
|
+
if (err.code === 'EADDRINUSE') {
|
|
16
|
+
resolve(false);
|
|
17
|
+
} else {
|
|
18
|
+
resolve(false);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
server.once('listening', () => {
|
|
23
|
+
server.close();
|
|
24
|
+
resolve(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
server.listen(port, '0.0.0.0');
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Find an available port within a range
|
|
33
|
+
* @param {number} preferredPort - Preferred port number (optional)
|
|
34
|
+
* @param {number} rangeStart - Start of port range (default: 49152)
|
|
35
|
+
* @param {number} rangeEnd - End of port range (default: 65535)
|
|
36
|
+
* @param {number} maxAttempts - Maximum number of attempts (default: 100)
|
|
37
|
+
* @returns {Promise<number>} Available port number
|
|
38
|
+
*/
|
|
39
|
+
async function findAvailablePort(preferredPort = null, rangeStart = 49152, rangeEnd = 65535, maxAttempts = 100) {
|
|
40
|
+
// If preferred port is provided and available, use it
|
|
41
|
+
if (preferredPort && await isPortAvailable(preferredPort)) {
|
|
42
|
+
return preferredPort;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Try to find a random available port
|
|
46
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
47
|
+
const randomPort = Math.floor(Math.random() * (rangeEnd - rangeStart + 1)) + rangeStart;
|
|
48
|
+
if (await isPortAvailable(randomPort)) {
|
|
49
|
+
return randomPort;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throw new Error(`Could not find an available port after ${maxAttempts} attempts`);
|
|
54
|
+
}
|
|
5
55
|
|
|
6
56
|
/**
|
|
7
57
|
* Database port configurations
|
|
@@ -10,20 +60,23 @@ const DATABASE_PORTS = {
|
|
|
10
60
|
postgres: {
|
|
11
61
|
name: 'PostgreSQL',
|
|
12
62
|
service: 'postgres',
|
|
13
|
-
|
|
14
|
-
|
|
63
|
+
containerPort: 5432,
|
|
64
|
+
defaultHostPort: 5432,
|
|
65
|
+
connectionExample: (host, hostPort, password) => `postgresql://ante:${password}@${host}:${hostPort}/ante_db`,
|
|
15
66
|
},
|
|
16
67
|
redis: {
|
|
17
68
|
name: 'Redis',
|
|
18
69
|
service: 'redis',
|
|
19
|
-
|
|
20
|
-
|
|
70
|
+
containerPort: 6379,
|
|
71
|
+
defaultHostPort: 6379,
|
|
72
|
+
connectionExample: (host, hostPort, password) => `redis://${host}:${hostPort} (password: ${password})`,
|
|
21
73
|
},
|
|
22
74
|
mongodb: {
|
|
23
75
|
name: 'MongoDB',
|
|
24
76
|
service: 'mongodb',
|
|
25
|
-
|
|
26
|
-
|
|
77
|
+
containerPort: 27017,
|
|
78
|
+
defaultHostPort: 27017,
|
|
79
|
+
connectionExample: (host, hostPort, password) => `mongodb://ante:${password}@${host}:${hostPort}/ante`,
|
|
27
80
|
},
|
|
28
81
|
};
|
|
29
82
|
|
|
@@ -69,10 +122,18 @@ async function writeComposeFile(composeFile, doc) {
|
|
|
69
122
|
* Add port bindings to database services
|
|
70
123
|
* @param {string} composeFile - Path to docker-compose.yml
|
|
71
124
|
* @param {Object} options - Port exposure options
|
|
72
|
-
* @returns {Promise<Object>} Result object with exposed services
|
|
125
|
+
* @returns {Promise<Object>} Result object with exposed services and their ports
|
|
73
126
|
*/
|
|
74
127
|
export async function exposeDatabasePorts(composeFile, options = {}) {
|
|
75
|
-
const {
|
|
128
|
+
const {
|
|
129
|
+
postgres = true,
|
|
130
|
+
redis = true,
|
|
131
|
+
mongodb = true,
|
|
132
|
+
postgresPort = null,
|
|
133
|
+
redisPort = null,
|
|
134
|
+
mongodbPort = null,
|
|
135
|
+
} = options;
|
|
136
|
+
|
|
76
137
|
const doc = await readComposeFile(composeFile);
|
|
77
138
|
|
|
78
139
|
if (!doc.services) {
|
|
@@ -80,6 +141,7 @@ export async function exposeDatabasePorts(composeFile, options = {}) {
|
|
|
80
141
|
}
|
|
81
142
|
|
|
82
143
|
const exposed = [];
|
|
144
|
+
const portMappings = {};
|
|
83
145
|
|
|
84
146
|
// Add PostgreSQL port binding
|
|
85
147
|
if (postgres && doc.services.postgres) {
|
|
@@ -88,10 +150,16 @@ export async function exposeDatabasePorts(composeFile, options = {}) {
|
|
|
88
150
|
}
|
|
89
151
|
// Remove existing port binding if any
|
|
90
152
|
doc.services.postgres.ports = doc.services.postgres.ports.filter(
|
|
91
|
-
(p) => !p.toString().includes(
|
|
153
|
+
(p) => !p.toString().includes(DATABASE_PORTS.postgres.containerPort.toString())
|
|
92
154
|
);
|
|
93
|
-
|
|
155
|
+
|
|
156
|
+
// Find available port
|
|
157
|
+
const hostPort = await findAvailablePort(postgresPort);
|
|
158
|
+
const portMapping = `${hostPort}:${DATABASE_PORTS.postgres.containerPort}`;
|
|
159
|
+
|
|
160
|
+
doc.services.postgres.ports.push(portMapping);
|
|
94
161
|
exposed.push('postgres');
|
|
162
|
+
portMappings.postgres = { hostPort, containerPort: DATABASE_PORTS.postgres.containerPort };
|
|
95
163
|
}
|
|
96
164
|
|
|
97
165
|
// Add Redis port binding
|
|
@@ -101,10 +169,16 @@ export async function exposeDatabasePorts(composeFile, options = {}) {
|
|
|
101
169
|
}
|
|
102
170
|
// Remove existing port binding if any
|
|
103
171
|
doc.services.redis.ports = doc.services.redis.ports.filter(
|
|
104
|
-
(p) => !p.toString().includes(
|
|
172
|
+
(p) => !p.toString().includes(DATABASE_PORTS.redis.containerPort.toString())
|
|
105
173
|
);
|
|
106
|
-
|
|
174
|
+
|
|
175
|
+
// Find available port
|
|
176
|
+
const hostPort = await findAvailablePort(redisPort);
|
|
177
|
+
const portMapping = `${hostPort}:${DATABASE_PORTS.redis.containerPort}`;
|
|
178
|
+
|
|
179
|
+
doc.services.redis.ports.push(portMapping);
|
|
107
180
|
exposed.push('redis');
|
|
181
|
+
portMappings.redis = { hostPort, containerPort: DATABASE_PORTS.redis.containerPort };
|
|
108
182
|
}
|
|
109
183
|
|
|
110
184
|
// Add MongoDB port binding
|
|
@@ -114,15 +188,21 @@ export async function exposeDatabasePorts(composeFile, options = {}) {
|
|
|
114
188
|
}
|
|
115
189
|
// Remove existing port binding if any
|
|
116
190
|
doc.services.mongodb.ports = doc.services.mongodb.ports.filter(
|
|
117
|
-
(p) => !p.toString().includes(
|
|
191
|
+
(p) => !p.toString().includes(DATABASE_PORTS.mongodb.containerPort.toString())
|
|
118
192
|
);
|
|
119
|
-
|
|
193
|
+
|
|
194
|
+
// Find available port
|
|
195
|
+
const hostPort = await findAvailablePort(mongodbPort);
|
|
196
|
+
const portMapping = `${hostPort}:${DATABASE_PORTS.mongodb.containerPort}`;
|
|
197
|
+
|
|
198
|
+
doc.services.mongodb.ports.push(portMapping);
|
|
120
199
|
exposed.push('mongodb');
|
|
200
|
+
portMappings.mongodb = { hostPort, containerPort: DATABASE_PORTS.mongodb.containerPort };
|
|
121
201
|
}
|
|
122
202
|
|
|
123
203
|
await writeComposeFile(composeFile, doc);
|
|
124
204
|
|
|
125
|
-
return { exposed, doc };
|
|
205
|
+
return { exposed, portMappings, doc };
|
|
126
206
|
}
|
|
127
207
|
|
|
128
208
|
/**
|
|
@@ -180,7 +260,7 @@ export async function secureDatabasePorts(composeFile) {
|
|
|
180
260
|
/**
|
|
181
261
|
* Get current port exposure status for database services
|
|
182
262
|
* @param {string} composeFile - Path to docker-compose.yml
|
|
183
|
-
* @returns {Promise<Object>} Status object
|
|
263
|
+
* @returns {Promise<Object>} Status object with actual port mappings
|
|
184
264
|
*/
|
|
185
265
|
export async function getDatabasePortStatus(composeFile) {
|
|
186
266
|
try {
|
|
@@ -194,38 +274,77 @@ export async function getDatabasePortStatus(composeFile) {
|
|
|
194
274
|
|
|
195
275
|
// Check PostgreSQL
|
|
196
276
|
if (doc.services.postgres) {
|
|
197
|
-
const
|
|
198
|
-
p.toString().includes(
|
|
277
|
+
const portBinding = doc.services.postgres.ports?.find((p) =>
|
|
278
|
+
p.toString().includes(`:${DATABASE_PORTS.postgres.containerPort}`)
|
|
199
279
|
);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
280
|
+
if (portBinding) {
|
|
281
|
+
const [hostPort, containerPort] = portBinding.toString().split(':');
|
|
282
|
+
status.postgres = {
|
|
283
|
+
name: DATABASE_PORTS.postgres.name,
|
|
284
|
+
exposed: true,
|
|
285
|
+
hostPort: parseInt(hostPort),
|
|
286
|
+
containerPort: parseInt(containerPort),
|
|
287
|
+
mapping: portBinding.toString(),
|
|
288
|
+
};
|
|
289
|
+
} else {
|
|
290
|
+
status.postgres = {
|
|
291
|
+
name: DATABASE_PORTS.postgres.name,
|
|
292
|
+
exposed: false,
|
|
293
|
+
hostPort: null,
|
|
294
|
+
containerPort: DATABASE_PORTS.postgres.containerPort,
|
|
295
|
+
mapping: null,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
205
298
|
}
|
|
206
299
|
|
|
207
300
|
// Check Redis
|
|
208
301
|
if (doc.services.redis) {
|
|
209
|
-
const
|
|
210
|
-
p.toString().includes(
|
|
302
|
+
const portBinding = doc.services.redis.ports?.find((p) =>
|
|
303
|
+
p.toString().includes(`:${DATABASE_PORTS.redis.containerPort}`)
|
|
211
304
|
);
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
305
|
+
if (portBinding) {
|
|
306
|
+
const [hostPort, containerPort] = portBinding.toString().split(':');
|
|
307
|
+
status.redis = {
|
|
308
|
+
name: DATABASE_PORTS.redis.name,
|
|
309
|
+
exposed: true,
|
|
310
|
+
hostPort: parseInt(hostPort),
|
|
311
|
+
containerPort: parseInt(containerPort),
|
|
312
|
+
mapping: portBinding.toString(),
|
|
313
|
+
};
|
|
314
|
+
} else {
|
|
315
|
+
status.redis = {
|
|
316
|
+
name: DATABASE_PORTS.redis.name,
|
|
317
|
+
exposed: false,
|
|
318
|
+
hostPort: null,
|
|
319
|
+
containerPort: DATABASE_PORTS.redis.containerPort,
|
|
320
|
+
mapping: null,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
217
323
|
}
|
|
218
324
|
|
|
219
325
|
// Check MongoDB
|
|
220
326
|
if (doc.services.mongodb) {
|
|
221
|
-
const
|
|
222
|
-
p.toString().includes(
|
|
327
|
+
const portBinding = doc.services.mongodb.ports?.find((p) =>
|
|
328
|
+
p.toString().includes(`:${DATABASE_PORTS.mongodb.containerPort}`)
|
|
223
329
|
);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
330
|
+
if (portBinding) {
|
|
331
|
+
const [hostPort, containerPort] = portBinding.toString().split(':');
|
|
332
|
+
status.mongodb = {
|
|
333
|
+
name: DATABASE_PORTS.mongodb.name,
|
|
334
|
+
exposed: true,
|
|
335
|
+
hostPort: parseInt(hostPort),
|
|
336
|
+
containerPort: parseInt(containerPort),
|
|
337
|
+
mapping: portBinding.toString(),
|
|
338
|
+
};
|
|
339
|
+
} else {
|
|
340
|
+
status.mongodb = {
|
|
341
|
+
name: DATABASE_PORTS.mongodb.name,
|
|
342
|
+
exposed: false,
|
|
343
|
+
hostPort: null,
|
|
344
|
+
containerPort: DATABASE_PORTS.mongodb.containerPort,
|
|
345
|
+
mapping: null,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
229
348
|
}
|
|
230
349
|
|
|
231
350
|
return status;
|
|
@@ -236,20 +355,23 @@ export async function getDatabasePortStatus(composeFile) {
|
|
|
236
355
|
|
|
237
356
|
/**
|
|
238
357
|
* Get connection examples for exposed databases
|
|
239
|
-
* @param {
|
|
358
|
+
* @param {Object} portMappings - Port mappings object from exposeDatabasePorts
|
|
240
359
|
* @param {string} host - Server hostname or IP
|
|
241
360
|
* @param {Object} credentials - Database credentials { postgres, redis, mongodb }
|
|
242
|
-
* @returns {Object} Connection examples
|
|
361
|
+
* @returns {Object} Connection examples with actual host ports
|
|
243
362
|
*/
|
|
244
|
-
export function getConnectionExamples(
|
|
363
|
+
export function getConnectionExamples(portMappings, host = 'SERVER_IP', credentials = {}) {
|
|
245
364
|
const examples = {};
|
|
246
365
|
|
|
247
|
-
|
|
366
|
+
Object.keys(portMappings).forEach((db) => {
|
|
248
367
|
if (DATABASE_PORTS[db]) {
|
|
249
368
|
const password = credentials[db] || 'PASSWORD';
|
|
369
|
+
const { hostPort } = portMappings[db];
|
|
250
370
|
examples[db] = {
|
|
251
371
|
name: DATABASE_PORTS[db].name,
|
|
252
|
-
|
|
372
|
+
hostPort,
|
|
373
|
+
containerPort: portMappings[db].containerPort,
|
|
374
|
+
example: DATABASE_PORTS[db].connectionExample(host, hostPort, password),
|
|
253
375
|
};
|
|
254
376
|
}
|
|
255
377
|
});
|
|
@@ -259,16 +381,16 @@ export function getConnectionExamples(exposed, host = 'SERVER_IP', credentials =
|
|
|
259
381
|
|
|
260
382
|
/**
|
|
261
383
|
* Get firewall rules for database ports
|
|
262
|
-
* @param {
|
|
263
|
-
* @returns {Array<string>} UFW command examples
|
|
384
|
+
* @param {Object} portMappings - Port mappings object from exposeDatabasePorts
|
|
385
|
+
* @returns {Array<string>} UFW command examples with actual host ports
|
|
264
386
|
*/
|
|
265
|
-
export function getFirewallRules(
|
|
387
|
+
export function getFirewallRules(portMappings) {
|
|
266
388
|
const rules = [];
|
|
267
389
|
|
|
268
|
-
|
|
390
|
+
Object.keys(portMappings).forEach((db) => {
|
|
269
391
|
if (DATABASE_PORTS[db]) {
|
|
270
|
-
const
|
|
271
|
-
rules.push(`ufw allow from YOUR_IP to any port ${
|
|
392
|
+
const { hostPort } = portMappings[db];
|
|
393
|
+
rules.push(`ufw allow from YOUR_IP to any port ${hostPort}`);
|
|
272
394
|
}
|
|
273
395
|
});
|
|
274
396
|
|