wative 1.2.8 → 1.2.11
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/lib/assets.d.ts.map +1 -1
- package/lib/index.esm.js +1 -1
- package/lib/index.umd.js +1 -1
- package/lib/tools.d.ts.map +1 -1
- package/lib/wallet-cli.esm.js +1 -1
- package/lib/wallet-cli.umd.js +1 -1
- package/lib/web3.d.ts +6 -2
- package/lib/web3.d.ts.map +1 -1
- package/package.json +21 -22
- package/src/account.ts +0 -864
- package/src/assets.ts +0 -399
- package/src/chain.ts +0 -28
- package/src/config.ts +0 -2074
- package/src/daemon-watcher.js +0 -181
- package/src/home_page.ts +0 -36
- package/src/index.d.ts +0 -12
- package/src/index.ts +0 -53
- package/src/network.ts +0 -819
- package/src/ssh/client.ts +0 -389
- package/src/ssh/config_manager.ts +0 -304
- package/src/ssh/index.ts +0 -66
- package/src/ssh/service_manager.ts +0 -685
- package/src/ssh/types.ts +0 -50
- package/src/ssh/utils.ts +0 -89
- package/src/ssh/wative_server.ts +0 -534
- package/src/tools.ts +0 -923
- package/src/tx_gas_utils.ts +0 -119
- package/src/utils.ts +0 -916
- package/src/wallet-cli.ts +0 -39
- package/src/wative.ts +0 -633
- package/src/web3.ts +0 -415
|
@@ -1,685 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { SSHConfigManager } from './config_manager';
|
|
4
|
-
import { WativeWielderServer } from './wative_server';
|
|
5
|
-
import { SSHServiceConfig, ServiceInstance } from './types';
|
|
6
|
-
// Removed PasswordManager import - using in-memory password management
|
|
7
|
-
import * as log4js from 'log4js';
|
|
8
|
-
import { inputPasswordWithoutValidator } from '../utils';
|
|
9
|
-
import { WativeCore } from 'wative-core';
|
|
10
|
-
import { spawn } from 'child_process';
|
|
11
|
-
import * as net from 'net';
|
|
12
|
-
|
|
13
|
-
export class SSHServiceManager {
|
|
14
|
-
private configManager: SSHConfigManager;
|
|
15
|
-
private runningServices: Map<string, ServiceInstance> = new Map();
|
|
16
|
-
private logger!: log4js.Logger;
|
|
17
|
-
private pidDir: string;
|
|
18
|
-
|
|
19
|
-
constructor(configDir?: string, keystorePath?: string) {
|
|
20
|
-
this.configManager = new SSHConfigManager(configDir, keystorePath);
|
|
21
|
-
this.pidDir = path.join(this.configManager.getKeystorePath(), 'ssh', 'pids');
|
|
22
|
-
this.ensurePidDir();
|
|
23
|
-
this.setupLogger();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* 确保 PID 目录存在
|
|
28
|
-
*/
|
|
29
|
-
private ensurePidDir(): void {
|
|
30
|
-
if (!fs.existsSync(this.pidDir)) {
|
|
31
|
-
fs.mkdirSync(this.pidDir, { recursive: true });
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* 检查端口是否被占用
|
|
37
|
-
* @param port 要检查的端口号
|
|
38
|
-
* @param host 主机地址,默认为 '127.0.0.1'
|
|
39
|
-
* @returns Promise<boolean> 如果端口被占用返回 true,否则返回 false
|
|
40
|
-
*/
|
|
41
|
-
private async isPortInUse(port: number, host: string = '127.0.0.1'): Promise<boolean> {
|
|
42
|
-
return new Promise((resolve) => {
|
|
43
|
-
const server = net.createServer();
|
|
44
|
-
|
|
45
|
-
server.listen(port, host, () => {
|
|
46
|
-
server.once('close', () => {
|
|
47
|
-
resolve(false); // 端口未被占用
|
|
48
|
-
});
|
|
49
|
-
server.close();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
server.on('error', (err: any) => {
|
|
53
|
-
if (err.code === 'EADDRINUSE') {
|
|
54
|
-
resolve(true); // 端口被占用
|
|
55
|
-
} else {
|
|
56
|
-
resolve(false); // 其他错误,假设端口可用
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 获取服务的 PID 文件路径
|
|
64
|
-
*/
|
|
65
|
-
private getPidFilePath(serviceName: string): string {
|
|
66
|
-
return path.join(this.pidDir, `${serviceName}.pid`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* 写入 PID 文件
|
|
71
|
-
*/
|
|
72
|
-
private writePidFile(serviceName: string, pid: number): void {
|
|
73
|
-
const pidFilePath = this.getPidFilePath(serviceName);
|
|
74
|
-
fs.writeFileSync(pidFilePath, pid.toString());
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* 读取 PID 文件
|
|
79
|
-
*/
|
|
80
|
-
private readPidFile(serviceName: string): number | null {
|
|
81
|
-
const pidFilePath = this.getPidFilePath(serviceName);
|
|
82
|
-
if (!fs.existsSync(pidFilePath)) {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
try {
|
|
86
|
-
const pidStr = fs.readFileSync(pidFilePath, 'utf8').trim();
|
|
87
|
-
return parseInt(pidStr, 10);
|
|
88
|
-
} catch (error) {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* 删除 PID 文件
|
|
95
|
-
*/
|
|
96
|
-
private removePidFile(serviceName: string): void {
|
|
97
|
-
const pidFilePath = this.getPidFilePath(serviceName);
|
|
98
|
-
if (fs.existsSync(pidFilePath)) {
|
|
99
|
-
fs.unlinkSync(pidFilePath);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* 检查进程是否存在
|
|
105
|
-
*/
|
|
106
|
-
private isProcessRunning(pid: number): boolean {
|
|
107
|
-
try {
|
|
108
|
-
process.kill(pid, 0);
|
|
109
|
-
return true;
|
|
110
|
-
} catch (error) {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* 启动指定的 SSH 服务
|
|
117
|
-
*/
|
|
118
|
-
public async startService(serviceName: string, passwords?: { [keystoreId: string]: string }): Promise<void> {
|
|
119
|
-
if (this.runningServices.has(serviceName)) {
|
|
120
|
-
throw new Error(`Service '${serviceName}' is already running.`);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const config = this.configManager.loadServiceConfig(serviceName);
|
|
124
|
-
|
|
125
|
-
// 检查端口是否被占用
|
|
126
|
-
const portInUse = await this.isPortInUse(config.ssh.port, config.ssh.listen);
|
|
127
|
-
if (portInUse) {
|
|
128
|
-
const errorMessage = `Port ${config.ssh.port} on ${config.ssh.listen} is already in use. Cannot start SSH service '${serviceName}'.`;
|
|
129
|
-
this.logger.error(errorMessage);
|
|
130
|
-
console.error(`Error: ${errorMessage}`);
|
|
131
|
-
throw new Error(errorMessage);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
this.logger.info(`Port ${config.ssh.port} on ${config.ssh.listen} is available for service '${serviceName}'`);
|
|
135
|
-
|
|
136
|
-
// 如果没有提供密码,则交互式输入
|
|
137
|
-
const keystorePasswords = passwords || await this.promptForPasswords(config.keystore.allowed_keystores);
|
|
138
|
-
|
|
139
|
-
// 验证密码
|
|
140
|
-
const validationResults = await this.configManager.validateKeystorePasswords(
|
|
141
|
-
config.keystore.allowed_keystores,
|
|
142
|
-
keystorePasswords
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
const invalidKeystores = Object.entries(validationResults)
|
|
146
|
-
.filter(([_, isValid]) => !isValid)
|
|
147
|
-
.map(([keystoreId, _]) => keystoreId);
|
|
148
|
-
|
|
149
|
-
if (invalidKeystores.length > 0) {
|
|
150
|
-
throw new Error(`Invalid passwords for keystores: ${invalidKeystores.join(', ')}`);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// 创建服务实例
|
|
154
|
-
const server = this.createServerInstance(config, keystorePasswords);
|
|
155
|
-
|
|
156
|
-
// 启动服务
|
|
157
|
-
server.listen(config.ssh.listen, config.ssh.port);
|
|
158
|
-
|
|
159
|
-
// 写入 PID 文件
|
|
160
|
-
this.writePidFile(serviceName, process.pid);
|
|
161
|
-
|
|
162
|
-
this.logger.info(`SSH service '${serviceName}' started on ${config.ssh.listen}:${config.ssh.port}`);
|
|
163
|
-
console.log(`SSH service '${serviceName}' is now running in the background on ${config.ssh.listen}:${config.ssh.port}`);
|
|
164
|
-
|
|
165
|
-
const serviceInstance: ServiceInstance = {
|
|
166
|
-
config,
|
|
167
|
-
passwords: keystorePasswords,
|
|
168
|
-
server
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
this.runningServices.set(serviceName, serviceInstance);
|
|
172
|
-
|
|
173
|
-
// 设置服务特定的日志文件
|
|
174
|
-
this.setupServiceLogger(serviceName);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* 停止指定的 SSH 服务
|
|
179
|
-
*/
|
|
180
|
-
public async stopService(serviceName: string): Promise<void> {
|
|
181
|
-
const serviceInstance = this.runningServices.get(serviceName);
|
|
182
|
-
const pid = this.readPidFile(serviceName);
|
|
183
|
-
|
|
184
|
-
// 检查daemon模式的PID文件
|
|
185
|
-
const daemonPidFile = path.join(this.pidDir, `${serviceName}-daemon.pid`);
|
|
186
|
-
const watcherPidFile = path.join(this.pidDir, `${serviceName}-watcher.pid`);
|
|
187
|
-
let daemonPid: number | null = null;
|
|
188
|
-
let watcherPid: number | null = null;
|
|
189
|
-
|
|
190
|
-
if (fs.existsSync(daemonPidFile)) {
|
|
191
|
-
const pidContent = fs.readFileSync(daemonPidFile, 'utf8').trim();
|
|
192
|
-
if (pidContent) {
|
|
193
|
-
daemonPid = parseInt(pidContent);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (fs.existsSync(watcherPidFile)) {
|
|
198
|
-
const pidContent = fs.readFileSync(watcherPidFile, 'utf8').trim();
|
|
199
|
-
if (pidContent) {
|
|
200
|
-
watcherPid = parseInt(pidContent);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// 如果服务在内存中运行
|
|
205
|
-
if (serviceInstance) {
|
|
206
|
-
// 停止服务器
|
|
207
|
-
if (serviceInstance.server && serviceInstance.server.close) {
|
|
208
|
-
serviceInstance.server.close();
|
|
209
|
-
}
|
|
210
|
-
this.runningServices.delete(serviceName);
|
|
211
|
-
}
|
|
212
|
-
// 如果是daemon模式的服务,优先停止监控器进程
|
|
213
|
-
else if (watcherPid && this.isProcessRunning(watcherPid)) {
|
|
214
|
-
try {
|
|
215
|
-
// 停止监控器进程,监控器会自动停止SSH服务进程
|
|
216
|
-
process.kill(watcherPid, 'SIGTERM');
|
|
217
|
-
// 等待一段时间后强制杀死
|
|
218
|
-
setTimeout(() => {
|
|
219
|
-
if (this.isProcessRunning(watcherPid!)) {
|
|
220
|
-
process.kill(watcherPid!, 'SIGKILL');
|
|
221
|
-
}
|
|
222
|
-
// 清理PID文件
|
|
223
|
-
try {
|
|
224
|
-
if (fs.existsSync(watcherPidFile)) fs.unlinkSync(watcherPidFile);
|
|
225
|
-
if (fs.existsSync(daemonPidFile)) fs.unlinkSync(daemonPidFile);
|
|
226
|
-
} catch (e) {}
|
|
227
|
-
}, 5000);
|
|
228
|
-
} catch (error) {
|
|
229
|
-
// 进程可能已经不存在,尝试直接停止SSH服务进程
|
|
230
|
-
if (daemonPid && this.isProcessRunning(daemonPid)) {
|
|
231
|
-
try {
|
|
232
|
-
process.kill(daemonPid, 'SIGTERM');
|
|
233
|
-
setTimeout(() => {
|
|
234
|
-
if (this.isProcessRunning(daemonPid!)) {
|
|
235
|
-
process.kill(daemonPid!, 'SIGKILL');
|
|
236
|
-
}
|
|
237
|
-
}, 5000);
|
|
238
|
-
} catch (e) {}
|
|
239
|
-
}
|
|
240
|
-
// 清理PID文件
|
|
241
|
-
try {
|
|
242
|
-
if (fs.existsSync(watcherPidFile)) fs.unlinkSync(watcherPidFile);
|
|
243
|
-
if (fs.existsSync(daemonPidFile)) fs.unlinkSync(daemonPidFile);
|
|
244
|
-
} catch (e) {}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
// 如果只有SSH服务进程在运行
|
|
248
|
-
else if (daemonPid && this.isProcessRunning(daemonPid)) {
|
|
249
|
-
try {
|
|
250
|
-
process.kill(daemonPid, 'SIGTERM');
|
|
251
|
-
// 等待一段时间后强制杀死
|
|
252
|
-
setTimeout(() => {
|
|
253
|
-
if (this.isProcessRunning(daemonPid!)) {
|
|
254
|
-
process.kill(daemonPid!, 'SIGKILL');
|
|
255
|
-
}
|
|
256
|
-
}, 5000);
|
|
257
|
-
// 删除daemon PID文件
|
|
258
|
-
if (fs.existsSync(daemonPidFile)) fs.unlinkSync(daemonPidFile);
|
|
259
|
-
} catch (error) {
|
|
260
|
-
// 进程可能已经不存在
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
// 如果通过普通 PID 文件检测到服务运行
|
|
264
|
-
else if (pid && this.isProcessRunning(pid)) {
|
|
265
|
-
try {
|
|
266
|
-
process.kill(pid, 'SIGTERM');
|
|
267
|
-
// 等待一段时间后强制杀死
|
|
268
|
-
setTimeout(() => {
|
|
269
|
-
if (this.isProcessRunning(pid)) {
|
|
270
|
-
process.kill(pid, 'SIGKILL');
|
|
271
|
-
}
|
|
272
|
-
}, 5000);
|
|
273
|
-
} catch (error) {
|
|
274
|
-
// 进程可能已经不存在
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
throw new Error(`Service '${serviceName}' is not running.`);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// 删除普通 PID 文件
|
|
282
|
-
this.removePidFile(serviceName);
|
|
283
|
-
this.logger.info(`SSH service '${serviceName}' stopped.`);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* 重启指定的 SSH 服务
|
|
288
|
-
*/
|
|
289
|
-
public async restartService(serviceName: string, passwords?: { [keystoreId: string]: string }): Promise<void> {
|
|
290
|
-
const wasRunning = this.runningServices.has(serviceName);
|
|
291
|
-
|
|
292
|
-
if (wasRunning) {
|
|
293
|
-
await this.stopService(serviceName);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
await this.startService(serviceName, passwords);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* 获取所有运行中的服务
|
|
301
|
-
*/
|
|
302
|
-
public getRunningServices(): string[] {
|
|
303
|
-
return Array.from(this.runningServices.keys());
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* 获取服务配置
|
|
308
|
-
*/
|
|
309
|
-
public getServiceConfig(serviceName: string): SSHServiceConfig {
|
|
310
|
-
return this.configManager.loadServiceConfig(serviceName);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* 获取服务状态
|
|
315
|
-
*/
|
|
316
|
-
public getServiceStatus(serviceName: string): 'running' | 'stopped' | 'not_found' {
|
|
317
|
-
// 首先检查内存中的运行状态
|
|
318
|
-
if (this.runningServices.has(serviceName)) {
|
|
319
|
-
return 'running';
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// 检查配置是否存在
|
|
323
|
-
try {
|
|
324
|
-
this.configManager.loadServiceConfig(serviceName);
|
|
325
|
-
|
|
326
|
-
// 检查daemon模式的PID文件
|
|
327
|
-
const daemonPidFile = path.join(this.pidDir, `${serviceName}-daemon.pid`);
|
|
328
|
-
if (fs.existsSync(daemonPidFile)) {
|
|
329
|
-
const pidContent = fs.readFileSync(daemonPidFile, 'utf8').trim();
|
|
330
|
-
if (pidContent) {
|
|
331
|
-
const pid = parseInt(pidContent);
|
|
332
|
-
if (this.isProcessRunning(pid)) {
|
|
333
|
-
return 'running';
|
|
334
|
-
} else {
|
|
335
|
-
// 进程不存在,清理PID文件
|
|
336
|
-
fs.unlinkSync(daemonPidFile);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// 通过普通 PID 文件检查服务是否运行
|
|
342
|
-
const pid = this.readPidFile(serviceName);
|
|
343
|
-
if (pid && this.isProcessRunning(pid)) {
|
|
344
|
-
return 'running';
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// 如果 PID 文件存在但进程不存在,清理 PID 文件
|
|
348
|
-
if (pid) {
|
|
349
|
-
this.removePidFile(serviceName);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return 'stopped';
|
|
353
|
-
} catch (error) {
|
|
354
|
-
return 'not_found';
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* 停止所有服务
|
|
360
|
-
*/
|
|
361
|
-
public async stopAllServices(): Promise<void> {
|
|
362
|
-
const serviceNames = Array.from(this.runningServices.keys());
|
|
363
|
-
|
|
364
|
-
for (const serviceName of serviceNames) {
|
|
365
|
-
await this.stopService(serviceName);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* 创建新的服务配置
|
|
371
|
-
*/
|
|
372
|
-
public async createService(
|
|
373
|
-
serviceName: string,
|
|
374
|
-
port: number,
|
|
375
|
-
allowedKeystores: string[],
|
|
376
|
-
clientName?: string,
|
|
377
|
-
description?: string
|
|
378
|
-
): Promise<SSHServiceConfig> {
|
|
379
|
-
return this.configManager.createServiceConfig(
|
|
380
|
-
serviceName,
|
|
381
|
-
port,
|
|
382
|
-
allowedKeystores,
|
|
383
|
-
clientName,
|
|
384
|
-
description
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* 删除服务配置
|
|
390
|
-
*/
|
|
391
|
-
public async removeService(serviceName: string): Promise<void> {
|
|
392
|
-
// 如果服务正在运行,先停止它
|
|
393
|
-
if (this.runningServices.has(serviceName)) {
|
|
394
|
-
await this.stopService(serviceName);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
console.log("serviceName:", serviceName);
|
|
398
|
-
this.configManager.removeServiceConfig(serviceName);
|
|
399
|
-
this.logger.info(`Service '${serviceName}' removed.`);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* 列出所有服务配置
|
|
404
|
-
*/
|
|
405
|
-
public listServices(): { name: string; status: string; port?: number; description?: string }[] {
|
|
406
|
-
const configNames = this.configManager.listServiceConfigs();
|
|
407
|
-
|
|
408
|
-
return configNames.map(name => {
|
|
409
|
-
const status = this.getServiceStatus(name);
|
|
410
|
-
let port: number | undefined;
|
|
411
|
-
let description: string | undefined;
|
|
412
|
-
|
|
413
|
-
try {
|
|
414
|
-
const config = this.configManager.loadServiceConfig(name);
|
|
415
|
-
port = config.ssh.port;
|
|
416
|
-
description = config.description;
|
|
417
|
-
} catch (error) {
|
|
418
|
-
// 忽略加载错误
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
return { name, status, port, description };
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* 更新服务配置中的 keystore 列表
|
|
427
|
-
*/
|
|
428
|
-
public updateServiceKeystores(serviceName: string, keystores: string[]): void {
|
|
429
|
-
this.configManager.updateServiceConfig(serviceName, {
|
|
430
|
-
keystore: {
|
|
431
|
-
path: this.configManager.loadServiceConfig(serviceName).keystore.path,
|
|
432
|
-
allowed_keystores: keystores
|
|
433
|
-
}
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* 交互式输入密码(公共方法)
|
|
439
|
-
*/
|
|
440
|
-
public async promptForPasswords(keystoreIds: string[]): Promise<{ [keystoreId: string]: string }> {
|
|
441
|
-
const passwords: { [keystoreId: string]: string } = {};
|
|
442
|
-
|
|
443
|
-
for (const keystoreId of keystoreIds) {
|
|
444
|
-
let password: string;
|
|
445
|
-
let isValidPassword = false;
|
|
446
|
-
let attempts = 0;
|
|
447
|
-
const maxAttempts = 3;
|
|
448
|
-
|
|
449
|
-
while (!isValidPassword && attempts < maxAttempts) {
|
|
450
|
-
password = await inputPasswordWithoutValidator(`Enter password for keystore [${keystoreId}]`);
|
|
451
|
-
|
|
452
|
-
// 使用 wative-core 验证密码
|
|
453
|
-
try {
|
|
454
|
-
const keystorePath = this.configManager.getKeystorePath();
|
|
455
|
-
const wativeCore = new WativeCore(keystorePath, [keystoreId]);
|
|
456
|
-
isValidPassword = wativeCore.account.checkPassword(keystoreId, password);
|
|
457
|
-
|
|
458
|
-
if (isValidPassword) {
|
|
459
|
-
passwords[keystoreId] = password;
|
|
460
|
-
break;
|
|
461
|
-
} else {
|
|
462
|
-
attempts++;
|
|
463
|
-
if (attempts < maxAttempts) {
|
|
464
|
-
console.log(`Invalid password for ${keystoreId}`);
|
|
465
|
-
} else {
|
|
466
|
-
throw new Error(`Failed to authenticate keystore [${keystoreId}] after ${maxAttempts} attempts.`);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
} catch (error: any) {
|
|
470
|
-
attempts++;
|
|
471
|
-
if (attempts < maxAttempts) {
|
|
472
|
-
console.log("Error validating password for keystore");
|
|
473
|
-
} else {
|
|
474
|
-
throw new Error(`Failed to authenticate keystore [${keystoreId}] after ${maxAttempts} attempts: ${error.message}`);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
return passwords;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* 创建服务器实例
|
|
487
|
-
*/
|
|
488
|
-
private createServerInstance(config: SSHServiceConfig, passwords: { [keystoreId: string]: string }): any {
|
|
489
|
-
const clients = config.clients;
|
|
490
|
-
if (clients.length === 0) {
|
|
491
|
-
throw new Error("No clients configured for service");
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
if (clients.length > 1) {
|
|
495
|
-
throw new Error("Only one client is supported for this service");
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
const client = JSON.parse(clients[0] as any);
|
|
499
|
-
const allowedAppIds = [client.name];
|
|
500
|
-
const allowedPublicKeyPaths = [client.public_key_path];
|
|
501
|
-
const allowedKeystoreIds = [client.allowed_keystores];
|
|
502
|
-
|
|
503
|
-
const keystoreIds = config.keystore.allowed_keystores;
|
|
504
|
-
if (keystoreIds.length === 0) {
|
|
505
|
-
throw new Error("No keystores configured for service");
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
let keystorePasswords: any = [];
|
|
509
|
-
for (const keystoreId of keystoreIds) {
|
|
510
|
-
keystorePasswords.push(passwords[keystoreId]);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
return new WativeWielderServer(
|
|
514
|
-
config.server_keys.private_key_path,
|
|
515
|
-
config.server_keys.passphrase,
|
|
516
|
-
allowedAppIds,
|
|
517
|
-
allowedPublicKeyPaths,
|
|
518
|
-
allowedKeystoreIds,
|
|
519
|
-
config.keystore.path,
|
|
520
|
-
keystoreIds,
|
|
521
|
-
keystorePasswords
|
|
522
|
-
);
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
/**
|
|
526
|
-
* 显示日志
|
|
527
|
-
*/
|
|
528
|
-
public async showLogs(serviceName?: string, lines: number = 50, follow: boolean = false): Promise<void> {
|
|
529
|
-
const logDir = process.env.HOME + '/.wative/logs';
|
|
530
|
-
let logFile: string;
|
|
531
|
-
|
|
532
|
-
if (serviceName) {
|
|
533
|
-
logFile = path.join(logDir, `${serviceName}.log`);
|
|
534
|
-
} else {
|
|
535
|
-
logFile = path.join(logDir, 'service_manager.log');
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
if (!fs.existsSync(logFile)) {
|
|
539
|
-
throw new Error(`Log file not found: ${logFile}`);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
const args = follow ? ['-f', `-n${lines}`, logFile] : [`-n${lines}`, logFile];
|
|
543
|
-
const tailProcess = spawn('tail', args, {
|
|
544
|
-
stdio: ['ignore', 'inherit', 'inherit']
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
if (follow) {
|
|
548
|
-
// 如果是跟踪模式,等待用户中断
|
|
549
|
-
process.on('SIGINT', () => {
|
|
550
|
-
tailProcess.kill('SIGTERM');
|
|
551
|
-
process.exit(0);
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
return new Promise((resolve, reject) => {
|
|
555
|
-
tailProcess.on('close', (code) => {
|
|
556
|
-
if (code === 0) {
|
|
557
|
-
resolve();
|
|
558
|
-
} else {
|
|
559
|
-
reject(new Error(`tail process exited with code ${code}`));
|
|
560
|
-
}
|
|
561
|
-
});
|
|
562
|
-
});
|
|
563
|
-
} else {
|
|
564
|
-
return new Promise((resolve, reject) => {
|
|
565
|
-
tailProcess.on('close', (code) => {
|
|
566
|
-
if (code === 0) {
|
|
567
|
-
resolve();
|
|
568
|
-
} else {
|
|
569
|
-
reject(new Error(`tail process exited with code ${code}`));
|
|
570
|
-
}
|
|
571
|
-
});
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* 设置日志记录器
|
|
578
|
-
*/
|
|
579
|
-
private setupLogger(): void {
|
|
580
|
-
const logDir = process.env.HOME + '/.wative/logs';
|
|
581
|
-
if (!fs.existsSync(logDir)) {
|
|
582
|
-
fs.mkdirSync(logDir, { recursive: true });
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
log4js.configure({
|
|
586
|
-
appenders: {
|
|
587
|
-
file: {
|
|
588
|
-
type: 'file',
|
|
589
|
-
filename: path.join(logDir, 'service_manager.log'),
|
|
590
|
-
maxLogSize: 10485760,
|
|
591
|
-
backups: 3,
|
|
592
|
-
compress: true,
|
|
593
|
-
keepFileExt: true,
|
|
594
|
-
layout: {
|
|
595
|
-
type: 'pattern',
|
|
596
|
-
pattern: '%d{yyyy-MM-dd hh:mm:ss.SSS} [%p] %c - %m'
|
|
597
|
-
},
|
|
598
|
-
encoding: 'utf-8'
|
|
599
|
-
},
|
|
600
|
-
console: { type: 'console' }
|
|
601
|
-
},
|
|
602
|
-
categories: {
|
|
603
|
-
default: { appenders: ['file', 'console'], level: 'info' }
|
|
604
|
-
}
|
|
605
|
-
});
|
|
606
|
-
|
|
607
|
-
this.logger = log4js.getLogger('service_manager');
|
|
608
|
-
this.logger.level = 'info';
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
/**
|
|
612
|
-
* 设置服务特定的日志记录器
|
|
613
|
-
*/
|
|
614
|
-
private setupServiceLogger(serviceName: string): void {
|
|
615
|
-
const logDir = process.env.HOME + '/.wative/logs';
|
|
616
|
-
if (!fs.existsSync(logDir)) {
|
|
617
|
-
fs.mkdirSync(logDir, { recursive: true });
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
const serviceLogFile = path.join(logDir, `${serviceName}.log`);
|
|
621
|
-
|
|
622
|
-
// 清理旧日志文件
|
|
623
|
-
this.cleanupOldLogs(logDir);
|
|
624
|
-
|
|
625
|
-
log4js.configure({
|
|
626
|
-
appenders: {
|
|
627
|
-
file: {
|
|
628
|
-
type: 'file',
|
|
629
|
-
filename: path.join(logDir, 'service_manager.log'),
|
|
630
|
-
maxLogSize: 10485760,
|
|
631
|
-
backups: 3,
|
|
632
|
-
compress: true,
|
|
633
|
-
keepFileExt: true,
|
|
634
|
-
layout: {
|
|
635
|
-
type: 'pattern',
|
|
636
|
-
pattern: '%d{yyyy-MM-dd hh:mm:ss.SSS} [%p] %c - %m'
|
|
637
|
-
},
|
|
638
|
-
encoding: 'utf-8'
|
|
639
|
-
},
|
|
640
|
-
console: { type: 'console' },
|
|
641
|
-
[`${serviceName}_file`]: {
|
|
642
|
-
type: 'file',
|
|
643
|
-
filename: serviceLogFile,
|
|
644
|
-
maxLogSize: 10485760,
|
|
645
|
-
backups: 3,
|
|
646
|
-
compress: true,
|
|
647
|
-
keepFileExt: true,
|
|
648
|
-
layout: {
|
|
649
|
-
type: 'pattern',
|
|
650
|
-
pattern: '%d{yyyy-MM-dd hh:mm:ss.SSS} [%p] %c - %m'
|
|
651
|
-
},
|
|
652
|
-
encoding: 'utf-8'
|
|
653
|
-
}
|
|
654
|
-
},
|
|
655
|
-
categories: {
|
|
656
|
-
default: { appenders: ['file', 'console'], level: 'info' },
|
|
657
|
-
[serviceName]: { appenders: [`${serviceName}_file`], level: 'info' }
|
|
658
|
-
}
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
/**
|
|
663
|
-
* 清理超过3天的旧日志文件
|
|
664
|
-
*/
|
|
665
|
-
private cleanupOldLogs(logDir: string): void {
|
|
666
|
-
try {
|
|
667
|
-
const files = fs.readdirSync(logDir);
|
|
668
|
-
const threeDaysAgo = new Date();
|
|
669
|
-
threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
|
|
670
|
-
|
|
671
|
-
files.forEach(file => {
|
|
672
|
-
const filePath = path.join(logDir, file);
|
|
673
|
-
const stats = fs.statSync(filePath);
|
|
674
|
-
|
|
675
|
-
// 删除超过3天的日志文件
|
|
676
|
-
if (stats.mtime < threeDaysAgo && file.endsWith('.log')) {
|
|
677
|
-
fs.unlinkSync(filePath);
|
|
678
|
-
this.logger.info(`Deleted old log file: ${file}`);
|
|
679
|
-
}
|
|
680
|
-
});
|
|
681
|
-
} catch (error) {
|
|
682
|
-
this.logger.warn(`Failed to cleanup old logs: ${error}`);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
}
|