wative 1.1.16 → 1.1.18

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.
Files changed (65) hide show
  1. package/lib/account.d.ts +14 -0
  2. package/lib/account.d.ts.map +1 -0
  3. package/lib/assets.d.ts +11 -0
  4. package/lib/assets.d.ts.map +1 -0
  5. package/lib/chain.d.ts +6 -0
  6. package/lib/chain.d.ts.map +1 -0
  7. package/lib/config.d.ts +23 -0
  8. package/lib/config.d.ts.map +1 -0
  9. package/lib/daemon-watcher.js +181 -0
  10. package/lib/home_page.d.ts +4 -0
  11. package/lib/home_page.d.ts.map +1 -0
  12. package/lib/index.d.ts +12 -0
  13. package/lib/index.d.ts.map +1 -0
  14. package/lib/index.esm.js +1 -1
  15. package/lib/index.umd.js +1 -1
  16. package/lib/network.d.ts +28 -0
  17. package/lib/network.d.ts.map +1 -0
  18. package/lib/ssh/client.d.ts +120 -0
  19. package/lib/ssh/client.d.ts.map +1 -0
  20. package/lib/ssh/client_example.d.ts +19 -0
  21. package/lib/ssh/client_example.d.ts.map +1 -0
  22. package/lib/ssh/client_test.d.ts +27 -0
  23. package/lib/ssh/client_test.d.ts.map +1 -0
  24. package/lib/ssh/config_manager.d.ts +55 -0
  25. package/lib/ssh/config_manager.d.ts.map +1 -0
  26. package/lib/ssh/config_template.d.ts +52 -0
  27. package/lib/ssh/config_template.d.ts.map +1 -0
  28. package/lib/ssh/index.d.ts +8 -0
  29. package/lib/ssh/index.d.ts.map +1 -0
  30. package/lib/ssh/remote_server_example.d.ts +16 -0
  31. package/lib/ssh/remote_server_example.d.ts.map +1 -0
  32. package/lib/ssh/service_manager.d.ts +119 -0
  33. package/lib/ssh/service_manager.d.ts.map +1 -0
  34. package/lib/ssh/types.d.ts +45 -0
  35. package/lib/ssh/types.d.ts.map +1 -0
  36. package/lib/ssh/utils.d.ts +11 -0
  37. package/lib/ssh/utils.d.ts.map +1 -0
  38. package/lib/ssh/wative_server.d.ts +26 -0
  39. package/lib/ssh/wative_server.d.ts.map +1 -0
  40. package/lib/tools.d.ts +6 -0
  41. package/lib/tools.d.ts.map +1 -0
  42. package/lib/tx_gas_utils.d.ts +18 -0
  43. package/lib/tx_gas_utils.d.ts.map +1 -0
  44. package/lib/utils.d.ts +49 -0
  45. package/lib/utils.d.ts.map +1 -0
  46. package/lib/wative.d.ts +4 -0
  47. package/lib/wative.d.ts.map +1 -0
  48. package/lib/wative.esm.js +1 -1
  49. package/lib/wative.umd.js +1 -1
  50. package/lib/web3.d.ts +59 -0
  51. package/lib/web3.d.ts.map +1 -0
  52. package/package.json +10 -6
  53. package/src/daemon-watcher.js +181 -0
  54. package/src/index.ts +22 -6
  55. package/src/ssh/client.rs +221 -0
  56. package/src/ssh/client.ts +389 -0
  57. package/src/ssh/config_manager.ts +317 -0
  58. package/src/ssh/index.ts +50 -36
  59. package/src/ssh/service_manager.ts +684 -0
  60. package/src/ssh/types.ts +35 -1
  61. package/src/ssh/wative_server.ts +1 -2
  62. package/src/wative.ts +566 -122
  63. package/bin/wative-ssh.sh +0 -44
  64. package/lib/wative-ssh.esm.js +0 -1
  65. package/lib/wative-ssh.umd.js +0 -1
