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/package.json +23 -23
- 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
package/src/ssh/client.ts
DELETED
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
import { Client } from 'ssh2';
|
|
2
|
-
import { readFileSync } from 'fs';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export interface SSHAuthConfig {
|
|
6
|
-
username: string;
|
|
7
|
-
privateKeyPath: string;
|
|
8
|
-
publicKeyPath: string;
|
|
9
|
-
passphrase?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface SSHConfig {
|
|
13
|
-
// Wative service configuration
|
|
14
|
-
host: string;
|
|
15
|
-
port: number;
|
|
16
|
-
|
|
17
|
-
// Optional: specific client authentication for Wative service
|
|
18
|
-
// If not provided, will use the main auth configuration
|
|
19
|
-
clientAuth: SSHAuthConfig;
|
|
20
|
-
|
|
21
|
-
// Remote server deployment support
|
|
22
|
-
useRemoteServer?: boolean;
|
|
23
|
-
remoteServer?: {
|
|
24
|
-
host: string;
|
|
25
|
-
port?: number;
|
|
26
|
-
auth: SSHAuthConfig;
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface WativeRequest {
|
|
31
|
-
method: string;
|
|
32
|
-
keystoreId?: string;
|
|
33
|
-
chainId?: string;
|
|
34
|
-
params?: any;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface WativeResponse {
|
|
38
|
-
status: boolean;
|
|
39
|
-
data?: any;
|
|
40
|
-
msg?: string;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export class WativeSSHClient {
|
|
44
|
-
private config: SSHConfig;
|
|
45
|
-
|
|
46
|
-
constructor(config: SSHConfig) {
|
|
47
|
-
this.config = config;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Execute a command on the SSH server
|
|
52
|
-
* @param command The command to execute
|
|
53
|
-
* @returns Promise<string> The command output
|
|
54
|
-
*/
|
|
55
|
-
private async executeCommand(command: string): Promise<string> {
|
|
56
|
-
if (this.config.useRemoteServer) {
|
|
57
|
-
return this.executeCommandThroughRemoteServer(command);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return new Promise((resolve, reject) => {
|
|
63
|
-
const conn = new Client();
|
|
64
|
-
|
|
65
|
-
conn.on('ready', () => {
|
|
66
|
-
conn.exec(command, (err, stream) => {
|
|
67
|
-
if (err) {
|
|
68
|
-
conn.end();
|
|
69
|
-
return reject(err);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
let output = '';
|
|
73
|
-
let errorOutput = '';
|
|
74
|
-
|
|
75
|
-
stream.on('close', (code: number, signal: string) => {
|
|
76
|
-
conn.end();
|
|
77
|
-
if (code === 0) {
|
|
78
|
-
resolve(output);
|
|
79
|
-
} else {
|
|
80
|
-
reject(new Error(`Command failed with code ${code}: ${errorOutput || output}`));
|
|
81
|
-
}
|
|
82
|
-
}).on('data', (data: Buffer) => {
|
|
83
|
-
output += data.toString();
|
|
84
|
-
}).stderr.on('data', (data: Buffer) => {
|
|
85
|
-
errorOutput += data.toString();
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
}).on('error', (err) => {
|
|
89
|
-
reject(err);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// Connect with public key authentication
|
|
93
|
-
const authConfig = this.config.clientAuth;
|
|
94
|
-
const connectOptions: any = {
|
|
95
|
-
host: this.config.host,
|
|
96
|
-
port: this.config.port,
|
|
97
|
-
username: authConfig.username,
|
|
98
|
-
privateKey: readFileSync(authConfig.privateKeyPath),
|
|
99
|
-
passphrase: authConfig.passphrase
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
conn.connect(connectOptions);
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Execute a command through remote server
|
|
108
|
-
* @param command The command to execute
|
|
109
|
-
* @returns Promise<string> The command output
|
|
110
|
-
*/
|
|
111
|
-
private async executeCommandThroughRemoteServer(command: string): Promise<string> {
|
|
112
|
-
return new Promise((resolve, reject) => {
|
|
113
|
-
const remoteConn = new Client();
|
|
114
|
-
|
|
115
|
-
remoteConn.on('ready', () => {
|
|
116
|
-
// Create a tunnel through remote server to Wative service
|
|
117
|
-
remoteConn.forwardOut(
|
|
118
|
-
'127.0.0.1', 0, // source
|
|
119
|
-
this.config.host, this.config.port, // destination (Wative service)
|
|
120
|
-
(err, stream) => {
|
|
121
|
-
if (err) {
|
|
122
|
-
remoteConn.end();
|
|
123
|
-
return reject(err);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Create connection to Wative service through tunnel
|
|
127
|
-
const wativeConn = new Client();
|
|
128
|
-
|
|
129
|
-
wativeConn.on('ready', () => {
|
|
130
|
-
wativeConn.exec(command, (err, execStream) => {
|
|
131
|
-
if (err) {
|
|
132
|
-
wativeConn.end();
|
|
133
|
-
remoteConn.end();
|
|
134
|
-
return reject(err);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
let output = '';
|
|
138
|
-
let errorOutput = '';
|
|
139
|
-
|
|
140
|
-
execStream.on('close', (code: number, signal: string) => {
|
|
141
|
-
wativeConn.end();
|
|
142
|
-
remoteConn.end();
|
|
143
|
-
if (code === 0) {
|
|
144
|
-
resolve(output);
|
|
145
|
-
} else {
|
|
146
|
-
reject(new Error(`Command failed with code ${code}: ${errorOutput || output}`));
|
|
147
|
-
}
|
|
148
|
-
}).on('data', (data: Buffer) => {
|
|
149
|
-
output += data.toString();
|
|
150
|
-
}).stderr.on('data', (data: Buffer) => {
|
|
151
|
-
errorOutput += data.toString();
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
}).on('error', (err) => {
|
|
155
|
-
remoteConn.end();
|
|
156
|
-
reject(err);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
// Connect to Wative service through tunnel using client keys
|
|
160
|
-
const clientAuth = this.config.clientAuth;
|
|
161
|
-
const wativeConnectOptions: any = {
|
|
162
|
-
sock: stream,
|
|
163
|
-
username: clientAuth.username,
|
|
164
|
-
privateKey: readFileSync(clientAuth.privateKeyPath),
|
|
165
|
-
passphrase: clientAuth.passphrase
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
wativeConn.connect(wativeConnectOptions);
|
|
169
|
-
}
|
|
170
|
-
);
|
|
171
|
-
}).on('error', (err) => {
|
|
172
|
-
reject(err);
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
// Connect to remote server using remote server keys
|
|
176
|
-
const remoteConnectOptions: any = {
|
|
177
|
-
host: this.config.remoteServer!.host,
|
|
178
|
-
port: this.config.remoteServer!.port || 22,
|
|
179
|
-
username: this.config.remoteServer!.auth.username,
|
|
180
|
-
privateKey: readFileSync(this.config.remoteServer!.auth.privateKeyPath),
|
|
181
|
-
passphrase: this.config.remoteServer!.auth.passphrase
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
remoteConn.connect(remoteConnectOptions);
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Send a request to the Wative server
|
|
192
|
-
* @param request The request object
|
|
193
|
-
* @returns Promise<WativeResponse> The server response
|
|
194
|
-
*/
|
|
195
|
-
async sendRequest(request: WativeRequest): Promise<WativeResponse> {
|
|
196
|
-
try {
|
|
197
|
-
const command = JSON.stringify(request);
|
|
198
|
-
|
|
199
|
-
const output = await this.executeCommand(command);
|
|
200
|
-
|
|
201
|
-
// Parse the JSON response
|
|
202
|
-
const response: WativeResponse = JSON.parse(output);
|
|
203
|
-
|
|
204
|
-
if (!response.status) {
|
|
205
|
-
throw new Error(response.msg || 'Request failed');
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return response;
|
|
209
|
-
} catch (error) {
|
|
210
|
-
if (error instanceof Error) {
|
|
211
|
-
throw new Error(`SSH request failed: ${error.message}`);
|
|
212
|
-
}
|
|
213
|
-
throw error;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Get root account for a keystore and chain
|
|
219
|
-
* @param keystoreId The keystore ID
|
|
220
|
-
* @param chainId The chain ID
|
|
221
|
-
* @returns Promise<any> The root account data
|
|
222
|
-
*/
|
|
223
|
-
async getRootAccount(keystoreId: string, chainId: string): Promise<any> {
|
|
224
|
-
const request: WativeRequest = {
|
|
225
|
-
method: 'get_root_account',
|
|
226
|
-
keystoreId,
|
|
227
|
-
chainId
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
const response = await this.sendRequest(request);
|
|
231
|
-
return response.data;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Get sub accounts for a keystore and chain
|
|
236
|
-
* @param keystoreId The keystore ID
|
|
237
|
-
* @param chainId The chain ID
|
|
238
|
-
* @param startIndex Optional start index
|
|
239
|
-
* @param endIndex Optional end index
|
|
240
|
-
* @returns Promise<any> The sub account data
|
|
241
|
-
*/
|
|
242
|
-
async getSubAccount(keystoreId: string, chainId: string, startIndex?: number, endIndex?: number): Promise<any> {
|
|
243
|
-
const request: WativeRequest = {
|
|
244
|
-
method: 'get_sub_account',
|
|
245
|
-
params: {
|
|
246
|
-
keystoreId,
|
|
247
|
-
chainId,
|
|
248
|
-
startIndex,
|
|
249
|
-
endIndex
|
|
250
|
-
}
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
const response = await this.sendRequest(request);
|
|
254
|
-
return response.data;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Sign a transaction
|
|
259
|
-
* @param txParams Transaction parameters
|
|
260
|
-
* @param rpcUrl RPC URL
|
|
261
|
-
* @returns Promise<any> The signed transaction
|
|
262
|
-
*/
|
|
263
|
-
async signTransaction(txParams: any, rpcUrl: string): Promise<any> {
|
|
264
|
-
const request: WativeRequest = {
|
|
265
|
-
method: 'sign',
|
|
266
|
-
params: {
|
|
267
|
-
txParams,
|
|
268
|
-
rpcUrl
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
const response = await this.sendRequest(request);
|
|
273
|
-
return response.data;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
/**
|
|
277
|
-
* Sign and send a transaction
|
|
278
|
-
* @param txParams Transaction parameters
|
|
279
|
-
* @param rpcUrl RPC URL
|
|
280
|
-
* @returns Promise<any> The transaction hash
|
|
281
|
-
*/
|
|
282
|
-
async signAndSendTransaction(txParams: any, rpcUrl: string): Promise<any> {
|
|
283
|
-
const request: WativeRequest = {
|
|
284
|
-
method: 'sign_and_send',
|
|
285
|
-
params: {
|
|
286
|
-
txParams,
|
|
287
|
-
rpcUrl
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
const response = await this.sendRequest(request);
|
|
292
|
-
return response.data;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Sign a Solana transaction
|
|
297
|
-
* @param txParams Transaction parameters
|
|
298
|
-
* @param rpcUrl RPC URL
|
|
299
|
-
* @param priorityFee Priority fee
|
|
300
|
-
* @returns Promise<any> The signed transaction
|
|
301
|
-
*/
|
|
302
|
-
async signSolanaTransaction(txParams: any, rpcUrl: string, priorityFee?: any): Promise<any> {
|
|
303
|
-
const request: WativeRequest = {
|
|
304
|
-
method: 'solana_sign',
|
|
305
|
-
params: {
|
|
306
|
-
txParams,
|
|
307
|
-
rpcUrl,
|
|
308
|
-
priority_fee: priorityFee
|
|
309
|
-
}
|
|
310
|
-
};
|
|
311
|
-
|
|
312
|
-
const response = await this.sendRequest(request);
|
|
313
|
-
return response.data;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Send a signed Solana transaction
|
|
318
|
-
* @param txParams Transaction parameters with signature
|
|
319
|
-
* @param rpcUrl RPC URL
|
|
320
|
-
* @returns Promise<any> The transaction result
|
|
321
|
-
*/
|
|
322
|
-
async sendSignedSolanaTransaction(txParams: any, rpcUrl: string): Promise<any> {
|
|
323
|
-
const request: WativeRequest = {
|
|
324
|
-
method: 'solana_send',
|
|
325
|
-
params: {
|
|
326
|
-
txParams,
|
|
327
|
-
rpcUrl
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
const response = await this.sendRequest(request);
|
|
332
|
-
return response.data;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Sign a message
|
|
337
|
-
* @param account The account to sign with
|
|
338
|
-
* @param message The message to sign
|
|
339
|
-
* @returns Promise<any> The signature
|
|
340
|
-
*/
|
|
341
|
-
async signMessage(account: string, message: string): Promise<any> {
|
|
342
|
-
const request: WativeRequest = {
|
|
343
|
-
method: 'sign_message',
|
|
344
|
-
params: {
|
|
345
|
-
account,
|
|
346
|
-
message
|
|
347
|
-
}
|
|
348
|
-
};
|
|
349
|
-
|
|
350
|
-
const response = await this.sendRequest(request);
|
|
351
|
-
return response.data;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
/**
|
|
355
|
-
* Sign typed data (EIP-712)
|
|
356
|
-
* @param account The account to sign with
|
|
357
|
-
* @param domain The domain
|
|
358
|
-
* @param typesName The types name
|
|
359
|
-
* @param types The types
|
|
360
|
-
* @param message The message
|
|
361
|
-
* @returns Promise<any> The signature
|
|
362
|
-
*/
|
|
363
|
-
async signTypedData(account: string, domain: string, typesName: string, types: string, message: string): Promise<any> {
|
|
364
|
-
const request: WativeRequest = {
|
|
365
|
-
method: 'sign_typed_data',
|
|
366
|
-
params: {
|
|
367
|
-
account,
|
|
368
|
-
domain,
|
|
369
|
-
types_name: typesName,
|
|
370
|
-
types,
|
|
371
|
-
message
|
|
372
|
-
}
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
const response = await this.sendRequest(request);
|
|
376
|
-
return response.data;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Create a new Wative SSH client instance
|
|
382
|
-
* @param config SSH configuration
|
|
383
|
-
* @returns WativeSSHClient instance
|
|
384
|
-
*/
|
|
385
|
-
export function createWativeSSHClient(config: SSHConfig): WativeSSHClient {
|
|
386
|
-
return new WativeSSHClient(config);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
export default WativeSSHClient;
|
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import * as ini from 'ini';
|
|
4
|
-
import * as crypto from 'crypto';
|
|
5
|
-
import { SSHServiceConfig } from './types';
|
|
6
|
-
import { execSync } from 'child_process';
|
|
7
|
-
|
|
8
|
-
export class SSHConfigManager {
|
|
9
|
-
private configDir: string;
|
|
10
|
-
private keystorePath: string;
|
|
11
|
-
|
|
12
|
-
constructor(configDir?: string, keystorePath?: string) {
|
|
13
|
-
this.configDir = configDir || path.join(require('os').homedir(), '.wative/ssh');
|
|
14
|
-
this.keystorePath = keystorePath || path.join(require('os').homedir(), '.wative');
|
|
15
|
-
|
|
16
|
-
if (!fs.existsSync(this.configDir)) {
|
|
17
|
-
fs.mkdirSync(this.configDir, { recursive: true });
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Create conf.d directory for service configurations
|
|
21
|
-
const confDir = path.join(this.configDir, 'conf.d');
|
|
22
|
-
if (!fs.existsSync(confDir)) {
|
|
23
|
-
fs.mkdirSync(confDir, { recursive: true });
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* 获取 keystore 路径
|
|
29
|
-
*/
|
|
30
|
-
public getKeystorePath(): string {
|
|
31
|
-
return this.keystorePath;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* 获取配置目录路径
|
|
36
|
-
*/
|
|
37
|
-
public getConfigDir(): string {
|
|
38
|
-
return this.configDir;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* 创建新的 SSH 服务配置
|
|
43
|
-
*/
|
|
44
|
-
public async createServiceConfig(
|
|
45
|
-
serviceName: string,
|
|
46
|
-
port: number,
|
|
47
|
-
allowedKeystores: string[],
|
|
48
|
-
clientName: string = 'default_client',
|
|
49
|
-
description?: string
|
|
50
|
-
): Promise<SSHServiceConfig> {
|
|
51
|
-
// 验证服务名称
|
|
52
|
-
if (!this.isValidServiceName(serviceName)) {
|
|
53
|
-
throw new Error('Invalid service name. Use only alphanumeric characters, hyphens, and underscores.');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// 检查端口是否已被使用
|
|
57
|
-
if (this.isPortInUse(port)) {
|
|
58
|
-
throw new Error(`Port ${port} is already in use by another service.`);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// 生成服务专用的密钥对
|
|
62
|
-
const serverKeyPaths = await this.generateServiceKeyPair(serviceName);
|
|
63
|
-
const clientKeyPaths = await this.generateClientKeyPair(serviceName, clientName);
|
|
64
|
-
|
|
65
|
-
const config: SSHServiceConfig = {
|
|
66
|
-
name: serviceName,
|
|
67
|
-
description: description || `Wative SSH Service - ${serviceName}`,
|
|
68
|
-
ssh: {
|
|
69
|
-
listen: '127.0.0.1',
|
|
70
|
-
port: port,
|
|
71
|
-
log: {
|
|
72
|
-
size: 10485760,
|
|
73
|
-
backups: 3
|
|
74
|
-
}
|
|
75
|
-
},
|
|
76
|
-
keystore: {
|
|
77
|
-
path: this.keystorePath,
|
|
78
|
-
allowed_keystores: allowedKeystores
|
|
79
|
-
},
|
|
80
|
-
clients: [{
|
|
81
|
-
name: clientName,
|
|
82
|
-
public_key_path: clientKeyPaths.publicKey,
|
|
83
|
-
allowed_keystores: allowedKeystores
|
|
84
|
-
}],
|
|
85
|
-
server_keys: {
|
|
86
|
-
private_key_path: serverKeyPaths.privateKey,
|
|
87
|
-
passphrase: serverKeyPaths.passphrase
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
// 保存配置文件
|
|
92
|
-
this.saveServiceConfig(serviceName, config);
|
|
93
|
-
|
|
94
|
-
return config;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* 加载服务配置
|
|
99
|
-
*/
|
|
100
|
-
public loadServiceConfig(serviceName: string): SSHServiceConfig {
|
|
101
|
-
const configPath = this.getServiceConfigPath(serviceName);
|
|
102
|
-
|
|
103
|
-
if (!fs.existsSync(configPath)) {
|
|
104
|
-
throw new Error(`Service configuration '${serviceName}' not found.`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const configData = ini.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
108
|
-
return this.parseConfigData(configData);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
public serviceIsExist(serviceName: string): boolean {
|
|
112
|
-
const configPath = this.getServiceConfigPath(serviceName);
|
|
113
|
-
return fs.existsSync(path.dirname(configPath));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* 获取所有服务配置列表
|
|
118
|
-
*/
|
|
119
|
-
public listServiceConfigs(): string[] {
|
|
120
|
-
const confDir = path.join(this.configDir, 'conf.d');
|
|
121
|
-
|
|
122
|
-
if (!fs.existsSync(confDir)) {
|
|
123
|
-
return [];
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const serviceDirs = fs.readdirSync(confDir)
|
|
127
|
-
.filter(item => {
|
|
128
|
-
const itemPath = path.join(confDir, item);
|
|
129
|
-
return fs.statSync(itemPath).isDirectory() &&
|
|
130
|
-
fs.existsSync(path.join(itemPath, 'config.ini'));
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
return serviceDirs;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* 删除服务配置
|
|
138
|
-
*/
|
|
139
|
-
public removeServiceConfig(serviceName: string): void {
|
|
140
|
-
const configPath = this.getServiceConfigPath(serviceName);
|
|
141
|
-
const configDir = path.dirname(configPath);
|
|
142
|
-
if (fs.existsSync(configDir)) {
|
|
143
|
-
fs.rmSync(configDir, { recursive: true });
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* 更新服务配置
|
|
149
|
-
*/
|
|
150
|
-
public updateServiceConfig(serviceName: string, updates: Partial<SSHServiceConfig>): void {
|
|
151
|
-
const config = this.loadServiceConfig(serviceName);
|
|
152
|
-
const updatedConfig = { ...config, ...updates };
|
|
153
|
-
this.saveServiceConfig(serviceName, updatedConfig);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* 验证 keystore 密码
|
|
158
|
-
*/
|
|
159
|
-
public async validateKeystorePasswords(
|
|
160
|
-
keystoreIds: string[],
|
|
161
|
-
passwords: { [keystoreId: string]: string }
|
|
162
|
-
): Promise<{ [keystoreId: string]: boolean }> {
|
|
163
|
-
const results: { [keystoreId: string]: boolean } = {};
|
|
164
|
-
|
|
165
|
-
// 这里需要集成 wative-core 的密码验证逻辑
|
|
166
|
-
const { WativeCore } = require('wative-core');
|
|
167
|
-
|
|
168
|
-
for (const keystoreId of keystoreIds) {
|
|
169
|
-
try {
|
|
170
|
-
const password = passwords[keystoreId];
|
|
171
|
-
if (!password) {
|
|
172
|
-
results[keystoreId] = false;
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// 尝试创建 WativeCore 实例来验证密码
|
|
177
|
-
const wativeCore = new WativeCore(this.keystorePath, [keystoreId], [password]);
|
|
178
|
-
results[keystoreId] = wativeCore.account.isLogin(keystoreId);
|
|
179
|
-
} catch (error) {
|
|
180
|
-
results[keystoreId] = false;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return results;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* 生成默认配置(向后兼容)
|
|
189
|
-
*/
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
private isValidServiceName(name: string): boolean {
|
|
193
|
-
return /^[a-zA-Z0-9_-]+$/.test(name) && name.length > 0 && name.length <= 50;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
private isPortInUse(port: number): boolean {
|
|
197
|
-
const configs = this.listServiceConfigs();
|
|
198
|
-
|
|
199
|
-
for (const configName of configs) {
|
|
200
|
-
try {
|
|
201
|
-
const config = this.loadServiceConfig(configName);
|
|
202
|
-
if (config.ssh.port === port) {
|
|
203
|
-
return true;
|
|
204
|
-
}
|
|
205
|
-
} catch (error) {
|
|
206
|
-
// 忽略无法加载的配置文件
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return false;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
private async generateServiceKeyPair(serviceName: string): Promise<{ privateKey: string; publicKey: string; passphrase: string }> {
|
|
214
|
-
const passphrase = this.generateSecurePassphrase();
|
|
215
|
-
const serviceDir = path.join(this.configDir, 'conf.d', serviceName);
|
|
216
|
-
|
|
217
|
-
// Create service directory if it doesn't exist
|
|
218
|
-
if (!fs.existsSync(serviceDir)) {
|
|
219
|
-
fs.mkdirSync(serviceDir, { recursive: true });
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const privateKeyPath = path.join(serviceDir, 'server_private_key');
|
|
223
|
-
const publicKeyPath = path.join(serviceDir, 'server_public_key.pub');
|
|
224
|
-
|
|
225
|
-
if (!fs.existsSync(privateKeyPath)) {
|
|
226
|
-
console.log(`Generating SSH server key pair for service '${serviceName}'...`);
|
|
227
|
-
execSync(`ssh-keygen -t rsa -b 4096 -f ${privateKeyPath} -N '${passphrase}' -C 'wative_${serviceName}_server'`, { stdio: 'inherit' });
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return {
|
|
231
|
-
privateKey: privateKeyPath,
|
|
232
|
-
publicKey: publicKeyPath,
|
|
233
|
-
passphrase: passphrase
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
private async generateClientKeyPair(serviceName: string, clientName: string): Promise<{ privateKey: string; publicKey: string; passphrase: string }> {
|
|
238
|
-
const serviceDir = path.join(this.configDir, 'conf.d', serviceName);
|
|
239
|
-
|
|
240
|
-
// Create service directory if it doesn't exist
|
|
241
|
-
if (!fs.existsSync(serviceDir)) {
|
|
242
|
-
fs.mkdirSync(serviceDir, { recursive: true });
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const publicKeyPath = path.join(serviceDir, `${clientName}.pub`);
|
|
246
|
-
|
|
247
|
-
// Note: We don't generate client private key here, only specify the public key path
|
|
248
|
-
// The client public key should be provided by the user
|
|
249
|
-
return {
|
|
250
|
-
privateKey: '', // Client private key is not stored on server
|
|
251
|
-
publicKey: publicKeyPath,
|
|
252
|
-
passphrase: '' // No passphrase needed for public key
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
private generateSecurePassphrase(): string {
|
|
257
|
-
return crypto.randomBytes(32).toString('hex');
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
private getServiceConfigPath(serviceName: string): string {
|
|
261
|
-
return path.join(this.configDir, 'conf.d', serviceName, 'config.ini');
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
private saveServiceConfig(serviceName: string, config: SSHServiceConfig): void {
|
|
265
|
-
const configPath = this.getServiceConfigPath(serviceName);
|
|
266
|
-
const configDir = path.dirname(configPath);
|
|
267
|
-
|
|
268
|
-
// Ensure the service directory exists
|
|
269
|
-
if (!fs.existsSync(configDir)) {
|
|
270
|
-
fs.mkdirSync(configDir, { recursive: true });
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const configText = ini.stringify(config);
|
|
274
|
-
fs.writeFileSync(configPath, configText);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
private parseConfigData(configData: any): SSHServiceConfig {
|
|
278
|
-
return {
|
|
279
|
-
name: configData.name,
|
|
280
|
-
description: configData.description,
|
|
281
|
-
ssh: {
|
|
282
|
-
listen: configData.ssh.listen,
|
|
283
|
-
port: parseInt(configData.ssh.port),
|
|
284
|
-
log: {
|
|
285
|
-
size: parseInt(configData.ssh.log.size),
|
|
286
|
-
backups: parseInt(configData.ssh.log.backups)
|
|
287
|
-
}
|
|
288
|
-
},
|
|
289
|
-
keystore: {
|
|
290
|
-
path: configData.keystore.path,
|
|
291
|
-
allowed_keystores: Array.isArray(configData.keystore.allowed_keystores)
|
|
292
|
-
? configData.keystore.allowed_keystores
|
|
293
|
-
: configData.keystore.allowed_keystores.split(',')
|
|
294
|
-
},
|
|
295
|
-
clients: Array.isArray(configData.clients)
|
|
296
|
-
? configData.clients
|
|
297
|
-
: [configData.clients],
|
|
298
|
-
server_keys: {
|
|
299
|
-
private_key_path: configData.server_keys.private_key_path,
|
|
300
|
-
passphrase: configData.server_keys.passphrase
|
|
301
|
-
}
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
}
|