ssh -L
This commit is contained in:
commit
8b136d1a5a
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
coverage
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
/logs
|
||||||
|
|
17
ecosystem.config.cjs
Normal file
17
ecosystem.config.cjs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: 'dev-app',
|
||||||
|
script: 'tsx ./src/index.ts',
|
||||||
|
log_date_format: 'YYYY-MM-DD HH:mm Z',
|
||||||
|
output: './logs/dev-app.log', // 所有日志输出到这里
|
||||||
|
error: './logs/dev-app.log', // 错误日志也写到同一个文件
|
||||||
|
log_date_format: 'YYYY-MM-DD HH:mm:ss',
|
||||||
|
watch: false,
|
||||||
|
env: {
|
||||||
|
PM2_COLOR: 'true', // 开启彩色日志
|
||||||
|
},
|
||||||
|
merge_logs: true, // 合并日志流
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
30
package.json
Normal file
30
package.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "dev-app",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"app": {
|
||||||
|
"type": "micro-app"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "abearxiong <xiongxiao@xiongxiao.me>",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"packageManager": "pnpm@9.8.0+sha512.8e4c3550fb500e808dbc30bb0ce4dd1eb614e30b1c55245f211591ec2cdf9c611cabd34e1364b42f564bd54b3945ed0f49d61d1bbf2ec9bd74b866fcdc723276",
|
||||||
|
"devDependencies": {
|
||||||
|
"@kevisual/router": "0.0.6-alpha-2",
|
||||||
|
"@kevisual/types": "^0.0.1",
|
||||||
|
"@kevisual/use-config": "^1.0.4",
|
||||||
|
"@types/node": "^22.10.1",
|
||||||
|
"typescript": "^5.7.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"esbuild": "^0.24.0",
|
||||||
|
"nanoid": "^5.0.9",
|
||||||
|
"sequelize": "^6.37.5",
|
||||||
|
"sqlite3": "^5.1.7"
|
||||||
|
}
|
||||||
|
}
|
1554
pnpm-lock.yaml
generated
Normal file
1554
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
5
src/app.ts
Normal file
5
src/app.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { App } from '@kevisual/router';
|
||||||
|
|
||||||
|
export const app = new App();
|
||||||
|
|
||||||
|
app.listen(9998)
|
15
src/child/run.ts
Normal file
15
src/child/run.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { spawn, spawnSync } from 'child_process';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const devPath = '/Users/xion/on/on-ai/packages/parsex-extensions';
|
||||||
|
export const runDev = () => {
|
||||||
|
const a = spawnSync('vite build --watch', {
|
||||||
|
stdio: 'inherit',
|
||||||
|
shell: true,
|
||||||
|
cwd: path.resolve(devPath),
|
||||||
|
env: process.env,
|
||||||
|
});
|
||||||
|
console.log('a===', a);
|
||||||
|
};
|
||||||
|
|
||||||
|
runDev();
|
11
src/index.ts
Normal file
11
src/index.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { app } from './app.ts';
|
||||||
|
import './route/ssh/list.ts';
|
||||||
|
|
||||||
|
app
|
||||||
|
.call({
|
||||||
|
path: 'ssh',
|
||||||
|
key: 'list',
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
console.log(res);
|
||||||
|
});
|
42
src/lib/build/a.ts
Normal file
42
src/lib/build/a.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { build } from 'esbuild';
|
||||||
|
import { writeFileSync } from 'fs';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
|
export async function transformToMJS(inputCode) {
|
||||||
|
// 1. 使用 esbuild 转换代码
|
||||||
|
const result = await build({
|
||||||
|
stdin: {
|
||||||
|
contents: inputCode,
|
||||||
|
resolveDir: process.cwd(), // 当前目录为基准路径,用于解析文件路径
|
||||||
|
loader: 'ts',
|
||||||
|
},
|
||||||
|
format: 'esm', // 输出为 ESM 格式
|
||||||
|
outfile: 'output.mjs', // 输出文件路径
|
||||||
|
write: false, // 不直接写入文件
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 将结果保存为 .mjs 文件
|
||||||
|
const outputPath = resolve('./output.mjs');
|
||||||
|
writeFileSync(outputPath, result.outputFiles[0].text);
|
||||||
|
|
||||||
|
console.log(`转换完成,文件已保存到: ${outputPath}`);
|
||||||
|
return outputPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 示例字符串代码
|
||||||
|
const inputCode = `
|
||||||
|
import path from 'path';
|
||||||
|
import {nanoid} from 'nanoid';
|
||||||
|
console.log(path.resolve(), nanoid(6));
|
||||||
|
`;
|
||||||
|
|
||||||
|
const runCode = (filePath: string) => {
|
||||||
|
const mjs = import(filePath);
|
||||||
|
//
|
||||||
|
};
|
||||||
|
transformToMJS(inputCode)
|
||||||
|
.then((outputPath) => {
|
||||||
|
console.log(`生成的 MJS 文件路径: ${outputPath}`);
|
||||||
|
runCode(outputPath);
|
||||||
|
})
|
||||||
|
.catch(console.error);
|
3
src/lib/ssh.ts
Normal file
3
src/lib/ssh.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { SSHManager } from './ssh/manager.ts';
|
||||||
|
|
||||||
|
export const sshManager = new SSHManager();
|
46
src/lib/ssh/manager.ts
Normal file
46
src/lib/ssh/manager.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { SSHServer, SSHValue } from './ssh.ts';
|
||||||
|
|
||||||
|
export class SSHManager {
|
||||||
|
servers: SSHServer[] = [];
|
||||||
|
constructor() {}
|
||||||
|
addServer(server: SSHServer) {
|
||||||
|
this.servers.push(server);
|
||||||
|
}
|
||||||
|
getServers() {
|
||||||
|
return this.servers.map((server) => {
|
||||||
|
return {
|
||||||
|
name: server.name,
|
||||||
|
isRunning: server.isRunning,
|
||||||
|
values: server.values,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
createServer(remote: string, values: SSHValue[]) {
|
||||||
|
if (!remote) {
|
||||||
|
console.error('remote 不能为空');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const has = this.servers.find((server) => server.name === remote);
|
||||||
|
let flag = false;
|
||||||
|
if (has && has.isRunning) {
|
||||||
|
flag = true;
|
||||||
|
has.stop();
|
||||||
|
}
|
||||||
|
const server = new SSHServer({ name: remote, values });
|
||||||
|
if (flag) {
|
||||||
|
setTimeout(() => {
|
||||||
|
server.start(); // 重新启动
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
server.start();
|
||||||
|
}
|
||||||
|
this.addServer(server);
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
stopServer(remote: string) {
|
||||||
|
const server = this.servers.find((server) => server.name === remote);
|
||||||
|
if (server) {
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
src/lib/ssh/ssh.ts
Normal file
103
src/lib/ssh/ssh.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// ssh -L 8080:localhost:80 -L 9090:localhost:90 user@remote-server
|
||||||
|
import { ChildProcess, spawn } from 'child_process';
|
||||||
|
type Options = {
|
||||||
|
name: string;
|
||||||
|
values: SSHValue[];
|
||||||
|
};
|
||||||
|
export type SSHValue = {
|
||||||
|
localPort: number;
|
||||||
|
remoteHost: string; // localhost
|
||||||
|
remotePort: number;
|
||||||
|
remote?: string; //sky config配置
|
||||||
|
status?: 'active' | 'inactive';
|
||||||
|
};
|
||||||
|
export const demos: SSHValue[] = [
|
||||||
|
{
|
||||||
|
localPort: 3000,
|
||||||
|
remoteHost: 'localhost',
|
||||||
|
remotePort: 3000,
|
||||||
|
remote: 'diana',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
localPort: 5244,
|
||||||
|
remoteHost: 'localhost',
|
||||||
|
remotePort: 5244,
|
||||||
|
remote: 'diana',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
export class SSHServer {
|
||||||
|
name = 'diana';
|
||||||
|
values: SSHValue[] = [];
|
||||||
|
childProcess: ChildProcess | null = null;
|
||||||
|
isRunning = false;
|
||||||
|
constructor(opts?: Options) {
|
||||||
|
this.name = opts?.name || this.name;
|
||||||
|
this.values = opts?.values || demos;
|
||||||
|
}
|
||||||
|
start(options?: Options) {
|
||||||
|
try {
|
||||||
|
const remote = options?.name || this.name;
|
||||||
|
const demos = options?.values || this.values;
|
||||||
|
const _port = demos.filter((item) => item.status === 'active');
|
||||||
|
if (_port.length === 0) {
|
||||||
|
this.isRunning = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const params = _port
|
||||||
|
.map((item) => {
|
||||||
|
return ['-L', `${item.localPort}:${item.remoteHost}:${item.remotePort}`];
|
||||||
|
})
|
||||||
|
.flat();
|
||||||
|
const childProcess = spawn(
|
||||||
|
'ssh',
|
||||||
|
[
|
||||||
|
'-N', // 不执行远程命令
|
||||||
|
// '-f', // 后台运行
|
||||||
|
'-o',
|
||||||
|
'ServerAliveInterval=60', // 每60秒发送一次心跳包
|
||||||
|
'-o',
|
||||||
|
'ServerAliveCountMax=3', // 尝试3次心跳失败后断开连接
|
||||||
|
'-T',
|
||||||
|
...params,
|
||||||
|
remote,
|
||||||
|
],
|
||||||
|
{
|
||||||
|
// stdio: 'ignore',
|
||||||
|
stdio: 'inherit',
|
||||||
|
killSignal: 'SIGINT',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
this.childProcess = childProcess;
|
||||||
|
childProcess.stdout?.on('data', (data) => {
|
||||||
|
console.log('childProcess', data.toString());
|
||||||
|
});
|
||||||
|
childProcess.on('error', (err: any) => {
|
||||||
|
if (err.code === 'EADDRINUSE') {
|
||||||
|
console.error('端口被占用:', err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.error('Failed to start SSH process:', err);
|
||||||
|
});
|
||||||
|
childProcess.on('exit', (code, signal) => {
|
||||||
|
console.log('SSH process exited:', code, signal);
|
||||||
|
this.isRunning = false;
|
||||||
|
});
|
||||||
|
this.isRunning = true;
|
||||||
|
console.log('当前ssh -L 服务启动', this.name);
|
||||||
|
// 监听的端口是
|
||||||
|
// console.log('监听的端口是:', _port.map((item) => `[${item.localPort},${item.remotePort}]`).join(','));
|
||||||
|
console.log('监听的端口是:', _port.map((item) => item.localPort).join(','));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('启动失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stop() {
|
||||||
|
if (this.childProcess) {
|
||||||
|
console.log('正在停止 ssh -L 服务:', this.name);
|
||||||
|
this.childProcess.kill(); // 发送默认信号 SIGTERM
|
||||||
|
this.childProcess = undefined;
|
||||||
|
} else {
|
||||||
|
console.log('没有正在运行的 ssh -L 服务.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
src/modules/get-config.ts
Normal file
47
src/modules/get-config.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import os from 'os';
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
export const envisionPath = path.join(os.homedir(), '.config', 'envision');
|
||||||
|
const configPath = path.join(os.homedir(), '.config', 'envision', 'config.json');
|
||||||
|
|
||||||
|
export const pidFilePath = path.join(envisionPath, 'app.pid');
|
||||||
|
export const dbPath = path.join(envisionPath, 'db.sqlite');
|
||||||
|
const envisionPidDir = path.join(envisionPath);
|
||||||
|
export const getPidList = () => {
|
||||||
|
const files = fs.readdirSync(envisionPidDir);
|
||||||
|
const pidFiles = files.filter((file) => file.endsWith('.pid'));
|
||||||
|
return pidFiles.map((file) => {
|
||||||
|
const pid = fs.readFileSync(path.join(envisionPidDir, file), 'utf-8');
|
||||||
|
return { pid, file: path.join(envisionPidDir, file) };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export const writeVitePid = (pid: number) => {
|
||||||
|
fs.writeFileSync(path.join(envisionPath, `vite-${pid}.pid`), pid.toString());
|
||||||
|
};
|
||||||
|
export const checkFileExists = (filePath: string) => {
|
||||||
|
try {
|
||||||
|
fs.accessSync(filePath, fs.constants.F_OK);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const getConfig = () => {
|
||||||
|
if (!checkFileExists(envisionPath)) {
|
||||||
|
fs.mkdirSync(envisionPath, { recursive: true });
|
||||||
|
}
|
||||||
|
if (checkFileExists(configPath)) {
|
||||||
|
const config = fs.readFileSync(configPath, 'utf-8');
|
||||||
|
try {
|
||||||
|
return JSON.parse(config);
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const writeConfig = (config: Record<string, any>) => {
|
||||||
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||||
|
};
|
16
src/modules/sequelize.ts
Normal file
16
src/modules/sequelize.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Sequelize } from 'sequelize';
|
||||||
|
import { dbPath } from './get-config.ts';
|
||||||
|
|
||||||
|
// connect to db
|
||||||
|
export const sequelize = new Sequelize({
|
||||||
|
dialect: 'sqlite',
|
||||||
|
storage: dbPath,
|
||||||
|
// logging: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
sequelize
|
||||||
|
.authenticate({ logging: false })
|
||||||
|
.then(() => {})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error('Unable to connect to the database:', err);
|
||||||
|
});
|
60
src/route/dev/model.ts
Normal file
60
src/route/dev/model.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { DataTypes, Model, Op } from 'sequelize';
|
||||||
|
import { sequelize } from '@/modules/sequelize.ts';
|
||||||
|
|
||||||
|
type DevData = {
|
||||||
|
cwd?: string; // 当前工作目录
|
||||||
|
command: string; // 启动命令
|
||||||
|
type: 'node' | 'shell' | 'script'; // 启动类型
|
||||||
|
env?: Record<string, string>; // 环境变量
|
||||||
|
keepalive?: boolean; // 是否保持运行, 让用户知道是不是保持运行的
|
||||||
|
code?: string; // 代码
|
||||||
|
status?: 'active' | 'inactive'; // 状态
|
||||||
|
path?: string; // esbuild 打包路径,用于 node 启动
|
||||||
|
};
|
||||||
|
export type Dev = Partial<InstanceType<typeof DevModel>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 本地开发 配置启动和程序关闭
|
||||||
|
*/
|
||||||
|
export class DevModel extends Model {
|
||||||
|
declare id: string;
|
||||||
|
declare title: string;
|
||||||
|
declare description: string;
|
||||||
|
declare data: DevData;
|
||||||
|
}
|
||||||
|
DevModel.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
primaryKey: true,
|
||||||
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: '',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
paranoid: true,
|
||||||
|
modelName: 'local_dev',
|
||||||
|
freezeTableName: true, // 禁用表名复数化
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
DevModel.sync({
|
||||||
|
alter: true,
|
||||||
|
logging: false,
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
59
src/route/ssh/config.ts
Normal file
59
src/route/ssh/config.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { DataTypes, Model, Op } from 'sequelize';
|
||||||
|
import { sequelize } from '@/modules/sequelize.ts';
|
||||||
|
|
||||||
|
type SSHModelData = {
|
||||||
|
localPort: number;
|
||||||
|
remoteHost: string; // localhost
|
||||||
|
remotePort: number;
|
||||||
|
status?: 'active' | 'inactive';
|
||||||
|
};
|
||||||
|
export type SSH = Partial<InstanceType<typeof SSHModel>>;
|
||||||
|
|
||||||
|
export class SSHModel extends Model {
|
||||||
|
declare id: string;
|
||||||
|
// declare title: string;
|
||||||
|
declare description: string;
|
||||||
|
declare configs: SSHModelData[];
|
||||||
|
declare remote: string;
|
||||||
|
}
|
||||||
|
SSHModel.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
primaryKey: true,
|
||||||
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
},
|
||||||
|
// title: {
|
||||||
|
// type: DataTypes.TEXT,
|
||||||
|
// allowNull: false,
|
||||||
|
// defaultValue: '',
|
||||||
|
// },
|
||||||
|
description: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
configs: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: [],
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
paranoid: true,
|
||||||
|
modelName: 'local_ssh',
|
||||||
|
freezeTableName: true, // 禁用表名复数化
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
SSHModel.sync({
|
||||||
|
alter: true,
|
||||||
|
logging: false,
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
79
src/route/ssh/list.ts
Normal file
79
src/route/ssh/list.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { app } from '@/app.ts';
|
||||||
|
import { SSHModel } from './model.ts';
|
||||||
|
import { sshManager } from '@/lib/ssh.ts';
|
||||||
|
import { exec } from 'child_process';
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
const sshList = await SSHModel.findAll();
|
||||||
|
sshList.forEach((ssh) => {
|
||||||
|
sshManager.createServer(ssh.remote, ssh.configs);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
setTimeout(init, 1000);
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'ssh',
|
||||||
|
key: 'list',
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const sshList = await SSHModel.findAll();
|
||||||
|
ctx.body = sshList;
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'ssh',
|
||||||
|
key: 'status',
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
ctx.body = sshManager.getServers();
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'ssh',
|
||||||
|
key: 'update',
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const data = ctx.query.data;
|
||||||
|
const isExec = ctx.query.exec ?? true;
|
||||||
|
const { description, configs, remote } = data;
|
||||||
|
if (!remote) {
|
||||||
|
ctx.throw(400, 'remote 不能为空');
|
||||||
|
}
|
||||||
|
let ssh = await SSHModel.findOne({ where: { remote } });
|
||||||
|
if (!ssh) {
|
||||||
|
ssh = await SSHModel.create({ description, configs, remote });
|
||||||
|
}
|
||||||
|
await ssh.update({ description, configs }, { where: { remote } });
|
||||||
|
//
|
||||||
|
if (isExec) {
|
||||||
|
sshManager.createServer(remote, configs);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.body = ssh;
|
||||||
|
})
|
||||||
|
.addTo(app);
|
||||||
|
|
||||||
|
app
|
||||||
|
.route({
|
||||||
|
path: 'ssh',
|
||||||
|
key: 'closePort',
|
||||||
|
})
|
||||||
|
.define(async (ctx) => {
|
||||||
|
const port = ctx.query.port;
|
||||||
|
if (!port) {
|
||||||
|
ctx.throw(400, 'port 不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
exec(`lsof -i:${port} | grep LISTEN | awk '{print $2}' | xargs kill -9`, (killErr, stdout, stderr) => {
|
||||||
|
if (killErr) {
|
||||||
|
console.error(`Failed to kill process on port ${port}:`, killErr.message);
|
||||||
|
} else {
|
||||||
|
console.log(`Port ${port} is now free. Restarting process...`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ctx.body = 'ok';
|
||||||
|
})
|
||||||
|
.addTo(app);
|
59
src/route/ssh/model.ts
Normal file
59
src/route/ssh/model.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { DataTypes, Model, Op } from 'sequelize';
|
||||||
|
import { sequelize } from '@/modules/sequelize.ts';
|
||||||
|
|
||||||
|
type SSHModelData = {
|
||||||
|
localPort: number;
|
||||||
|
remoteHost: string; // localhost
|
||||||
|
remotePort: number;
|
||||||
|
status?: 'active' | 'inactive';
|
||||||
|
};
|
||||||
|
export type SSH = Partial<InstanceType<typeof SSHModel>>;
|
||||||
|
|
||||||
|
export class SSHModel extends Model {
|
||||||
|
declare id: string;
|
||||||
|
// declare title: string;
|
||||||
|
declare description: string;
|
||||||
|
declare configs: SSHModelData[];
|
||||||
|
declare remote: string;
|
||||||
|
}
|
||||||
|
SSHModel.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
primaryKey: true,
|
||||||
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
},
|
||||||
|
// title: {
|
||||||
|
// type: DataTypes.TEXT,
|
||||||
|
// allowNull: false,
|
||||||
|
// defaultValue: '',
|
||||||
|
// },
|
||||||
|
description: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
configs: {
|
||||||
|
type: DataTypes.JSON,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: [],
|
||||||
|
},
|
||||||
|
remote: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: false,
|
||||||
|
defaultValue: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
paranoid: true,
|
||||||
|
modelName: 'local_ssh',
|
||||||
|
freezeTableName: true, // 禁用表名复数化
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
SSHModel.sync({
|
||||||
|
alter: true,
|
||||||
|
logging: false,
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
});
|
72
src/scripts/add-nisar.ts
Normal file
72
src/scripts/add-nisar.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { app } from '../app.ts';
|
||||||
|
import '../index.ts';
|
||||||
|
const sleep = (time: number) => new Promise((resolve) => setTimeout(resolve, time));
|
||||||
|
const main = async () => {
|
||||||
|
await sleep(3000);
|
||||||
|
// console.log('add nisar')
|
||||||
|
|
||||||
|
app.call({
|
||||||
|
path: 'ssh',
|
||||||
|
key: 'update',
|
||||||
|
payload: {
|
||||||
|
exec: true,
|
||||||
|
data: {
|
||||||
|
remote: 'nisar',
|
||||||
|
description: 'diana的项目ssl l关联',
|
||||||
|
configs: [
|
||||||
|
{
|
||||||
|
localPort: 3000,
|
||||||
|
remoteHost: 'localhost',
|
||||||
|
remotePort: 3000,
|
||||||
|
description: 'openweb ui',
|
||||||
|
status: 'active',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
localPort: 5244,
|
||||||
|
remoteHost: 'localhost',
|
||||||
|
remotePort: 5244,
|
||||||
|
description: 'alist',
|
||||||
|
status: 'inactive',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
localPort: 3003,
|
||||||
|
remoteHost: 'localhost',
|
||||||
|
remotePort: 3003,
|
||||||
|
description: 'onai api',
|
||||||
|
status: 'inactive',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
localPort: 3002,
|
||||||
|
remoteHost: 'localhost',
|
||||||
|
remotePort: 3004,
|
||||||
|
description: 'codeflow ui',
|
||||||
|
status: 'inactive',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
localPort: 8000,
|
||||||
|
remoteHost: 'localhost',
|
||||||
|
remotePort: 8000,
|
||||||
|
description: 'parsex python api',
|
||||||
|
status: 'inactive',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
localPort: 9092,
|
||||||
|
remoteHost: 'localhost',
|
||||||
|
remotePort: 9092,
|
||||||
|
description: 'kafka',
|
||||||
|
status: 'inactive',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
localPort: 9200,
|
||||||
|
remoteHost: 'localhost',
|
||||||
|
remotePort: 9200,
|
||||||
|
description: 'elasticsearch',
|
||||||
|
status: 'inactive',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
main();
|
33
tsconfig.json
Normal file
33
tsconfig.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "nodenext",
|
||||||
|
"target": "esnext",
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"sourceMap": false,
|
||||||
|
"allowJs": true,
|
||||||
|
"newLine": "LF",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"typeRoots": [
|
||||||
|
"node_modules/@types",
|
||||||
|
],
|
||||||
|
"declaration": true,
|
||||||
|
"noEmit": false,
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"typings.d.ts",
|
||||||
|
"src/**/*.ts",
|
||||||
|
"test/**/*.ts",
|
||||||
|
],
|
||||||
|
}
|
33
tsconfig/app-lib.tsconfig.json
Normal file
33
tsconfig/app-lib.tsconfig.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "nodenext",
|
||||||
|
"target": "esnext",
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"sourceMap": false,
|
||||||
|
"allowJs": true,
|
||||||
|
"newLine": "LF",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"typeRoots": [
|
||||||
|
"node_modules/@types",
|
||||||
|
"node_modules/@kevisual/types"
|
||||||
|
],
|
||||||
|
"declaration": true,
|
||||||
|
"noEmit": false,
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"typings.d.ts",
|
||||||
|
"src/**/*.ts"
|
||||||
|
]
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user