package/src/wative.ts CHANGED
@@ -1,20 +1,58 @@
1
1
  import * as path from 'path';
2
2
  import { Command } from 'commander';
3
3
  import { execSync } from 'child_process';
4
- import { getAndGenerateConfig } from './ssh/utils';
4
+ import { SSHServiceManager } from './ssh/service_manager';
5
+ import { SSHConfigManager } from './ssh/config_manager';
5
6
  import * as fs from 'fs';
7
+ import { spawn } from 'child_process';
6
8
 
7
- const os = require('os');
8
9
  const program = new Command();
9
10
  const packageJson = require('../package.json');
10
11
 
11
- const hasSystemd = () => {
12
- try {
13
- execSync('systemctl --version', { stdio: 'ignore' });
14
- return true;
15
- } catch (error) {
16
- return false;
12
+ // 全局服务管理器实例
13
+ let serviceManager: SSHServiceManager;
14
+
15
+ // 守护进程监控器
16
+ const startDaemonWatcher = (serviceName: string, encryptedPasswords: string) => {
17
+ const logDir = path.join(process.env.HOME || '', '.wative', 'logs');
18
+ const pidDir = path.join(process.env.HOME || '', '.wative', 'ssh', 'pids');
19
+
20
+ if (!fs.existsSync(logDir)) {
21
+ fs.mkdirSync(logDir, { recursive: true });
22
+ }
23
+ if (!fs.existsSync(pidDir)) {
24
+ fs.mkdirSync(pidDir, { recursive: true });
17
25
  }
26
+
27
+ const logFile = path.join(logDir, `${serviceName}-daemon.log`);
28
+ const pidFile = path.join(pidDir, `${serviceName}-daemon.pid`);
29
+ const watcherPidFile = path.join(pidDir, `${serviceName}-watcher.pid`);
30
+
31
+ // Start daemon watcher process
32
+ const watcher = spawn('node', [
33
+ path.resolve(__dirname, 'daemon-watcher.js'),
34
+ serviceName,
35
+ logFile,
36
+ pidFile,
37
+ watcherPidFile,
38
+ path.resolve(__dirname, '..')
39
+ ], {
40
+ detached: true,
41
+ stdio: 'ignore',
42
+ env: {
43
+ ...process.env,
44
+ WATIVE_ENCRYPTED_PASSWORDS: encryptedPasswords
45
+ }
46
+ });
47
+
48
+ // 写入监控器PID文件
49
+ fs.writeFileSync(watcherPidFile, watcher.pid!.toString());
50
+
51
+ watcher.unref();
52
+
53
+ console.log(`SSH service '${serviceName}' daemon watcher started with PID: ${watcher.pid}`);
54
+ console.log(`Daemon log file: ${logFile}`);
55
+ console.log(`Watcher PID file: ${watcherPidFile}`);
18
56
  };
19
57
 
20
58
  program
@@ -28,144 +66,544 @@ program
28
66
  console.log(`Wative CLI Tool v${packageJson.version}`);
29
67
  });
30
68
 
