wative 1.2.7 → 1.2.10

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