ante-erp-cli 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/.eslintrc.json +15 -0
- package/CHANGELOG.md +189 -0
- package/IMPLEMENTATION-SUMMARY.md +358 -0
- package/LICENSE +22 -0
- package/PUBLISHING-GUIDE.md +217 -0
- package/README.md +307 -0
- package/bin/ante-cli.js +190 -0
- package/jest.config.js +24 -0
- package/package.json +67 -0
- package/src/commands/backup.js +127 -0
- package/src/commands/database.js +324 -0
- package/src/commands/doctor.js +87 -0
- package/src/commands/install.js +277 -0
- package/src/commands/logs.js +33 -0
- package/src/commands/restore.js +227 -0
- package/src/commands/service.js +72 -0
- package/src/commands/status.js +69 -0
- package/src/commands/uninstall.js +94 -0
- package/src/commands/update.js +75 -0
- package/src/templates/docker-compose.yml.js +193 -0
- package/src/templates/env.js +89 -0
- package/src/utils/config.js +93 -0
- package/src/utils/docker.js +226 -0
- package/src/utils/password.js +35 -0
- package/src/utils/validation.js +202 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import boxen from 'boxen';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { Listr } from 'listr2';
|
|
6
|
+
import { mkdirSync, writeFileSync, existsSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { runSystemChecks } from '../utils/validation.js';
|
|
9
|
+
import { generateCredentials } from '../utils/password.js';
|
|
10
|
+
import { saveInstallConfig, detectInstallation } from '../utils/config.js';
|
|
11
|
+
import { pullImages, startServices, waitForServiceHealthy } from '../utils/docker.js';
|
|
12
|
+
import { generateDockerCompose } from '../templates/docker-compose.yml.js';
|
|
13
|
+
import { generateEnv } from '../templates/env.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Show welcome message
|
|
17
|
+
*/
|
|
18
|
+
function showWelcome() {
|
|
19
|
+
console.log(
|
|
20
|
+
boxen(
|
|
21
|
+
chalk.bold.cyan('ANTE ERP') + '\n' +
|
|
22
|
+
chalk.white('Self-Hosted Installation Tool\n') +
|
|
23
|
+
chalk.gray(`Version ${chalk.white('1.0.0')}`),
|
|
24
|
+
{
|
|
25
|
+
padding: 1,
|
|
26
|
+
margin: 1,
|
|
27
|
+
borderStyle: 'round',
|
|
28
|
+
borderColor: 'cyan'
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Show success message
|
|
36
|
+
*/
|
|
37
|
+
function showSuccess(installDir, credentials) {
|
|
38
|
+
console.log(
|
|
39
|
+
boxen(
|
|
40
|
+
chalk.green.bold('ā Installation Complete!') + '\n\n' +
|
|
41
|
+
chalk.white('Access Information:') + '\n' +
|
|
42
|
+
chalk.gray('ā'.repeat(40)) + '\n' +
|
|
43
|
+
chalk.cyan('Frontend: ') + chalk.white('http://localhost:8080') + '\n' +
|
|
44
|
+
chalk.cyan('Backend: ') + chalk.white('http://localhost:3001') + '\n' +
|
|
45
|
+
chalk.cyan('WebSocket: ') + chalk.white('ws://localhost:4001') + '\n\n' +
|
|
46
|
+
chalk.yellow('ā IMPORTANT:') + '\n' +
|
|
47
|
+
chalk.white(`Credentials saved to:\n${installDir}/installation-credentials.txt`) + '\n' +
|
|
48
|
+
chalk.gray('Save this file securely!') + '\n\n' +
|
|
49
|
+
chalk.white('Next steps:') + '\n' +
|
|
50
|
+
chalk.gray('1. Open http://localhost:8080 in your browser') + '\n' +
|
|
51
|
+
chalk.gray('2. Create your admin account') + '\n' +
|
|
52
|
+
chalk.gray('3. Explore the documentation at https://docs.ante.ph'),
|
|
53
|
+
{
|
|
54
|
+
padding: 1,
|
|
55
|
+
margin: 1,
|
|
56
|
+
borderStyle: 'round',
|
|
57
|
+
borderColor: 'green'
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Install ANTE ERP
|
|
65
|
+
*/
|
|
66
|
+
export async function install(options) {
|
|
67
|
+
try {
|
|
68
|
+
showWelcome();
|
|
69
|
+
|
|
70
|
+
// Check if already installed
|
|
71
|
+
const existing = detectInstallation();
|
|
72
|
+
if (existing && !options.force) {
|
|
73
|
+
console.log(chalk.yellow('\nā ANTE is already installed at:'), chalk.white(existing));
|
|
74
|
+
const { continueAnyway } = await inquirer.prompt([
|
|
75
|
+
{
|
|
76
|
+
type: 'confirm',
|
|
77
|
+
name: 'continueAnyway',
|
|
78
|
+
message: 'Install anyway?',
|
|
79
|
+
default: false
|
|
80
|
+
}
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
if (!continueAnyway) {
|
|
84
|
+
console.log(chalk.gray('\nInstallation cancelled.'));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// System checks
|
|
90
|
+
console.log(chalk.bold('\nš Checking system requirements...\n'));
|
|
91
|
+
const { ok, checks } = await runSystemChecks();
|
|
92
|
+
|
|
93
|
+
// Display check results
|
|
94
|
+
console.log(chalk.bold('System Requirements:'));
|
|
95
|
+
console.log(`${checks.docker.ok ? chalk.green('ā') : chalk.red('ā')} Docker: ${checks.docker.message}`);
|
|
96
|
+
console.log(`${checks.dockerCompose.ok ? chalk.green('ā') : chalk.red('ā')} Docker Compose: ${checks.dockerCompose.message}`);
|
|
97
|
+
console.log(`${checks.node.ok ? chalk.green('ā') : chalk.red('ā')} Node.js: ${checks.node.message}`);
|
|
98
|
+
|
|
99
|
+
console.log(chalk.bold('\nResources:'));
|
|
100
|
+
console.log(`${checks.diskSpace.ok ? chalk.green('ā') : chalk.red('ā')} Disk Space: ${checks.diskSpace.message}`);
|
|
101
|
+
console.log(`${checks.memory.ok ? chalk.green('ā') : chalk.red('ā')} Memory: ${checks.memory.message}`);
|
|
102
|
+
console.log(`${checks.cpu.ok ? chalk.green('ā') : chalk.yellow('ā ')} CPU: ${checks.cpu.message}`);
|
|
103
|
+
|
|
104
|
+
if (!ok) {
|
|
105
|
+
console.log(chalk.red('\nā System requirements not met. Please resolve issues above.\n'));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log(chalk.green('\nā All requirements met!\n'));
|
|
110
|
+
|
|
111
|
+
// Interactive prompts or use options
|
|
112
|
+
let config;
|
|
113
|
+
if (options.interactive) {
|
|
114
|
+
config = await inquirer.prompt([
|
|
115
|
+
{
|
|
116
|
+
type: 'input',
|
|
117
|
+
name: 'installDir',
|
|
118
|
+
message: 'Installation directory:',
|
|
119
|
+
default: options.dir || './ante-erp'
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
type: 'list',
|
|
123
|
+
name: 'preset',
|
|
124
|
+
message: 'Choose installation type:',
|
|
125
|
+
choices: [
|
|
126
|
+
{ name: 'Minimal (Evaluation)', value: 'minimal' },
|
|
127
|
+
{ name: 'Standard (Recommended)', value: 'standard' },
|
|
128
|
+
{ name: 'Enterprise (Full Features)', value: 'enterprise' }
|
|
129
|
+
],
|
|
130
|
+
default: 'standard'
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
type: 'confirm',
|
|
134
|
+
name: 'useDomain',
|
|
135
|
+
message: 'Do you have a domain name?',
|
|
136
|
+
default: false
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: 'input',
|
|
140
|
+
name: 'domain',
|
|
141
|
+
message: 'Domain name:',
|
|
142
|
+
when: (answers) => answers.useDomain
|
|
143
|
+
}
|
|
144
|
+
]);
|
|
145
|
+
} else {
|
|
146
|
+
config = {
|
|
147
|
+
installDir: options.dir,
|
|
148
|
+
preset: options.preset || 'standard',
|
|
149
|
+
useDomain: !!options.domain,
|
|
150
|
+
domain: options.domain
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Generate credentials
|
|
155
|
+
console.log(chalk.bold('\nš Generating secure credentials...\n'));
|
|
156
|
+
const credentials = generateCredentials();
|
|
157
|
+
|
|
158
|
+
// Installation tasks
|
|
159
|
+
const tasks = new Listr([
|
|
160
|
+
{
|
|
161
|
+
title: 'Creating installation directory',
|
|
162
|
+
task: () => {
|
|
163
|
+
mkdirSync(config.installDir, { recursive: true });
|
|
164
|
+
mkdirSync(join(config.installDir, 'backups'), { recursive: true });
|
|
165
|
+
mkdirSync(join(config.installDir, 'logs'), { recursive: true });
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
title: 'Generating configuration files',
|
|
170
|
+
task: () => {
|
|
171
|
+
const dockerCompose = generateDockerCompose({
|
|
172
|
+
frontendPort: parseInt(options.port) || 8080,
|
|
173
|
+
backendPort: 3001,
|
|
174
|
+
websocketPort: 4001
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const envContent = generateEnv(credentials, {
|
|
178
|
+
frontendUrl: config.domain ? `https://${config.domain}` : 'http://localhost:8080',
|
|
179
|
+
apiUrl: config.domain ? `https://${config.domain}/api` : 'http://localhost:3001',
|
|
180
|
+
socketUrl: config.domain ? `wss://${config.domain}/socket.io` : 'ws://localhost:4001'
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
writeFileSync(join(config.installDir, 'docker-compose.yml'), dockerCompose);
|
|
184
|
+
writeFileSync(join(config.installDir, '.env'), envContent);
|
|
185
|
+
|
|
186
|
+
// Save credentials
|
|
187
|
+
const credentialsText = `ANTE ERP Installation Credentials
|
|
188
|
+
Generated: ${new Date().toISOString()}
|
|
189
|
+
|
|
190
|
+
ā ļø IMPORTANT: Keep this file secure and never commit to version control!
|
|
191
|
+
|
|
192
|
+
Database Credentials:
|
|
193
|
+
āāāāāāāāāāāāāāāāāāāā
|
|
194
|
+
PostgreSQL Password: ${credentials.dbPassword}
|
|
195
|
+
Redis Password: ${credentials.redisPassword}
|
|
196
|
+
MongoDB Password: ${credentials.mongoPassword}
|
|
197
|
+
|
|
198
|
+
Security Keys:
|
|
199
|
+
āāāāāāāāāāāāāā
|
|
200
|
+
JWT Secret: ${credentials.jwtSecret}
|
|
201
|
+
Developer Key: ${credentials.developerKey}
|
|
202
|
+
Encryption Key: ${credentials.encryptionKey}
|
|
203
|
+
|
|
204
|
+
Access Information:
|
|
205
|
+
āāāāāāāāāāāāāāāāāāā
|
|
206
|
+
Frontend: http://localhost:${options.port || 8080}
|
|
207
|
+
Backend: http://localhost:3001
|
|
208
|
+
WebSocket: ws://localhost:4001
|
|
209
|
+
|
|
210
|
+
Next Steps:
|
|
211
|
+
āāāāāāāāāā
|
|
212
|
+
1. Access the frontend URL in your browser
|
|
213
|
+
2. Create your admin account
|
|
214
|
+
3. Configure your organization settings
|
|
215
|
+
4. Start using ANTE ERP!
|
|
216
|
+
|
|
217
|
+
Documentation: https://docs.ante.ph
|
|
218
|
+
Support: support@ante.ph
|
|
219
|
+
`;
|
|
220
|
+
|
|
221
|
+
writeFileSync(join(config.installDir, 'installation-credentials.txt'), credentialsText);
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
title: 'Pulling Docker images',
|
|
226
|
+
task: async () => {
|
|
227
|
+
const composeFile = join(config.installDir, 'docker-compose.yml');
|
|
228
|
+
await pullImages(composeFile);
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
title: 'Starting services',
|
|
233
|
+
task: async () => {
|
|
234
|
+
const composeFile = join(config.installDir, 'docker-compose.yml');
|
|
235
|
+
await startServices(composeFile);
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
title: 'Waiting for services to be ready',
|
|
240
|
+
task: async () => {
|
|
241
|
+
const composeFile = join(config.installDir, 'docker-compose.yml');
|
|
242
|
+
|
|
243
|
+
// Wait for backend to be healthy
|
|
244
|
+
const backendHealthy = await waitForServiceHealthy(composeFile, 'backend', 120);
|
|
245
|
+
if (!backendHealthy) {
|
|
246
|
+
throw new Error('Backend service failed to start');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Give it a few more seconds
|
|
250
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
], {
|
|
254
|
+
rendererOptions: {
|
|
255
|
+
collapseSubtasks: false
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
await tasks.run();
|
|
260
|
+
|
|
261
|
+
// Save installation configuration
|
|
262
|
+
saveInstallConfig({
|
|
263
|
+
installPath: config.installDir,
|
|
264
|
+
version: '1.0.0',
|
|
265
|
+
domain: config.domain
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Show success message
|
|
269
|
+
showSuccess(config.installDir, credentials);
|
|
270
|
+
|
|
271
|
+
} catch (error) {
|
|
272
|
+
console.error(chalk.red('\nā Installation failed:'), error.message);
|
|
273
|
+
console.error(chalk.gray('\nFor help, visit: https://docs.ante.ph/self-hosting/troubleshooting\n'));
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { getInstallDir } from '../utils/config.js';
|
|
4
|
+
import { getLogs } from '../utils/docker.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* View ANTE logs
|
|
8
|
+
*/
|
|
9
|
+
export async function logs(options) {
|
|
10
|
+
try {
|
|
11
|
+
const installDir = getInstallDir();
|
|
12
|
+
const composeFile = join(installDir, 'docker-compose.yml');
|
|
13
|
+
|
|
14
|
+
console.log(chalk.bold(`\nš ANTE Logs${options.service ? ` (${options.service})` : ''}\n`));
|
|
15
|
+
|
|
16
|
+
const logOptions = {
|
|
17
|
+
tail: options.lines ? parseInt(options.lines) : 100,
|
|
18
|
+
follow: options.follow,
|
|
19
|
+
since: options.since
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const logOutput = await getLogs(composeFile, options.service, logOptions);
|
|
23
|
+
|
|
24
|
+
if (!options.follow && logOutput) {
|
|
25
|
+
console.log(logOutput);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error(chalk.red('Error:'), error.message);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import { join, basename } from 'path';
|
|
5
|
+
import { existsSync, mkdirSync, readdirSync } from 'fs';
|
|
6
|
+
import { execa } from 'execa';
|
|
7
|
+
import { getInstallDir } from '../utils/config.js';
|
|
8
|
+
import { execInContainer, stopServices, startServices } from '../utils/docker.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Restore ANTE ERP from backup
|
|
12
|
+
*/
|
|
13
|
+
export async function restore(backupFile, options = {}) {
|
|
14
|
+
try {
|
|
15
|
+
const installDir = getInstallDir();
|
|
16
|
+
const composeFile = join(installDir, 'docker-compose.yml');
|
|
17
|
+
|
|
18
|
+
// Validate backup file
|
|
19
|
+
if (!backupFile) {
|
|
20
|
+
const backupDir = join(installDir, 'backups');
|
|
21
|
+
|
|
22
|
+
if (!existsSync(backupDir)) {
|
|
23
|
+
console.log(chalk.red('ā No backups directory found'));
|
|
24
|
+
console.log(chalk.gray(`Expected: ${backupDir}`));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const backups = readdirSync(backupDir)
|
|
29
|
+
.filter(f => f.endsWith('.tar.gz'))
|
|
30
|
+
.sort()
|
|
31
|
+
.reverse();
|
|
32
|
+
|
|
33
|
+
if (backups.length === 0) {
|
|
34
|
+
console.log(chalk.red('ā No backups found'));
|
|
35
|
+
console.log(chalk.gray('Create a backup first: ante backup'));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Prompt user to select backup
|
|
40
|
+
const { selectedBackup } = await inquirer.prompt([
|
|
41
|
+
{
|
|
42
|
+
type: 'list',
|
|
43
|
+
name: 'selectedBackup',
|
|
44
|
+
message: 'Select backup to restore:',
|
|
45
|
+
choices: backups.map(b => ({
|
|
46
|
+
name: `${b} (${getSizeSync(join(backupDir, b))})`,
|
|
47
|
+
value: b
|
|
48
|
+
}))
|
|
49
|
+
}
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
backupFile = join(backupDir, selectedBackup);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!existsSync(backupFile)) {
|
|
56
|
+
console.log(chalk.red('ā Backup file not found:'), backupFile);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Warning and confirmation
|
|
61
|
+
console.log(chalk.yellow('\nā WARNING: This will restore ANTE from backup'));
|
|
62
|
+
console.log(chalk.gray('Current data will be replaced with backup data'));
|
|
63
|
+
console.log(chalk.white('\nBackup file:'), chalk.cyan(basename(backupFile)));
|
|
64
|
+
|
|
65
|
+
if (!options.force) {
|
|
66
|
+
const { confirm } = await inquirer.prompt([
|
|
67
|
+
{
|
|
68
|
+
type: 'confirm',
|
|
69
|
+
name: 'confirm',
|
|
70
|
+
message: 'Continue with restore?',
|
|
71
|
+
default: false
|
|
72
|
+
}
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
if (!confirm) {
|
|
76
|
+
console.log(chalk.gray('\nRestore cancelled.'));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const spinner = ora('Preparing restore...').start();
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// Create temporary directory
|
|
85
|
+
const tempDir = join(installDir, '.restore-temp');
|
|
86
|
+
if (existsSync(tempDir)) {
|
|
87
|
+
await execa('rm', ['-rf', tempDir]);
|
|
88
|
+
}
|
|
89
|
+
mkdirSync(tempDir, { recursive: true });
|
|
90
|
+
|
|
91
|
+
// Extract backup
|
|
92
|
+
spinner.text = 'Extracting backup...';
|
|
93
|
+
await execa('tar', ['-xzf', backupFile, '-C', tempDir]);
|
|
94
|
+
|
|
95
|
+
// Stop services
|
|
96
|
+
spinner.text = 'Stopping services...';
|
|
97
|
+
await stopServices(composeFile);
|
|
98
|
+
|
|
99
|
+
// Wait for services to stop
|
|
100
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
101
|
+
|
|
102
|
+
// Start only database services for restore
|
|
103
|
+
spinner.text = 'Starting database services...';
|
|
104
|
+
await startServices(composeFile, ['postgres', 'mongodb', 'redis']);
|
|
105
|
+
|
|
106
|
+
// Wait for databases to be ready
|
|
107
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
108
|
+
|
|
109
|
+
// Restore PostgreSQL
|
|
110
|
+
spinner.text = 'Restoring PostgreSQL database...';
|
|
111
|
+
const pgDumpFile = join(tempDir, 'postgres.dump');
|
|
112
|
+
if (existsSync(pgDumpFile)) {
|
|
113
|
+
// Drop and recreate database
|
|
114
|
+
await execInContainer(
|
|
115
|
+
composeFile,
|
|
116
|
+
'postgres',
|
|
117
|
+
['psql', '-U', 'ante', '-c', 'DROP DATABASE IF EXISTS ante_db;']
|
|
118
|
+
);
|
|
119
|
+
await execInContainer(
|
|
120
|
+
composeFile,
|
|
121
|
+
'postgres',
|
|
122
|
+
['psql', '-U', 'ante', '-c', 'CREATE DATABASE ante_db;']
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Restore from dump
|
|
126
|
+
const dumpContent = await execa('cat', [pgDumpFile]);
|
|
127
|
+
await execInContainer(
|
|
128
|
+
composeFile,
|
|
129
|
+
'postgres',
|
|
130
|
+
['pg_restore', '-U', 'ante', '-d', 'ante_db'],
|
|
131
|
+
{ input: dumpContent.stdout }
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Restore MongoDB
|
|
136
|
+
spinner.text = 'Restoring MongoDB database...';
|
|
137
|
+
const mongoDumpDir = join(tempDir, 'mongodb');
|
|
138
|
+
if (existsSync(mongoDumpDir)) {
|
|
139
|
+
await execa('docker', [
|
|
140
|
+
'cp',
|
|
141
|
+
mongoDumpDir,
|
|
142
|
+
'ante-mongodb:/tmp/restore'
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
await execInContainer(
|
|
146
|
+
composeFile,
|
|
147
|
+
'mongodb',
|
|
148
|
+
['mongorestore', '--drop', '--db', 'ante', '/tmp/restore/ante']
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Restore uploaded files
|
|
153
|
+
spinner.text = 'Restoring uploaded files...';
|
|
154
|
+
const uploadsDir = join(tempDir, 'uploads');
|
|
155
|
+
if (existsSync(uploadsDir)) {
|
|
156
|
+
await execa('docker', [
|
|
157
|
+
'cp',
|
|
158
|
+
uploadsDir,
|
|
159
|
+
'ante-backend:/app/'
|
|
160
|
+
]);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Restore configuration files
|
|
164
|
+
spinner.text = 'Restoring configuration...';
|
|
165
|
+
const envBackup = join(tempDir, 'env.backup');
|
|
166
|
+
if (existsSync(envBackup)) {
|
|
167
|
+
await execa('cp', [envBackup, join(installDir, '.env')]);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const composeBackup = join(tempDir, 'docker-compose.yml');
|
|
171
|
+
if (existsSync(composeBackup)) {
|
|
172
|
+
await execa('cp', [composeBackup, composeFile]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Start all services
|
|
176
|
+
spinner.text = 'Starting all services...';
|
|
177
|
+
await startServices(composeFile);
|
|
178
|
+
|
|
179
|
+
// Cleanup
|
|
180
|
+
spinner.text = 'Cleaning up...';
|
|
181
|
+
await execa('rm', ['-rf', tempDir]);
|
|
182
|
+
|
|
183
|
+
spinner.succeed(chalk.green('Restore completed successfully!'));
|
|
184
|
+
|
|
185
|
+
console.log(chalk.white('\nā PostgreSQL database restored'));
|
|
186
|
+
console.log(chalk.white('ā MongoDB database restored'));
|
|
187
|
+
console.log(chalk.white('ā Uploaded files restored'));
|
|
188
|
+
console.log(chalk.white('ā Configuration restored'));
|
|
189
|
+
console.log(chalk.white('ā All services started'));
|
|
190
|
+
|
|
191
|
+
console.log(chalk.cyan('\nANTE is now running with restored data'));
|
|
192
|
+
|
|
193
|
+
} catch (error) {
|
|
194
|
+
spinner.fail(chalk.red('Restore failed'));
|
|
195
|
+
console.error(chalk.red('\nError:'), error.message);
|
|
196
|
+
|
|
197
|
+
// Try to start services anyway
|
|
198
|
+
try {
|
|
199
|
+
await startServices(composeFile);
|
|
200
|
+
} catch (startError) {
|
|
201
|
+
console.error(chalk.red('Failed to start services:'), startError.message);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error(chalk.red('\nRestore failed:'), error.message);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Get file size in human-readable format
|
|
215
|
+
*/
|
|
216
|
+
function getSizeSync(filePath) {
|
|
217
|
+
try {
|
|
218
|
+
const stats = require('fs').statSync(filePath);
|
|
219
|
+
const bytes = stats.size;
|
|
220
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
221
|
+
if (bytes === 0) return '0 B';
|
|
222
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
223
|
+
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
|
|
224
|
+
} catch (error) {
|
|
225
|
+
return 'Unknown';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { getInstallDir } from '../utils/config.js';
|
|
5
|
+
import { startServices, stopServices, restartServices } from '../utils/docker.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Start ANTE services
|
|
9
|
+
*/
|
|
10
|
+
export async function start(options) {
|
|
11
|
+
try {
|
|
12
|
+
const installDir = getInstallDir();
|
|
13
|
+
const composeFile = join(installDir, 'docker-compose.yml');
|
|
14
|
+
|
|
15
|
+
const spinner = ora('Starting ANTE services...').start();
|
|
16
|
+
|
|
17
|
+
const services = options.service ? [options.service] : [];
|
|
18
|
+
await startServices(composeFile, services);
|
|
19
|
+
|
|
20
|
+
spinner.succeed(services.length ? `${options.service} started` : 'All services started');
|
|
21
|
+
console.log(chalk.gray('\nRun "ante status" to check service status\n'));
|
|
22
|
+
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error(chalk.red('\nError:'), error.message);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Stop ANTE services
|
|
31
|
+
*/
|
|
32
|
+
export async function stop(options) {
|
|
33
|
+
try {
|
|
34
|
+
const installDir = getInstallDir();
|
|
35
|
+
const composeFile = join(installDir, 'docker-compose.yml');
|
|
36
|
+
|
|
37
|
+
const spinner = ora('Stopping ANTE services...').start();
|
|
38
|
+
|
|
39
|
+
const services = options.service ? [options.service] : [];
|
|
40
|
+
await stopServices(composeFile, services);
|
|
41
|
+
|
|
42
|
+
spinner.succeed(services.length ? `${options.service} stopped` : 'All services stopped');
|
|
43
|
+
console.log();
|
|
44
|
+
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(chalk.red('\nError:'), error.message);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Restart ANTE services
|
|
53
|
+
*/
|
|
54
|
+
export async function restart(options) {
|
|
55
|
+
try {
|
|
56
|
+
const installDir = getInstallDir();
|
|
57
|
+
const composeFile = join(installDir, 'docker-compose.yml');
|
|
58
|
+
|
|
59
|
+
const spinner = ora('Restarting ANTE services...').start();
|
|
60
|
+
|
|
61
|
+
const services = options.service ? [options.service] : [];
|
|
62
|
+
await restartServices(composeFile, services);
|
|
63
|
+
|
|
64
|
+
spinner.succeed(services.length ? `${options.service} restarted` : 'All services restarted');
|
|
65
|
+
console.log(chalk.gray('\nRun "ante status" to check service status\n'));
|
|
66
|
+
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(chalk.red('\nError:'), error.message);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Table from 'cli-table3';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { getInstallDir } from '../utils/config.js';
|
|
5
|
+
import { getServiceStatus } from '../utils/docker.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Show ANTE service status
|
|
9
|
+
*/
|
|
10
|
+
export async function status() {
|
|
11
|
+
try {
|
|
12
|
+
const installDir = getInstallDir();
|
|
13
|
+
const composeFile = join(installDir, 'docker-compose.yml');
|
|
14
|
+
|
|
15
|
+
console.log(chalk.bold('\nš ANTE ERP Status\n'));
|
|
16
|
+
|
|
17
|
+
const services = await getServiceStatus(composeFile);
|
|
18
|
+
|
|
19
|
+
if (services.length === 0) {
|
|
20
|
+
console.log(chalk.yellow('No services running'));
|
|
21
|
+
console.log(chalk.gray('Run "ante start" to start services\n'));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const table = new Table({
|
|
26
|
+
head: [
|
|
27
|
+
chalk.cyan('Service'),
|
|
28
|
+
chalk.cyan('Status'),
|
|
29
|
+
chalk.cyan('Health'),
|
|
30
|
+
chalk.cyan('Ports')
|
|
31
|
+
],
|
|
32
|
+
style: {
|
|
33
|
+
head: [],
|
|
34
|
+
border: ['gray']
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
for (const service of services) {
|
|
39
|
+
const statusIcon = service.State === 'running' ? chalk.green('ā') : chalk.red('ā');
|
|
40
|
+
const healthIcon = service.Health === 'healthy' ? chalk.green('ā') :
|
|
41
|
+
service.Health === 'unhealthy' ? chalk.red('ā') :
|
|
42
|
+
chalk.yellow('ā');
|
|
43
|
+
|
|
44
|
+
table.push([
|
|
45
|
+
service.Service,
|
|
46
|
+
`${statusIcon} ${service.State}`,
|
|
47
|
+
service.Health ? `${healthIcon} ${service.Health}` : chalk.gray('N/A'),
|
|
48
|
+
service.Publishers?.map(p => `${p.PublishedPort}ā${p.TargetPort}`).join(', ') || chalk.gray('internal')
|
|
49
|
+
]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
console.log(table.toString());
|
|
53
|
+
console.log();
|
|
54
|
+
|
|
55
|
+
const allHealthy = services.every(s => s.State === 'running');
|
|
56
|
+
|
|
57
|
+
if (allHealthy) {
|
|
58
|
+
console.log(chalk.green('ā All services running\n'));
|
|
59
|
+
} else {
|
|
60
|
+
console.log(chalk.yellow('ā Some services are not running'));
|
|
61
|
+
console.log(chalk.gray('Run "ante restart" to restart services\n'));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error(chalk.red('Error:'), error.message);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|