31
-
32
- const startSSHService = async () => {
33
- if (!hasSystemd()) {
34
- console.log('Systemd is not available.');
35
- return;
69
+ // 初始化服务管理器
70
+ const initServiceManager = () => {
71
+ if (!serviceManager) {
72
+ const configDir = path.join(process.env.HOME || '', '.wative', 'ssh');
73
+ serviceManager = new SSHServiceManager(configDir);
36
74
  }
75
+ return serviceManager;
76
+ };
37
77
 
38
- let logDir = '/var/log/wative';
39
- if (!fs.existsSync(logDir)) {
40
- fs.mkdirSync(logDir);
41
- }
78
+ const sshCommand = program
79
+ .command('ssh')
80
+ .description('Manage SSH services');
42
81
 
43
- const userHomeDirectory = os.homedir();
44
- const keystorePath = path.join(userHomeDirectory, '.wative');
45
- await getAndGenerateConfig(keystorePath);
46
- console.log('Starting SSH service...');
47
- try {
48
- execSync('wative-ssh --daemon', { stdio: 'inherit' });
49
- } catch (error) { }
50
- console.log('SSH service started.');
51
- }
52
82
 
53
- const stopSSHService = () => {
54
- if (!hasSystemd()) {
55
- console.log('Systemd is not available.');
56
- return;
57
- }
58
83
 
59
- try {
60
- execSync("ps aux | grep wative-ssh | grep -v grep | awk '{print $2}' | sudo xargs kill", { stdio: 'inherit' });
61
- } catch (error) {
62
- // console.log(error);
63
- }
64
- console.log('SSH service stopped.');
65
- }
84
+ // SSH start command
85
+ sshCommand
86
+ .command('start')
87
+ .description('Start SSH service')
88
+ .option('-c, --config <name>', 'Specify service configuration name')
89
+ .option('-d, --daemon', 'Run service in daemon mode')
90
+ .action(async (options) => {
91
+ const manager = initServiceManager();
66
92
 
67
- const addSSHService = async () => {
68
- if (!hasSystemd()) {
69
- console.log('Systemd is not available.');
70
- return;
71
- }
93
+ try {
94
+ if (!options.config) {
95
+ console.error('Error: Configuration name is required. Use -c or --config to specify a service configuration.');
96
+ process.exit(1);
97
+ }
98
+
99
+ const serviceName = options.config;
100
+
101
+ // 在启动前检查端口是否被占用
102
+ const config = manager.getServiceConfig(serviceName);
103
+ const net = require('net');
104
+
105
+ // 端口占用检查函数
106
+ const isPortInUse = async (port: number, host: string = '127.0.0.1'): Promise<boolean> => {
107
+ return new Promise((resolve) => {
108
+ const server = net.createServer();
109
+
110
+ server.listen(port, host, () => {
111
+ server.once('close', () => {
112
+ resolve(false); // 端口未被占用
113
+ });
114
+ server.close();
115
+ });
116
+
117
+ server.on('error', (err: any) => {
118
+ if (err.code === 'EADDRINUSE') {
119
+ resolve(true); // 端口被占用
120
+ } else {
121
+ resolve(false); // 其他错误,假设端口可用
122
+ }
123
+ });
124
+ });
125
+ };
126
+
127
+ // 检查端口占用
128
+ const portInUse = await isPortInUse(config.ssh.port, config.ssh.listen);
129
+ if (portInUse) {
130
+ const errorMessage = `Port ${config.ssh.port} on ${config.ssh.listen} is already in use. Cannot start SSH service '${serviceName}'.`;
131
+ console.error(`Error: ${errorMessage}`);
132
+ process.exit(1);
133
+ }
134
+
135
+ console.log(`Port ${config.ssh.port} on ${config.ssh.listen} is available for service '${serviceName}'`);
136
+
137
+ if (options.daemon) {
138
+ console.log(`Starting SSH service '${serviceName}' in daemon mode...`);
139
+
140
+ // 在 daemon 模式下,先收集密码
141
+ const passwords = await manager.promptForPasswords(config.keystore.allowed_keystores);
142
+
143
+ const crypto = require('crypto');
144
+
145
+ // 生成临时密钥用于加密密码
146
+ const tempKey = crypto.randomBytes(32);
147
+ const iv = crypto.randomBytes(16);
148
+ const cipher = crypto.createCipheriv('aes-256-cbc', tempKey, iv);
149
+ let encryptedPasswords = cipher.update(JSON.stringify(passwords), 'utf8', 'hex');
150
+ encryptedPasswords += cipher.final('hex');
151
+ const encryptedData = iv.toString('hex') + ':' + tempKey.toString('hex') + ':' + encryptedPasswords;
152
+
153
+ // 启动守护进程监控器
154
+ startDaemonWatcher(serviceName, encryptedData);
155
+
156
+ return;
157
+ } else if (process.env.WATIVE_DAEMON_PASSWORDS && process.env.WATIVE_WORKER_MODE) {
158
+ // 工作进程模式:解密并使用密码
159
+ const crypto = require('crypto');
160
+ const encryptedPasswords = process.env.WATIVE_DAEMON_PASSWORDS;
161
+
162
+ // 立即清除环境变量以防止其他进程访问
163
+ delete process.env.WATIVE_DAEMON_PASSWORDS;
164
+ delete process.env.WATIVE_WORKER_MODE;
165
+
166
+ try {
167
+ // 解密密码
168
+ const parts = encryptedPasswords.split(':');
169
+ const iv = Buffer.from(parts[0], 'hex');
170
+ const tempKey = Buffer.from(parts[1], 'hex');
171
+ const encryptedData = parts[2];
172
+ const decipher = crypto.createDecipheriv('aes-256-cbc', tempKey, iv);
173
+ let decryptedData = decipher.update(encryptedData, 'hex', 'utf8');
174
+ decryptedData += decipher.final('utf8');
175
+ const passwords = JSON.parse(decryptedData);
176
+
177
+ console.log(`[${new Date().toISOString()}] Worker process starting SSH service '${serviceName}'...`);
178
+
179
+ // 启动服务
180
+ const workerManager = initServiceManager();
181
+ await workerManager.startService(serviceName, passwords);
182
+
183
+ // 立即清除内存中的密码
184
+ for (const key in passwords) {
185
+ passwords[key] = null;
186
+ delete passwords[key];
187
+ }
188
+
189
+ console.log(`[${new Date().toISOString()}] SSH service '${serviceName}' started successfully`);
190
+
191
+ // 设置优雅关闭处理
192
+ const gracefulShutdown = async (signal: string) => {
193
+ console.log(`[${new Date().toISOString()}] Worker received ${signal}, stopping service...`);
194
+ try {
195
+ await workerManager.stopService(serviceName);
196
+ console.log(`[${new Date().toISOString()}] Service stopped successfully`);
197
+ } catch (error) {
198
+ console.error(`[${new Date().toISOString()}] Error stopping service:`, error);
199
+ }
200
+ process.exit(0);
201
+ };
202
+
203
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
204
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
205
+
206
+ // 监控服务状态
207
+ const monitorInterval = setInterval(async () => {
208
+ try {
209
+ const status = await workerManager.getServiceStatus(serviceName);
210
+ if (status !== 'running') {
211
+ console.log(`[${new Date().toISOString()}] Service status changed to: ${status}`);
212
+ if (status === 'stopped') {
213
+ console.log(`[${new Date().toISOString()}] Service stopped unexpectedly, exiting worker...`);
214
+ clearInterval(monitorInterval);
215
+ process.exit(1);
216
+ }
217
+ }
218
+ } catch (error) {
219
+ console.error(`[${new Date().toISOString()}] Error checking service status:`, error);
220
+ }
221
+ }, 30000); // 每30秒检查一次
222
+
223
+ // 保持进程活跃
224
+ const keepAliveInterval = setInterval(() => {
225
+ // 空操作,保持进程运行
226
+ }, 60000);
227
+
228
+ // 清理函数
229
+ const cleanup = () => {
230
+ clearInterval(monitorInterval);
231
+ clearInterval(keepAliveInterval);
232
+ };
233
+
234
+ process.on('exit', cleanup);
235
+
236
+ } catch (error) {
237
+ console.error(`[${new Date().toISOString()}] Worker process error:`, error);
238
+ process.exit(1);
239
+ }
240
+
241
+ return;
242
+ } else {
243
+ // 非 daemon 模式
244
+ await manager.startService(serviceName);
245
+ console.log(`SSH service '${serviceName}' started successfully`);
246
+
247
+ // 非 daemon 模式下保持进程运行
248
+ process.on('SIGINT', async () => {
249
+ console.log('\nReceived SIGINT, stopping services...');
250
+ await manager.stopAllServices();
251
+ process.exit(0);
252
+ });
253
+
254
+ process.on('SIGTERM', async () => {
255
+ console.log('\nReceived SIGTERM, stopping services...');
256
+ await manager.stopAllServices();
257
+ process.exit(0);
258
+ });
259
+
260
+ // 保持进程活跃
261
+ const keepAlive = setInterval(() => {
262
+ const status = manager.getServiceStatus(serviceName);
263
+ if (status !== 'running') {
264
+ console.log(`Service ${serviceName} is no longer running, exiting...`);
265
+ clearInterval(keepAlive);
266
+ process.exit(1);
267
+ }
268
+ }, 5000);
269
+
270
+ // 阻止进程退出
271
+ process.stdin.resume();
272
+ }
273
+ } catch (error) {
274
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
275
+ process.exit(1);
276
+ }
277
+ });
72
278
 
73
- let logDir = '/var/log/wative';
74
- if (!fs.existsSync(logDir)) {
75
- fs.mkdirSync(logDir);
76
- }
279
+ // SSH stop command
280
+ sshCommand
281
+ .command('stop')
282
+ .description('Stop SSH service')
283
+ .option('-c, --config <name>', 'Specify service configuration name')
284
+ .action(async (options) => {
285
+ const manager = initServiceManager();
77
286
 
78
- const userHomeDirectory = os.homedir();
79
- const keystorePath = path.join(userHomeDirectory, '.wative');
80
- await getAndGenerateConfig(keystorePath);
81
-
82
- const serviceFilePath = '/etc/systemd/system/wative.service';
83
- const serviceContent = `
84
- [Unit]
85
- Description=Wative SSH Server
86
- After=network.target
87
-
88
- [Service]
89
- ExecStart=/usr/local/bin/wative-ssh
90
- User=root
91
- Group=root
92
- StandardOutput=append:/var/log/wative/wative.log
93
- StandardError=append:/var/log/wative/wative.err
94
-
95
- [Install]
96
- WantedBy=multi-user.target
97
- `;
98
-
99
- console.log('Adding SSH service...');
100
- try {
101
- execSync(`sudo echo '${serviceContent}' > ${serviceFilePath}`, { stdio: 'inherit' });
102
- execSync('sudo systemctl daemon-reload', { stdio: 'inherit' });
103
- execSync('sudo systemctl enable wative', { stdio: 'inherit' });
104
- execSync('sudo systemctl start wative', { stdio: 'inherit' });
105
- } catch (error) {
106
- // console.log(error);
107
- }
287
+ try {
288
+ if (!options.config) {
289
+ console.error('Error: Configuration name is required. Use -c or --config to specify a service configuration.');
290
+ process.exit(1);
291
+ }
292
+
293
+ await manager.stopService(options.config);
294
+ console.log(`SSH service '${options.config}' stopped successfully`);
295
+ } catch (error) {
296
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
297
+ process.exit(1);
298
+ }
299
+ });
108
300
 
109
- console.log('SSH service added.');
110
- }
301
+ // SSH restart command
302
+ sshCommand
303
+ .command('restart')
304
+ .description('Restart SSH service')
305
+ .option('-c, --config <name>', 'Specify service configuration name')
306
+ .action(async (options) => {
307
+ const manager = initServiceManager();
111
308
 
309
+ try {
310
+ if (!options.config) {
311
+ console.error('Error: Configuration name is required. Use -c or --config to specify a service configuration.');
312
+ process.exit(1);
313
+ }
314
+
315
+ await manager.restartService(options.config);
316
+ console.log(`SSH service '${options.config}' restarted successfully`);
317
+ } catch (error) {
318
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
319
+ process.exit(1);
320
+ }
321
+ });
112
322
 
113
- const removeSSHService = () => {
114
- if (!hasSystemd()) {
115
- console.log('Systemd is not available.');
116
- return;
117
- }
118
- console.log('Removing SSH service...');
323
+ // SSH create command
324
+ sshCommand
325
+ .command('create')
326
+ .description('Create new SSH service configuration interactively')
327
+ .action(async () => {
328
+ const readline = require('readline');
329
+ const fs = require('fs');
330
+ const path = require('path');
331
+ // Use the already imported SSHConfigManager instead of requiring it again
332
+
333
+ const rl = readline.createInterface({
334
+ input: process.stdin,
335
+ output: process.stdout
336
+ });
337
+
338
+ const question = (prompt: string): Promise<string> => {
339
+ return new Promise((resolve) => {
340
+ rl.question(prompt, resolve);
341
+ });
342
+ };
119
343
 
120
- try {
121
- execSync('sudo systemctl stop wative', { stdio: 'inherit' });
122
- } catch (error) {
123
- }
344
+ try {
345
+ console.log('SSH create command started...');
346
+
347
+ // 1. Configure storage location
348
+ const defaultStoragePath = path.join(process.env.HOME || '', '.wative/ssh');
349
+ const storagePathInput = await question(`SSH storage located in (default: ${defaultStoragePath}): `);
350
+ const storagePath = storagePathInput.trim() || defaultStoragePath;
351
+
352
+ // 2. Configure keystore location
353
+ const defaultKeystorePath = path.join(process.env.HOME || '', '.wative');
354
+ const keystorePathInput = await question(`Keystore storage located in (default: ${defaultKeystorePath}): `);
355
+ const keystorePath = keystorePathInput.trim() || defaultKeystorePath;
356
+
357
+ const configManager = new SSHConfigManager(storagePath, keystorePath);
358
+
359
+ // 3. Enter service name
360
+ let serviceName: string;
361
+ while (true) {
362
+ serviceName = await question('Enter service name: ');
363
+ if (serviceName.trim()) {
364
+ try {
365
+ configManager.loadServiceConfig(serviceName);
366
+ console.log('Service name already exists, please enter a different name');
367
+ } catch {
368
+ break; // Service doesn't exist, can use this name
369
+ }
370
+ } else {
371
+ console.log('Service name cannot be empty');
372
+ }
373
+ }
374
+
375
+ // 4. Enter port number
376
+ let port: number;
377
+ while (true) {
378
+ const portStr = await question('Enter port number: ');
379
+ port = parseInt(portStr);
380
+ if (isNaN(port) || port < 1024 || port > 65535) {
381
+ console.log('Port number must be between 1024-65535');
382
+ continue;
383
+ }
384
+
385
+ // 检查端口是否已被使用
386
+ const existingConfigs = configManager.listServiceConfigs();
387
+ let portInUse = false;
388
+ for (const configName of existingConfigs) {
389
+ try {
390
+ const config = configManager.loadServiceConfig(configName);
391
+ if (config.ssh.port === port) {
392
+ portInUse = true;
393
+ break;
394
+ }
395
+ } catch {
396
+ // Ignore loading errors
397
+ }
398
+ }
399
+
400
+ if (portInUse) {
401
+ console.log(`Port ${port} is already in use by another service, please enter a different port`);
402
+ } else {
403
+ break;
404
+ }
405
+ }
406
+
407
+ // 5. Enter allowed keystores
408
+ let allowedKeystores: string[];
409
+
410
+ while (true) {
411
+ const keystoresStr = await question('Enter allowed keystore list (comma separated): ');
412
+ if (!keystoresStr.trim()) {
413
+ console.log('Keystore list cannot be empty');
414
+ continue;
415
+ }
416
+
417
+ allowedKeystores = keystoresStr.split(',').map(k => k.trim()).filter(k => k);
418
+ if (allowedKeystores.length === 0) {
419
+ console.log('At least one valid keystore is required');
420
+ continue;
421
+ }
422
+
423
+ // Only verify keystore existence (no password verification)
424
+ const keystoreAccountsPath = path.join(keystorePath, 'accounts');
425
+ let allKeystoresValid = true;
426
+
427
+ for (const keystore of allowedKeystores) {
428
+ const keystoreFile = path.join(keystoreAccountsPath, `${keystore}.json`);
429
+ if (!fs.existsSync(keystoreFile)) {
430
+ console.log(`Keystore '${keystore}' does not exist`);
431
+ allKeystoresValid = false;
432
+ break;
433
+ }
434
+ }
435
+
436
+ if (allKeystoresValid) {
437
+ console.log('All keystores verified to exist');
438
+ break;
439
+ } else {
440
+ console.log('Please re-enter valid keystore list');
441
+ }
442
+ }
443
+
444
+ // 6. Enter remote client name
445
+ let clientName: string;
446
+ while (true) {
447
+ clientName = await question('Enter remote client name: ');
448
+ if (clientName.trim()) {
449
+ break;
450
+ } else {
451
+ console.log('Client name cannot be empty');
452
+ }
453
+ }
454
+
455
+ // 7. Prompt user to place client public key
456
+ const serviceDir = path.join(storagePath, 'conf.d', serviceName);
457
+ if (!fs.existsSync(serviceDir)) {
458
+ fs.mkdirSync(serviceDir, { recursive: true });
459
+ }
460
+ const clientPublicKeyPath = path.join(serviceDir, `${clientName}.pub`);
461
+
462
+ console.log(`\nPlease place the remote client's public key file at the following path:`);
463
+ console.log(`${clientPublicKeyPath}`);
464
+ console.log('\nPress Enter to continue after placing the file...');
465
+
466
+ while (true) {
467
+ await question('');
468
+ if (fs.existsSync(clientPublicKeyPath)) {
469
+ console.log('Client public key file found');
470
+ break;
471
+ } else {
472
+ console.log('Client public key file not found, please confirm the file is correctly placed and press Enter again');
473
+ }
474
+ }
475
+
476
+ // 8. Enter service description (optional)
477
+ const description = await question('Enter service description (optional): ');
478
+
479
+ // Create service configuration
480
+ console.log('\nCreating SSH service configuration...');
481
+ const config = await configManager.createServiceConfig(
482
+ serviceName,
483
+ port,
484
+ allowedKeystores,
485
+ clientName,
486
+ description || undefined
487
+ );
488
+
489
+ // Configuration created successfully
490
+ console.log('\nNote: Passwords will be requested when starting the service.');
491
+
492
+ console.log(`\nSSH service configuration '${serviceName}' created successfully!`);
493
+ console.log(`Port: ${port}`);
494
+ console.log(`Allowed keystores: ${allowedKeystores.join(', ')}`);
495
+ console.log(`Client name: ${clientName}`);
496
+ console.log(`Server private key: ${config.server_keys.private_key_path}`);
497
+ console.log(`Client public key: ${clientPublicKeyPath}`);
498
+
499
+ } catch (error) {
500
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
501
+ process.exit(1);
502
+ } finally {
503
+ rl.close();
504
+ }
505
+ });
124
506
 
125
- try {
126
- execSync('sudo systemctl disable wative', { stdio: 'inherit' });
127
- } catch (error) {
128
- }
507
+ // SSH remove command
508
+ sshCommand
509
+ .command('remove')
510
+ .description('Remove SSH service configuration')
511
+ .option('-c, --config <name>', 'Service configuration name')
512
+ .action(async (options) => {
513
+ const manager = initServiceManager();
129
514
 
130
- try {
131
- execSync('sudo rm -rf /etc/systemd/system/wative.service', { stdio: 'inherit' });
132
- } catch (error) {
133
- }
515
+ try {
516
+ if (!options.config) {
517
+ console.error('Error: Configuration name is required for remove command');
518
+ process.exit(1);
519
+ }
520
+
521
+ await manager.removeService(options.config);
522
+ console.log(`SSH service configuration '${options.config}' removed successfully`);
523
+ } catch (error) {
524
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
525
+ process.exit(1);
526
+ }
527
+ });
134
528
 
135
- try {
136
- execSync('sudo systemctl daemon-reload', { stdio: 'inherit' });
137
- } catch (error) {
138
- }
139
- console.log('SSH service removed.');
140
- }
141
529
 
142
530
 
531
+ // SSH list command
532
+ sshCommand
533
+ .command('list')
534
+ .description('List all SSH service configurations')
535
+ .action(async () => {
536
+ const configManager = new SSHConfigManager();
537
+ const serviceManager = new SSHServiceManager();
143
538
 
144
- program
145
- .command('ssh')
146
- .option('start', 'Start SSH service')
147
- .option('stop', 'Stop SSH service')
148
- .option('service add', 'Add SSH service')
149
- .option('service remove', 'Remove SSH service')
150
- .action(async (argv1, argv2) => {
151
- if (argv1 === 'start') {
152
- await startSSHService();
539
+ const configNames = configManager.listServiceConfigs();
540
+
541
+ if (configNames.length === 0) {
542
+ console.log('No SSH service configurations found.');
153
543
  return;
154
544
  }
155
545
 
156
- if (argv1 === 'stop') {
157
- stopSSHService();
158
- return;
546
+ console.log('SSH Service Configurations:');
547
+ for (const configName of configNames) {
548
+ try {
549
+ const config = configManager.loadServiceConfig(configName);
550
+ const status = serviceManager.getServiceStatus(configName);
551
+ console.log(` - ${configName}: ${config.description || 'No description'} (${status})`);
552
+ } catch (error) {
553
+ const status = serviceManager.getServiceStatus(configName);
554
+ console.log(` - ${configName}: Configuration error (${status})`);
555
+ }
159
556
  }
557
+ });
160
558
 
161
- if (argv1 === 'service' && argv2 === 'add') {
162
- addSSHService();
163
- return;
559
+ // SSH status command
560
+ sshCommand
561
+ .command('status')
562
+ .description('Show SSH service status')
563
+ .option('-c, --config <name>', 'Service configuration name')
564
+ .action(async (options) => {
565
+ const manager = initServiceManager();
566
+
567
+ try {
568
+ if (options.config) {
569
+ const status = manager.getServiceStatus(options.config);
570
+ console.log(`SSH service '${options.config}' status: ${status}`);
571
+ } else {
572
+ const services = manager.listServices();
573
+
574
+ if (services.length === 0) {
575
+ console.log('No SSH service configurations found');
576
+ return;
577
+ }
578
+
579
+ console.log('SSH Service Status:');
580
+ services.forEach(service => {
581
+ const status = manager.getServiceStatus(service.name);
582
+ console.log(` - ${service.name}: ${status}`);
583
+ });
584
+ }
585
+ } catch (error) {
586
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
587
+ process.exit(1);
164
588
  }
589
+ });
165
590
 
166
- if (argv1 === 'service' && argv2 === 'remove') {
167
- removeSSHService();
168
- return;
591
+ // SSH log command
592
+ sshCommand
593
+ .command('log')
594
+ .description('Show SSH service logs')
595
+ .option('-c, --config <name>', 'Service configuration name')
596
+ .option('-n, --lines <number>', 'Number of lines to show (default: 50)', '50')
597
+ .option('-f, --follow', 'Follow log output')
598
+ .action(async (options) => {
599
+ const manager = initServiceManager();
600
+
601
+ try {
602
+ const lines = parseInt(options.lines) || 50;
603
+ await manager.showLogs(options.config, lines, options.follow);
604
+ } catch (error) {
605
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
606
+ process.exit(1);
169
607
  }
170
608
  });
171
609
 
@@ -180,4 +618,10 @@ program
180
618
  });
181
619
 
182
620
 
183
- program.parse(process.argv);
621
+ // Parse CLI arguments only when called from bin script or directly
622
+ if (require.main === module || (process.argv[1] && process.argv[1].includes('bin/wative.js'))) {
623
+ program.parse(process.argv);
624
+ }
625
+
626
+ // Export the program for potential library usage
627
+ export { program };