mozhost-cli 2.1.1 → 2.2.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/bin/mozhost.js CHANGED
@@ -5,6 +5,7 @@ const chalk = require('chalk');
5
5
  const authCommands = require('../src/commands/auth');
6
6
  const containerCommands = require('../src/commands/containers');
7
7
  const deployCommands = require('../src/commands/deploy');
8
+ const domainCommands = require('../src/commands/domains');
8
9
  const databaseCommands = require('../src/commands/databases'); // 👈 NOVO
9
10
  const packageJson = require('../package.json');
10
11
 
@@ -142,6 +143,34 @@ program
142
143
  .description('Vincular diretório atual a um container')
143
144
  .action(deployCommands.link);
144
145
 
146
+ program
147
+ .command('domain:list [container]')
148
+ .alias('domain:ls')
149
+ .description('Listar domínios (opcionalmente de um container)')
150
+ .action(domainCommands.list);
151
+
152
+ program
153
+ .command('domain:add <container> <domain>')
154
+ .description('Adicionar domínio customizado')
155
+ .action(domainCommands.add);
156
+
157
+ program
158
+ .command('domain:verify <domain>')
159
+ .description('Verificar status do domínio (DNS e SSL)')
160
+ .action(domainCommands.verify);
161
+
162
+ program
163
+ .command('domain:watch <domain>')
164
+ .description('Monitorar propagação do domínio em tempo real')
165
+ .action(domainCommands.watch);
166
+
167
+ program
168
+ .command('domain:remove <container> <domain>')
169
+ .alias('domain:rm')
170
+ .description('Remover domínio')
171
+ .action(domainCommands.remove);
172
+
173
+
145
174
  // ============================================
146
175
  // PARSE & HELP
147
176
  // ============================================
