mozhost-cli 2.3.0 → 2.5.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 +15 -0
- package/package.json +7 -6
- package/src/commands/terminal.js +198 -0
- package/src/utils/api.js +9 -0
package/bin/mozhost.js
CHANGED
|
@@ -7,6 +7,7 @@ const containerCommands = require('../src/commands/containers');
|
|
|
7
7
|
const deployCommands = require('../src/commands/deploy');
|
|
8
8
|
const domainCommands = require('../src/commands/domains');
|
|
9
9
|
const databaseCommands = require('../src/commands/databases'); // 👈 NOVO
|
|
10
|
+
const terminalCommands = require('../src/commands/terminal');
|
|
10
11
|
const packageJson = require('../package.json');
|
|
11
12
|
|
|
12
13
|
program
|
|
@@ -171,6 +172,20 @@ program
|
|
|
171
172
|
.action(domainCommands.remove);
|
|
172
173
|
|
|
173
174
|
|
|
175
|
+
// ============================================
|
|
176
|
+
// TERMINAL COMMANDS 👈 ADICIONAR ANTES DO PARSE
|
|
177
|
+
// ============================================
|
|
178
|
+
program
|
|
179
|
+
.command('ssh <container>')
|
|
180
|
+
.description('Acessar terminal do container')
|
|
181
|
+
.action(terminalCommands.ssh);
|
|
182
|
+
|
|
183
|
+
program
|
|
184
|
+
.command('exec <container> <command>')
|
|
185
|
+
.description('Executar comando no container')
|
|
186
|
+
.action(terminalCommands.exec);
|
|
187
|
+
|
|
188
|
+
|
|
174
189
|
// ============================================
|
|
175
190
|
// PARSE & HELP
|
|
176
191
|
// ============================================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mozhost-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Command-line interface for MozHost container platform",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -19,15 +19,16 @@
|
|
|
19
19
|
"author": "Eliobros Tech",
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
|
+
"archiver": "^6.0.1",
|
|
22
23
|
"axios": "^1.6.0",
|
|
23
|
-
"commander": "^11.1.0",
|
|
24
24
|
"chalk": "^4.1.2",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
25
|
+
"commander": "^11.1.0",
|
|
26
|
+
"dotenv": "^16.3.1",
|
|
27
27
|
"form-data": "^4.0.0",
|
|
28
|
-
"archiver": "^6.0.1",
|
|
29
28
|
"fs-extra": "^11.1.1",
|
|
30
|
-
"
|
|
29
|
+
"inquirer": "^8.2.5",
|
|
30
|
+
"ora": "^5.4.1",
|
|
31
|
+
"ws": "^8.18.3"
|
|
31
32
|
},
|
|
32
33
|
"engines": {
|
|
33
34
|
"node": ">=14.0.0"
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const ora = require('ora');
|
|
3
|
+
const WebSocket = require('ws');
|
|
4
|
+
const api = require('../utils/api');
|
|
5
|
+
const config = require('../utils/config');
|
|
6
|
+
|
|
7
|
+
// ============================================
|
|
8
|
+
// HELPER - Resolver Container
|
|
9
|
+
// ============================================
|
|
10
|
+
async function resolveContainer(identifier) {
|
|
11
|
+
try {
|
|
12
|
+
const response = await api.getContainer(identifier);
|
|
13
|
+
return response.container;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (error.response?.status === 404) {
|
|
16
|
+
const listResponse = await api.listContainers();
|
|
17
|
+
const found = listResponse.containers.find(c =>
|
|
18
|
+
c.name === identifier ||
|
|
19
|
+
c.id === identifier ||
|
|
20
|
+
c.id.startsWith(identifier)
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
if (!found) {
|
|
24
|
+
const similar = listResponse.containers.filter(c =>
|
|
25
|
+
c.name.toLowerCase().includes(identifier.toLowerCase()) ||
|
|
26
|
+
c.id.includes(identifier)
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (similar.length > 0) {
|
|
30
|
+
console.log(chalk.yellow('\n💡 Você quis dizer:'));
|
|
31
|
+
similar.forEach(c => {
|
|
32
|
+
console.log(chalk.white(` • ${c.name} ${chalk.gray(`(${c.id.slice(0, 8)})`)}`));
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
throw new Error(`Container "${identifier}" não encontrado`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return found;
|
|
40
|
+
}
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ============================================
|
|
46
|
+
// EXEC - Executar comando remoto
|
|
47
|
+
// ============================================
|
|
48
|
+
async function exec(containerIdentifier, command) {
|
|
49
|
+
try {
|
|
50
|
+
if (!command) {
|
|
51
|
+
console.error(chalk.red('❌ Comando não especificado'));
|
|
52
|
+
console.log(chalk.gray(' Use: mozhost exec <container> "<comando>"'));
|
|
53
|
+
console.log(chalk.gray(' Exemplo: mozhost exec meu-bot "npm install"'));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const spinner = ora('Resolvendo container...').start();
|
|
58
|
+
const container = await resolveContainer(containerIdentifier);
|
|
59
|
+
|
|
60
|
+
if (container.status !== 'running') {
|
|
61
|
+
spinner.fail(chalk.red(`Container está ${container.status}, não rodando`));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
spinner.text = `Executando comando em ${container.name}...`;
|
|
66
|
+
|
|
67
|
+
const response = await api.executeCommand(container.id, command);
|
|
68
|
+
|
|
69
|
+
spinner.stop();
|
|
70
|
+
|
|
71
|
+
console.log(chalk.cyan.bold(`\n📟 Executando em ${container.name}\n`));
|
|
72
|
+
console.log(chalk.gray(`$ ${command}\n`));
|
|
73
|
+
|
|
74
|
+
if (response.output) {
|
|
75
|
+
console.log(response.output);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log();
|
|
79
|
+
|
|
80
|
+
if (response.exitCode === 0) {
|
|
81
|
+
console.log(chalk.green(`✅ Comando executado com sucesso (exit code: ${response.exitCode})`));
|
|
82
|
+
} else {
|
|
83
|
+
console.log(chalk.red(`❌ Comando falhou (exit code: ${response.exitCode})`));
|
|
84
|
+
process.exit(response.exitCode);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(chalk.red('\n❌ Erro ao executar comando:'));
|
|
89
|
+
console.error(chalk.red(` ${error.response?.data?.error || error.message}`));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============================================
|
|
95
|
+
// SSH - Terminal interativo via WebSocket
|
|
96
|
+
// ============================================
|
|
97
|
+
async function ssh(containerIdentifier) {
|
|
98
|
+
try {
|
|
99
|
+
const spinner = ora('Resolvendo container...').start();
|
|
100
|
+
const container = await resolveContainer(containerIdentifier);
|
|
101
|
+
|
|
102
|
+
if (container.status !== 'running') {
|
|
103
|
+
spinner.fail(chalk.red(`Container está ${container.status}, não rodando`));
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const token = await config.getToken();
|
|
108
|
+
if (!token) {
|
|
109
|
+
spinner.fail(chalk.red('Não autenticado'));
|
|
110
|
+
console.log(chalk.gray(' Use: mozhost auth'));
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const apiUrl = await config.getApiUrl();
|
|
115
|
+
const wsUrl = apiUrl
|
|
116
|
+
.replace('https://', 'wss://')
|
|
117
|
+
.replace('http://', 'ws://');
|
|
118
|
+
|
|
119
|
+
spinner.text = `Conectando ao terminal de ${container.name}...`;
|
|
120
|
+
|
|
121
|
+
const ws = new WebSocket(
|
|
122
|
+
`${wsUrl}/api/terminal/${container.id}?token=${token}`
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
ws.on('open', () => {
|
|
126
|
+
spinner.stop();
|
|
127
|
+
console.log(chalk.green(`\n✅ Conectado ao container ${chalk.bold(container.name)}`));
|
|
128
|
+
console.log(chalk.gray(' Digite comandos normalmente. Ctrl+C para sair.\n'));
|
|
129
|
+
|
|
130
|
+
// Configurar stdin para modo raw
|
|
131
|
+
if (process.stdin.isTTY) {
|
|
132
|
+
process.stdin.setRawMode(true);
|
|
133
|
+
process.stdin.resume();
|
|
134
|
+
process.stdin.setEncoding('utf8');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Enviar input do usuário para o WebSocket
|
|
138
|
+
process.stdin.on('data', (key) => {
|
|
139
|
+
// Ctrl+C para sair
|
|
140
|
+
if (key === '\u0003') {
|
|
141
|
+
console.log(chalk.yellow('\n\n👋 Desconectando...'));
|
|
142
|
+
ws.close();
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Enviar tecla para o container
|
|
147
|
+
ws.send(JSON.stringify({
|
|
148
|
+
type: 'input',
|
|
149
|
+
data: key
|
|
150
|
+
}));
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
ws.on('message', (data) => {
|
|
155
|
+
try {
|
|
156
|
+
const message = JSON.parse(data.toString());
|
|
157
|
+
|
|
158
|
+
if (message.type === 'output') {
|
|
159
|
+
process.stdout.write(message.data);
|
|
160
|
+
} else if (message.type === 'error') {
|
|
161
|
+
console.error(chalk.red(`\n❌ ${message.message}`));
|
|
162
|
+
ws.close();
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error(chalk.red('Erro ao processar mensagem:', error.message));
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
ws.on('error', (error) => {
|
|
171
|
+
spinner.stop();
|
|
172
|
+
console.error(chalk.red('\n❌ Erro de conexão:'));
|
|
173
|
+
console.error(chalk.red(` ${error.message}`));
|
|
174
|
+
process.exit(1);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
ws.on('close', () => {
|
|
178
|
+
if (process.stdin.isTTY) {
|
|
179
|
+
process.stdin.setRawMode(false);
|
|
180
|
+
}
|
|
181
|
+
console.log(chalk.yellow('\n\n👋 Conexão encerrada'));
|
|
182
|
+
process.exit(0);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error(chalk.red('\n❌ Erro ao conectar:'));
|
|
187
|
+
console.error(chalk.red(` ${error.message}`));
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ============================================
|
|
193
|
+
// EXPORTS
|
|
194
|
+
// ============================================
|
|
195
|
+
module.exports = {
|
|
196
|
+
exec,
|
|
197
|
+
ssh
|
|
198
|
+
};
|
package/src/utils/api.js
CHANGED
|
@@ -231,6 +231,15 @@ class ApiClient {
|
|
|
231
231
|
const response = await client.delete(`/api/domains/${domainId}`);
|
|
232
232
|
return response.data;
|
|
233
233
|
}
|
|
234
|
+
|
|
235
|
+
// Terminal
|
|
236
|
+
async executeCommand(containerId, command) {
|
|
237
|
+
const client = await this.getClient();
|
|
238
|
+
const response = await client.post(`/api/terminal/${containerId}/exec`, {
|
|
239
|
+
command
|
|
240
|
+
});
|
|
241
|
+
return response.data;
|
|
242
|
+
}
|
|
234
243
|
}
|
|
235
244
|
|
|
236
245
|
module.exports = new ApiClient();
|