Files
certd/packages/ui/certd-server/src/plugins/plugin-host/lib/ssh.ts

239 lines
7.0 KiB
TypeScript
Raw Normal View History

2023-01-11 20:39:48 +08:00
// @ts-ignore
import ssh2, { ConnectConfig } from 'ssh2';
import path from 'path';
2024-07-15 00:30:33 +08:00
import * as _ from 'lodash-es';
import { ILogger } from '@certd/pipeline';
2024-06-27 16:38:43 +08:00
import iconv from 'iconv-lite';
2024-07-15 00:30:33 +08:00
import { SshAccess } from '../access/index.js';
2024-06-25 12:25:57 +08:00
export class AsyncSsh2Client {
conn: ssh2.Client;
logger: ILogger;
connConf: ssh2.ConnectConfig;
2024-07-15 00:30:33 +08:00
windows = false;
encoding: string;
2024-06-27 16:38:43 +08:00
constructor(connConf: SshAccess, logger: ILogger) {
2024-06-25 12:25:57 +08:00
this.connConf = connConf;
this.logger = logger;
2024-06-27 16:38:43 +08:00
this.windows = connConf.windows || false;
this.encoding = connConf.encoding;
}
convert(buffer: Buffer) {
2024-07-15 00:30:33 +08:00
if (this.encoding) {
2024-06-27 16:38:43 +08:00
return iconv.decode(buffer, this.encoding);
}
return buffer.toString();
2024-06-25 12:25:57 +08:00
}
async connect() {
this.logger.info(`开始连接,${this.connConf.host}:${this.connConf.port}`);
return new Promise((resolve, reject) => {
2024-07-18 21:10:13 +08:00
try {
const conn = new ssh2.Client();
conn
.on('error', (err: any) => {
this.logger.error('连接失败', err);
reject(err);
})
.on('ready', () => {
this.logger.info('连接成功');
this.conn = conn;
resolve(this.conn);
})
.connect(this.connConf);
} catch (e) {
reject(e);
}
2024-06-25 12:25:57 +08:00
});
}
async getSftp() {
return new Promise((resolve, reject) => {
2024-06-25 12:28:37 +08:00
this.logger.info('获取sftp');
2024-06-25 12:25:57 +08:00
this.conn.sftp((err: any, sftp: any) => {
if (err) {
reject(err);
return;
}
resolve(sftp);
});
});
}
async fastPut(options: { sftp: any; localPath: string; remotePath: string }) {
const { sftp, localPath, remotePath } = options;
return new Promise((resolve, reject) => {
2024-06-26 13:58:17 +08:00
this.logger.info(`开始上传:${localPath} => ${remotePath}`);
2024-06-25 12:25:57 +08:00
sftp.fastPut(localPath, remotePath, (err: Error) => {
if (err) {
reject(err);
return;
}
2024-06-26 13:58:17 +08:00
this.logger.info(`上传文件成功:${localPath} => ${remotePath}`);
2024-06-25 12:25:57 +08:00
resolve({});
});
});
}
async exec(script: string) {
2024-08-05 16:00:04 +08:00
if (!script) {
this.logger.info('script 为空,取消执行');
return;
}
2024-06-25 12:25:57 +08:00
return new Promise((resolve, reject) => {
2024-06-27 16:38:43 +08:00
this.logger.info(`执行命令:[${this.connConf.host}][exec]: ` + script);
2024-06-25 12:25:57 +08:00
this.conn.exec(script, (err: Error, stream: any) => {
if (err) {
reject(err);
return;
}
2024-07-15 00:30:33 +08:00
let data = '';
2024-06-25 12:25:57 +08:00
stream
.on('close', (code: any, signal: any) => {
this.logger.info(`[${this.connConf.host}][close]:code:${code}`);
if (code === 0) {
resolve(data);
} else {
reject(new Error(data));
}
})
2024-06-27 16:38:43 +08:00
.on('data', (ret: Buffer) => {
2024-07-15 00:30:33 +08:00
const out = this.convert(ret);
data += out;
2024-07-03 18:30:38 +08:00
this.logger.info(`[${this.connConf.host}][info]: ` + out.trimEnd());
2024-06-25 12:25:57 +08:00
})
2024-08-05 16:00:04 +08:00
.on('error', (err: any) => {
reject(err);
this.logger.error(err);
})
2024-07-15 00:30:33 +08:00
.stderr.on('data', (ret: Buffer) => {
const err = this.convert(ret);
data += err;
2024-07-18 21:10:13 +08:00
this.logger.info(`[${this.connConf.host}][error]: ` + err.trimEnd());
2024-06-25 12:25:57 +08:00
});
});
});
}
2024-06-25 12:28:37 +08:00
async shell(script: string | string[]): Promise<string[]> {
2024-06-25 12:25:57 +08:00
return new Promise<any>((resolve, reject) => {
2024-07-15 00:30:33 +08:00
this.logger.info(`执行shell脚本[${this.connConf.host}][shell]: ` + script);
2024-06-25 12:25:57 +08:00
this.conn.shell((err: Error, stream: any) => {
if (err) {
reject(err);
return;
}
const output: string[] = [];
stream
.on('close', () => {
this.logger.info('Stream :: close');
resolve(output);
})
2024-06-27 16:38:43 +08:00
.on('data', (ret: Buffer) => {
2024-07-15 00:30:33 +08:00
const data = this.convert(ret);
2024-06-25 12:25:57 +08:00
this.logger.info('' + data);
2024-06-27 16:38:43 +08:00
output.push(data);
})
2024-07-15 00:30:33 +08:00
.stderr.on('data', (ret: Buffer) => {
const data = this.convert(ret);
2024-06-27 16:38:43 +08:00
output.push(data);
this.logger.info(`[${this.connConf.host}][error]: ` + data);
2024-07-15 00:30:33 +08:00
});
2024-06-25 12:25:57 +08:00
stream.end(script + '\nexit\n');
});
});
}
end() {
if (this.conn) {
this.conn.end();
}
}
}
2024-08-28 14:40:50 +08:00
2022-11-07 23:31:20 +08:00
export class SshClient {
2023-01-11 20:39:48 +08:00
logger: ILogger;
constructor(logger: ILogger) {
2022-11-07 23:31:20 +08:00
this.logger = logger;
}
/**
*
* @param connectConf
{
host: '192.168.100.100',
port: 22,
username: 'frylock',
password: 'nodejsrules'
}
* @param options
*/
2024-07-18 21:10:13 +08:00
async uploadFiles(options: { connectConf: SshAccess; transports: any; mkdirs: boolean }) {
const { connectConf, transports, mkdirs } = options;
2024-06-25 12:25:57 +08:00
await this._call({
connectConf,
callable: async (conn: AsyncSsh2Client) => {
const sftp = await conn.getSftp();
2024-06-26 13:58:17 +08:00
this.logger.info('开始上传');
2024-06-25 12:25:57 +08:00
for (const transport of transports) {
if (mkdirs !== false) {
const filePath = path.dirname(transport.remotePath);
let mkdirCmd = `mkdir -p ${filePath} `;
if (conn.windows) {
if (filePath.indexOf('/') > -1) {
this.logger.info('--------------------------');
2024-07-18 21:10:13 +08:00
this.logger.info('请注意windows下文件目录分隔应该写成\\而不是/');
this.logger.info('--------------------------');
}
const spec = await conn.exec('echo %COMSPEC%');
if (spec.toString().trim() === '%COMSPEC%') {
mkdirCmd = `New-Item -ItemType Directory -Path "${filePath}" -Force`;
} else {
mkdirCmd = `if not exist "${filePath}" mkdir "${filePath}"`;
}
2024-07-03 18:30:38 +08:00
}
await conn.exec(mkdirCmd);
2024-06-27 16:38:43 +08:00
}
2024-06-25 12:25:57 +08:00
await conn.fastPut({ sftp, ...transport });
}
2024-06-26 13:58:17 +08:00
this.logger.info('文件全部上传成功');
2024-06-25 12:25:57 +08:00
},
2022-11-07 23:31:20 +08:00
});
}
2024-07-15 00:30:33 +08:00
async exec(options: { connectConf: SshAccess; script: string | Array<string> }) {
2022-11-07 23:31:20 +08:00
let { script } = options;
const { connectConf } = options;
if (_.isArray(script)) {
script = script as Array<string>;
script = script.join('\n');
2022-11-07 23:31:20 +08:00
}
this.logger.info('执行命令:', script);
2024-06-25 12:25:57 +08:00
return await this._call({
connectConf,
callable: async (conn: AsyncSsh2Client) => {
return await conn.exec(script as string);
},
2022-11-07 23:31:20 +08:00
});
}
2024-07-15 00:30:33 +08:00
async shell(options: { connectConf: SshAccess; script: string }): Promise<string[]> {
2022-11-07 23:31:20 +08:00
const { connectConf, script } = options;
2024-06-25 12:25:57 +08:00
return await this._call({
connectConf,
callable: async (conn: AsyncSsh2Client) => {
return await conn.shell(script as string);
},
2022-11-07 23:31:20 +08:00
});
}
2024-07-15 00:30:33 +08:00
async _call(options: { connectConf: SshAccess; callable: any }): Promise<string[]> {
2024-06-25 12:25:57 +08:00
const { connectConf, callable } = options;
const conn = new AsyncSsh2Client(connectConf, this.logger);
await conn.connect();
try {
return await callable(conn);
} finally {
conn.end();
}
2022-11-07 23:31:20 +08:00
}
}