@@ -159,5 +188,8 @@ if (!process.argv.slice(2).length) {
159
188
  console.log(chalk.white(' mozhost create -n app -t nodejs # Criar container'));
160
189
  console.log(chalk.white(' mozhost db:list # Listar databases'));
161
190
  console.log(chalk.white(' mozhost db:create -n db -t mysql # Criar database'));
191
+ console.log(chalk.white(' mozhost domain:list # Listar domínios'));
192
+ console.log(chalk.white(' mozhost domain:add bot exemplo.com # Adicionar domínio'));
193
+ console.log(chalk.white(' mozhost domain:verify exemplo.com # Verificar domínio'));
162
194
  console.log();
163
195
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mozhost-cli",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "Command-line interface for MozHost container platform",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -0,0 +1,394 @@
1
+ const chalk = require('chalk');
2
+ const ora = require('ora');
3
+ const inquirer = require('inquirer');
4
+ const api = require('../utils/api');
5
+
6
+ // ============================================
7
+ // HELPER FUNCTION - Resolver Container
8
+ // ============================================
9
+ async function resolveContainer(identifier) {
10
+ try {
11
+ const response = await api.getContainer(identifier);
12
+ return response.container;
13
+ } catch (error) {
14
+ if (error.response?.status === 404) {
15
+ const listResponse = await api.listContainers();
16
+ const found = listResponse.containers.find(c =>
17
+ c.name === identifier ||
18
+ c.id === identifier ||
19
+ c.id.startsWith(identifier)
20
+ );
21
+
22
+ if (!found) {
23
+ const similar = listResponse.containers.filter(c =>
24
+ c.name.toLowerCase().includes(identifier.toLowerCase()) ||
25
+ c.id.includes(identifier)
26
+ );
27
+
28
+ if (similar.length > 0) {
29
+ console.log(chalk.yellow('\n💡 Você quis dizer:'));
30
+ similar.forEach(c => {
31
+ console.log(chalk.white(` • ${c.name} ${chalk.gray(`(${c.id.slice(0, 8)})`)}`));
32
+ });
33
+ } else {
34
+ console.log(chalk.gray('\n💡 Use: mozhost ls para ver todos os containers'));
35
+ }
36
+
37
+ throw new Error(`Container "${identifier}" não encontrado`);
38
+ }
39
+
40
+ return found;
41
+ }
42
+ throw error;
43
+ }
44
+ }
45
+
46
+ // ============================================
47
+ // LIST - Listar domínios
48
+ // ============================================
49
+ async function list(containerIdentifier) {
50
+ try {
51
+ const spinner = ora('Carregando domínios...').start();
52
+
53
+ let domains;
54
+ if (containerIdentifier) {
55
+ // Listar domínios de um container específico
56
+ const container = await resolveContainer(containerIdentifier);
57
+ const response = await api.getContainerDomains(container.id);
58
+ domains = response;
59
+ spinner.stop();
60
+
61
+ console.log(chalk.cyan.bold(`\n🌐 Domínios de ${container.name}\n`));
62
+ } else {
63
+ // Listar todos os domínios
64
+ const response = await api.listDomains();
65
+ domains = response;
66
+ spinner.stop();
67
+
68
+ console.log(chalk.cyan.bold(`\n🌐 Domínios (${domains.length})\n`));
69
+ }
70
+
71
+ if (domains.length === 0) {
72
+ console.log(chalk.yellow('⚠️ Nenhum domínio encontrado'));
73
+ console.log(chalk.gray(' Use: mozhost domain:add <container> <dominio>'));
74
+ return;
75
+ }
76
+
77
+ domains.forEach(domain => {
78
+ const statusIcons = {
79
+ active: '✅',
80
+ pending: '⏳',
81
+ dns_pending: '🔍',
82
+ ssl_generating: '🔒',
83
+ failed: '❌'
84
+ };
85
+ const icon = statusIcons[domain.status] || '○';
86
+
87
+ console.log(`${icon} ${chalk.white.bold(domain.domain)}`);
88
+ console.log(` ${chalk.gray('Container:')} ${domain.container_name || domain.container_id}`);
89
+ console.log(` ${chalk.gray('Status:')} ${domain.status}`);
90
+
91
+ if (domain.status === 'active') {
92
+ console.log(` ${chalk.gray('URL:')} ${chalk.cyan(`https://${domain.domain}`)}`);
93
+ }
94
+
95
+ if (domain.ip_detected) {
96
+ console.log(` ${chalk.gray('DNS IP:')} ${domain.ip_detected}`);
97
+ }
98
+
99
+ if (domain.ssl_expires_at) {
100
+ const expiryDate = new Date(domain.ssl_expires_at);
101
+ console.log(` ${chalk.gray('SSL expira:')} ${expiryDate.toLocaleDateString('pt-BR')}`);
102
+ }
103
+
104
+ console.log(` ${chalk.gray('Criado:')} ${new Date(domain.created_at).toLocaleDateString('pt-BR')}`);
105
+ console.log();
106
+ });
107
+
108
+ } catch (error) {
109
+ console.error(chalk.red('\n❌ Erro ao listar domínios:'));
110
+ console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
111
+ process.exit(1);
112
+ }
113
+ }
114
+
115
+ // ============================================
116
+ // ADD - Adicionar domínio
117
+ // ============================================
118
+ async function add(containerIdentifier, domain) {
119
+ try {
120
+ if (!containerIdentifier || !domain) {
121
+ console.error(chalk.red('❌ Container e domínio são obrigatórios'));
122
+ console.log(chalk.gray(' Use: mozhost domain:add <container> <dominio>'));
123
+ process.exit(1);
124
+ }
125
+
126
+ const spinner = ora('Resolvendo container...').start();
127
+ const container = await resolveContainer(containerIdentifier);
128
+
129
+ spinner.text = 'Adicionando domínio...';
130
+
131
+ const response = await api.addDomain({
132
+ containerId: container.id,
133
+ domain: domain
134
+ });
135
+
136
+ spinner.succeed(chalk.green(`✅ Domínio ${domain} adicionado com sucesso!`));
137
+
138
+ console.log(chalk.cyan.bold('\n📝 Configure seu DNS:\n'));
139
+ console.log(chalk.white('┌──────┬──────┬────────────────┐'));
140
+ console.log(chalk.white('│ Tipo │ Host │ Valor │'));
141
+ console.log(chalk.white('├──────┼──────┼────────────────┤'));
142
+ console.log(chalk.white(`│ A │ @ │ ${response.instructions.ip} │`));
143
+ console.log(chalk.white('└──────┴──────┴────────────────┘'));
144
+
145
+ console.log(chalk.gray('\n🔧 Passos:'));
146
+ response.instructions.steps.forEach((step, i) => {
147
+ console.log(chalk.white(` ${i + 1}. ${step}`));
148
+ });
149
+
150
+ console.log(chalk.gray('\n💡 Comandos úteis:'));
151
+ console.log(chalk.white(` • mozhost domain:verify ${domain} - verificar DNS`));
152
+ console.log(chalk.white(` • mozhost domain:watch ${domain} - monitorar propagação`));
153
+
154
+ } catch (error) {
155
+ console.error(chalk.red('\n❌ Erro ao adicionar domínio:'));
156
+
157
+ if (error.response?.data) {
158
+ const err = error.response.data.error;
159
+ console.error(chalk.red(` ${err}`));
160
+
161
+ if (err.includes('já cadastrado')) {
162
+ console.log(chalk.yellow('\n💡 Este domínio já está em uso'));
163
+ } else if (err.includes('inválido')) {
164
+ console.log(chalk.yellow('\n💡 Formato de domínio inválido'));
165
+ console.log(chalk.gray(' Exemplo: exemplo.com ou api.exemplo.com'));
166
+ }
167
+ } else {
168
+ console.error(chalk.red(` ${error.message}`));
169
+ }
170
+
171
+ process.exit(1);
172
+ }
173
+ }
174
+
175
+ // ============================================
176
+ // VERIFY - Verificar domínio
177
+ // ============================================
178
+ async function verify(domain) {
179
+ try {
180
+ if (!domain) {
181
+ console.error(chalk.red('❌ Domínio não especificado'));
182
+ console.log(chalk.gray(' Use: mozhost domain:verify <dominio>'));
183
+ process.exit(1);
184
+ }
185
+
186
+ const spinner = ora(`Verificando ${domain}...`).start();
187
+
188
+ const response = await api.verifyDomain(domain);
189
+ spinner.stop();
190
+
191
+ console.log(chalk.cyan.bold(`\n🔍 Verificação de ${response.domain}\n`));
192
+
193
+ // DNS
194
+ console.log(chalk.white.bold('DNS:'));
195
+ if (response.dns_valid) {
196
+ console.log(`├─ Status: ${chalk.green('✅ Configurado')}`);
197
+ console.log(`├─ IP atual: ${chalk.cyan(response.current_ip)}`);
198
+ console.log(`└─ IP esperado: ${chalk.gray(response.expected_ip)}`);
199
+ } else {
200
+ console.log(`├─ Status: ${chalk.red('❌ Não configurado')}`);
201
+ console.log(`├─ IP esperado: ${chalk.cyan(response.expected_ip)}`);
202
+ console.log(`└─ IP atual: ${chalk.gray(response.current_ip || 'não encontrado')}`);
203
+ }
204
+
205
+ console.log();
206
+
207
+ // SSL
208
+ console.log(chalk.white.bold('SSL:'));
209
+ if (response.ssl_active) {
210
+ const expiryDate = new Date(response.ssl_expires);
211
+ console.log(`├─ Status: ${chalk.green('✅ Ativo')}`);
212
+ console.log(`└─ Expira: ${chalk.gray(expiryDate.toLocaleDateString('pt-BR'))}`);
213
+ } else {
214
+ console.log(`└─ Status: ${chalk.yellow('⏳ Pendente/Gerando...')}`);
215
+ }
216
+
217
+ console.log();
218
+
219
+ // Container
220
+ console.log(chalk.gray(`📦 Container: ${response.container}`));
221
+ console.log(chalk.gray(`📅 Criado: ${new Date(response.created_at).toLocaleString('pt-BR')}`));
222
+
223
+ if (response.verified_at) {
224
+ console.log(chalk.gray(`✅ Verificado: ${new Date(response.verified_at).toLocaleString('pt-BR')}`));
225
+ }
226
+
227
+ console.log();
228
+
229
+ // Status final
230
+ if (response.dns_valid && response.ssl_active) {
231
+ console.log(chalk.green.bold('✅ Domínio totalmente funcional!'));
232
+ console.log(chalk.cyan(` 🌐 https://${response.domain}\n`));
233
+ } else if (response.dns_valid) {
234
+ console.log(chalk.yellow('⚠️ DNS configurado, aguardando SSL...'));
235
+ console.log(chalk.gray(' Pode levar de 2-5 minutos para o SSL ser gerado\n'));
236
+ } else {
237
+ console.log(chalk.red('❌ Configure o DNS primeiro'));
238
+ console.log(chalk.gray(` Aponte o registro A para: ${response.expected_ip}\n`));
239
+ }
240
+
241
+ } catch (error) {
242
+ if (error.response?.status === 404) {
243
+ console.error(chalk.red(`\n❌ Domínio "${domain}" não encontrado`));
244
+ console.log(chalk.gray(' Use: mozhost domain:list para ver seus domínios'));
245
+ } else {
246
+ console.error(chalk.red('\n❌ Erro ao verificar domínio:'));
247
+ console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
248
+ }
249
+ process.exit(1);
250
+ }
251
+ }
252
+
253
+ // ============================================
254
+ // WATCH - Monitorar domínio (polling)
255
+ // ============================================
256
+ async function watch(domain) {
257
+ try {
258
+ if (!domain) {
259
+ console.error(chalk.red('❌ Domínio não especificado'));
260
+ console.log(chalk.gray(' Use: mozhost domain:watch <dominio>'));
261
+ process.exit(1);
262
+ }
263
+
264
+ console.log(chalk.cyan.bold(`\n🔍 Monitorando ${domain}...`));
265
+ console.log(chalk.gray(' Pressione Ctrl+C para sair\n'));
266
+
267
+ let attempt = 0;
268
+ const maxAttempts = 40; // 20 minutos (30s * 40)
269
+
270
+ const checkInterval = setInterval(async () => {
271
+ attempt++;
272
+ const time = new Date().toLocaleTimeString('pt-BR');
273
+
274
+ try {
275
+ const response = await api.verifyDomain(domain);
276
+
277
+ if (response.dns_valid && response.ssl_active) {
278
+ clearInterval(checkInterval);
279
+ console.log(chalk.green(`[${time}] ✅ DNS propagado!`));
280
+ console.log(chalk.green(`[${time}] ✅ SSL configurado!`));
281
+ console.log(chalk.green.bold(`\n🎉 Domínio ativo: https://${domain}\n`));
282
+ process.exit(0);
283
+ } else if (response.dns_valid) {
284
+ console.log(chalk.yellow(`[${time}] ✅ DNS OK | 🔒 Gerando SSL...`));
285
+ } else {
286
+ console.log(chalk.gray(`[${time}] ⏳ Aguardando DNS... (${attempt}/${maxAttempts})`));
287
+ }
288
+
289
+ if (attempt >= maxAttempts) {
290
+ clearInterval(checkInterval);
291
+ console.log(chalk.yellow('\n⏰ Tempo limite atingido'));
292
+ console.log(chalk.gray(' Use: mozhost domain:verify para verificar manualmente'));
293
+ process.exit(0);
294
+ }
295
+
296
+ } catch (error) {
297
+ console.log(chalk.red(`[${time}] ❌ Erro: ${error.message}`));
298
+ }
299
+
300
+ }, 30000); // 30 segundos
301
+
302
+ // Primeira verificação imediata
303
+ try {
304
+ const response = await api.verifyDomain(domain);
305
+ const time = new Date().toLocaleTimeString('pt-BR');
306
+
307
+ if (response.dns_valid && response.ssl_active) {
308
+ clearInterval(checkInterval);
309
+ console.log(chalk.green.bold(`🎉 Domínio já está ativo: https://${domain}\n`));
310
+ process.exit(0);
311
+ } else if (response.dns_valid) {
312
+ console.log(chalk.yellow(`[${time}] ✅ DNS OK | 🔒 Gerando SSL...`));
313
+ } else {
314
+ console.log(chalk.gray(`[${time}] ⏳ Aguardando DNS...`));
315
+ }
316
+ } catch (error) {
317
+ console.error(chalk.red(`❌ ${error.message}`));
318
+ clearInterval(checkInterval);
319
+ process.exit(1);
320
+ }
321
+
322
+ } catch (error) {
323
+ console.error(chalk.red('\n❌ Erro:'), error.message);
324
+ process.exit(1);
325
+ }
326
+ }
327
+
328
+ // ============================================
329
+ // REMOVE - Remover domínio
330
+ // ============================================
331
+ async function remove(containerIdentifier, domain) {
332
+ try {
333
+ if (!containerIdentifier || !domain) {
334
+ console.error(chalk.red('❌ Container e domínio são obrigatórios'));
335
+ console.log(chalk.gray(' Use: mozhost domain:remove <container> <dominio>'));
336
+ process.exit(1);
337
+ }
338
+
339
+ const spinner = ora('Resolvendo container...').start();
340
+ const container = await resolveContainer(containerIdentifier);
341
+
342
+ spinner.text = 'Buscando domínio...';
343
+
344
+ // Buscar domínios do container
345
+ const domains = await api.getContainerDomains(container.id);
346
+ const foundDomain = domains.find(d => d.domain === domain);
347
+
348
+ if (!foundDomain) {
349
+ spinner.fail(chalk.red(`Domínio "${domain}" não encontrado neste container`));
350
+ process.exit(1);
351
+ }
352
+
353
+ spinner.stop();
354
+
355
+ console.log(chalk.yellow('\n⚠️ Remover domínio:'));
356
+ console.log(chalk.white(` ${domain}`));
357
+ console.log(chalk.gray(' O domínio será removido e o SSL será revogado\n'));
358
+
359
+ const confirm = await inquirer.prompt([
360
+ {
361
+ type: 'confirm',
362
+ name: 'confirmed',
363
+ message: 'Tem certeza?',
364
+ default: false
365
+ }
366
+ ]);
367
+
368
+ if (!confirm.confirmed) {
369
+ console.log(chalk.yellow('Operação cancelada'));
370
+ return;
371
+ }
372
+
373
+ const deleteSpinner = ora(`Removendo ${domain}...`).start();
374
+ await api.deleteDomain(foundDomain.id);
375
+
376
+ deleteSpinner.succeed(chalk.green(`✅ Domínio ${domain} removido com sucesso!`));
377
+
378
+ } catch (error) {
379
+ console.error(chalk.red('\n❌ Erro ao remover domínio:'));
380
+ console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
381
+ process.exit(1);
382
+ }
383
+ }
384
+
385
+ // ============================================
386
+ // EXPORTS
387
+ // ============================================
388
+ module.exports = {
389
+ list,
390
+ add,
391
+ verify,
392
+ watch,
393
+ remove
394
+ };
package/src/utils/api.js CHANGED
@@ -9,7 +9,7 @@ class ApiClient {
9
9
 
10
10
  const client = axios.create({
11
11
  baseURL,
12
- timeout: 30000,
12
+ timeout: 160000,
13
13
  headers: {
14
14
  'Content-Type': 'application/json'
15
15
  }
@@ -201,6 +201,37 @@ class ApiClient {
201
201
  return response.data;
202
202
  }
203
203
 
204
- } // 👈 FECHA A CLASSE AQUI (depois de TODOS os métodos)
204
+ }
205
+
206
+ // Domains
207
+ async listDomains() {
208
+ const client = await this.getClient();
209
+ const response = await client.get('/api/domains');
210
+ return response.data;
211
+ }
212
+
213
+ async getContainerDomains(containerId) {
214
+ const client = await this.getClient();
215
+ const response = await client.get(`/api/domains/container/${containerId}`);
216
+ return response.data;
217
+ }
218
+
219
+ async addDomain(data) {
220
+ const client = await this.getClient();
221
+ const response = await client.post('/api/domains', data);
222
+ return response.data;
223
+ }
224
+
225
+ async verifyDomain(domain) {
226
+ const client = await this.getClient();
227
+ const response = await client.get(`/api/domains/verify/${domain}`);
228
+ return response.data;
229
+ }
230
+
231
+ async deleteDomain(domainId) {
232
+ const client = await this.getClient();
233
+ const response = await client.delete(`/api/domains/${domainId}`);
234
+ return response.data;
235
+ }
205
236
 
206
237
  module.exports = new ApiClient();