mirror of
https://github.com/whyour/qinglong.git
synced 2025-05-22 22:36:06 +08:00
增加ssh key操作service
This commit is contained in:
parent
0218405998
commit
2c9b283b75
|
@ -19,11 +19,7 @@ async function startServer() {
|
||||||
|
|
||||||
const server = app
|
const server = app
|
||||||
.listen(config.port, () => {
|
.listen(config.port, () => {
|
||||||
Logger.info(`
|
Logger.debug(`✌️ Back server launched on port ${config.port}`);
|
||||||
################################################
|
|
||||||
🛡️ Server listening on port: ${config.port} 🛡️
|
|
||||||
################################################
|
|
||||||
`);
|
|
||||||
})
|
})
|
||||||
.on('error', (err) => {
|
.on('error', (err) => {
|
||||||
Logger.error(err);
|
Logger.error(err);
|
||||||
|
|
71
back/data/subscription.ts
Normal file
71
back/data/subscription.ts
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import { sequelize } from '.';
|
||||||
|
import { DataTypes, Model, ModelDefined } from 'sequelize';
|
||||||
|
import { SimpleIntervalSchedule } from 'toad-scheduler';
|
||||||
|
|
||||||
|
export class Subscription {
|
||||||
|
name?: string;
|
||||||
|
type?: 'public-repo' | 'private-repo' | 'file';
|
||||||
|
schedule?: string | SimpleIntervalSchedule;
|
||||||
|
url?: string;
|
||||||
|
whitelist?: string;
|
||||||
|
blacklist?: string;
|
||||||
|
dependences?: string;
|
||||||
|
branch?: string;
|
||||||
|
status?: SubscriptionStatus;
|
||||||
|
pull_type?: 'ssh-key' | 'user-pwd';
|
||||||
|
pull_option?:
|
||||||
|
| { private_key: string; key_alias: string }
|
||||||
|
| { username: string; password: string };
|
||||||
|
pid?: string;
|
||||||
|
|
||||||
|
constructor(options: Subscription) {
|
||||||
|
this.name = options.name;
|
||||||
|
this.type = options.type;
|
||||||
|
this.schedule = options.schedule;
|
||||||
|
this.url = options.url;
|
||||||
|
this.whitelist = options.whitelist;
|
||||||
|
this.blacklist = options.blacklist;
|
||||||
|
this.dependences = options.dependences;
|
||||||
|
this.branch = options.branch;
|
||||||
|
this.status = options.status;
|
||||||
|
this.pull_type = options.pull_type;
|
||||||
|
this.pull_option = options.pull_option;
|
||||||
|
this.pid = options.pid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SubscriptionStatus {
|
||||||
|
'running',
|
||||||
|
'idle',
|
||||||
|
'disabled',
|
||||||
|
'queued',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SubscriptionInstance
|
||||||
|
extends Model<Subscription, Subscription>,
|
||||||
|
Subscription {}
|
||||||
|
export const CrontabModel = sequelize.define<SubscriptionInstance>(
|
||||||
|
'Subscription',
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
unique: 'compositeIndex',
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
unique: 'compositeIndex',
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
},
|
||||||
|
schedule: {
|
||||||
|
unique: 'compositeIndex',
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
},
|
||||||
|
whitelist: DataTypes.STRING,
|
||||||
|
blacklist: DataTypes.STRING,
|
||||||
|
status: DataTypes.NUMBER,
|
||||||
|
dependences: DataTypes.STRING,
|
||||||
|
branch: DataTypes.STRING,
|
||||||
|
pull_type: DataTypes.STRING,
|
||||||
|
pull_option: DataTypes.JSON,
|
||||||
|
pid: DataTypes.NUMBER,
|
||||||
|
},
|
||||||
|
);
|
|
@ -4,6 +4,7 @@ import Logger from './logger';
|
||||||
import initData from './initData';
|
import initData from './initData';
|
||||||
import { Application } from 'express';
|
import { Application } from 'express';
|
||||||
import linkDeps from './deps';
|
import linkDeps from './deps';
|
||||||
|
import initTask from './initTask';
|
||||||
|
|
||||||
export default async ({ expressApp }: { expressApp: Application }) => {
|
export default async ({ expressApp }: { expressApp: Application }) => {
|
||||||
await depInjectorLoader();
|
await depInjectorLoader();
|
||||||
|
@ -16,5 +17,8 @@ export default async ({ expressApp }: { expressApp: Application }) => {
|
||||||
Logger.info('✌️ init data loaded');
|
Logger.info('✌️ init data loaded');
|
||||||
|
|
||||||
await linkDeps();
|
await linkDeps();
|
||||||
Logger.info('✌️ link deps');
|
Logger.info('✌️ link deps loaded');
|
||||||
|
|
||||||
|
initTask();
|
||||||
|
Logger.info('✌️ init task loaded');
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,16 +7,12 @@ import EnvService from '../services/env';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { DependenceModel } from '../data/dependence';
|
import { DependenceModel } from '../data/dependence';
|
||||||
import { Op } from 'sequelize';
|
import { Op } from 'sequelize';
|
||||||
import SystemService from '../services/system';
|
|
||||||
import ScheduleService from '../services/schedule';
|
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
const cronService = Container.get(CronService);
|
const cronService = Container.get(CronService);
|
||||||
const envService = Container.get(EnvService);
|
const envService = Container.get(EnvService);
|
||||||
const dependenceService = Container.get(DependenceService);
|
const dependenceService = Container.get(DependenceService);
|
||||||
const systemService = Container.get(SystemService);
|
|
||||||
const scheduleService = Container.get(ScheduleService);
|
|
||||||
|
|
||||||
// 初始化更新所有任务状态为空闲
|
// 初始化更新所有任务状态为空闲
|
||||||
await CrontabModel.update(
|
await CrontabModel.update(
|
||||||
|
@ -119,34 +115,4 @@ export default async () => {
|
||||||
// 初始化保存一次ck和定时任务数据
|
// 初始化保存一次ck和定时任务数据
|
||||||
await cronService.autosave_crontab();
|
await cronService.autosave_crontab();
|
||||||
await envService.set_envs();
|
await envService.set_envs();
|
||||||
|
|
||||||
// 运行删除日志任务
|
|
||||||
const data = await systemService.getLogRemoveFrequency();
|
|
||||||
if (data && data.info && data.info.frequency) {
|
|
||||||
const cron = {
|
|
||||||
id: data.id,
|
|
||||||
name: '删除日志',
|
|
||||||
command: `ql rmlog ${data.info.frequency}`,
|
|
||||||
};
|
|
||||||
await scheduleService.createIntervalTask(cron, {
|
|
||||||
days: data.info.frequency,
|
|
||||||
runImmediately: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function randomSchedule(from: number, to: number) {
|
|
||||||
const result: any[] = [];
|
|
||||||
const arr = [...Array(from).keys()];
|
|
||||||
let count = arr.length;
|
|
||||||
for (let i = 0; i < to; i++) {
|
|
||||||
const index = ~~(Math.random() * count) + i;
|
|
||||||
if (result.includes(arr[index])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
result[i] = arr[index];
|
|
||||||
arr[index] = arr[i];
|
|
||||||
count--;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
23
back/loaders/initTask.ts
Normal file
23
back/loaders/initTask.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { Container } from 'typedi';
|
||||||
|
import _ from 'lodash';
|
||||||
|
import SystemService from '../services/system';
|
||||||
|
import ScheduleService from '../services/schedule';
|
||||||
|
|
||||||
|
export default async () => {
|
||||||
|
const systemService = Container.get(SystemService);
|
||||||
|
const scheduleService = Container.get(ScheduleService);
|
||||||
|
|
||||||
|
// 运行删除日志任务
|
||||||
|
const data = await systemService.getLogRemoveFrequency();
|
||||||
|
if (data && data.info && data.info.frequency) {
|
||||||
|
const cron = {
|
||||||
|
id: data.id,
|
||||||
|
name: '删除日志',
|
||||||
|
command: `ql rmlog ${data.info.frequency}`,
|
||||||
|
};
|
||||||
|
await scheduleService.createIntervalTask(cron, {
|
||||||
|
days: data.info.frequency,
|
||||||
|
runImmediately: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -22,11 +22,7 @@ app
|
||||||
await require('./loaders/sentry').default({ expressApp: app });
|
await require('./loaders/sentry').default({ expressApp: app });
|
||||||
await require('./loaders/db').default();
|
await require('./loaders/db').default();
|
||||||
|
|
||||||
Logger.info(`
|
Logger.debug(`✌️ Back server launched on port ${config.publicPort}`);
|
||||||
################################################
|
|
||||||
🛡️ Public listening on port: ${config.publicPort} 🛡️
|
|
||||||
################################################
|
|
||||||
`);
|
|
||||||
})
|
})
|
||||||
.on('error', (err) => {
|
.on('error', (err) => {
|
||||||
Logger.error(err);
|
Logger.error(err);
|
||||||
|
|
|
@ -39,7 +39,7 @@ export default class ScheduleService {
|
||||||
{ maxBuffer: this.maxBuffer },
|
{ maxBuffer: this.maxBuffer },
|
||||||
async (error, stdout, stderr) => {
|
async (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
await this.logger.info(
|
await this.logger.error(
|
||||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||||
command,
|
command,
|
||||||
new Date().toLocaleString(),
|
new Date().toLocaleString(),
|
||||||
|
@ -48,7 +48,7 @@ export default class ScheduleService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
await this.logger.info(
|
await this.logger.error(
|
||||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||||
command,
|
command,
|
||||||
new Date().toLocaleString(),
|
new Date().toLocaleString(),
|
||||||
|
@ -58,7 +58,7 @@ export default class ScheduleService {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await this.logger.info(
|
await this.logger.error(
|
||||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||||
command,
|
command,
|
||||||
new Date().toLocaleString(),
|
new Date().toLocaleString(),
|
||||||
|
@ -97,7 +97,7 @@ export default class ScheduleService {
|
||||||
{ maxBuffer: this.maxBuffer },
|
{ maxBuffer: this.maxBuffer },
|
||||||
async (error, stdout, stderr) => {
|
async (error, stdout, stderr) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
await this.logger.info(
|
await this.logger.error(
|
||||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||||
command,
|
command,
|
||||||
new Date().toLocaleString(),
|
new Date().toLocaleString(),
|
||||||
|
@ -106,7 +106,7 @@ export default class ScheduleService {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stderr) {
|
if (stderr) {
|
||||||
await this.logger.info(
|
await this.logger.error(
|
||||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||||
command,
|
command,
|
||||||
new Date().toLocaleString(),
|
new Date().toLocaleString(),
|
||||||
|
@ -122,7 +122,7 @@ export default class ScheduleService {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
(err) => {
|
(err) => {
|
||||||
this.logger.info(
|
this.logger.error(
|
||||||
'执行任务%s失败,时间:%s, 错误信息:%j',
|
'执行任务%s失败,时间:%s, 错误信息:%j',
|
||||||
command,
|
command,
|
||||||
new Date().toLocaleString(),
|
new Date().toLocaleString(),
|
||||||
|
|
67
back/services/sshKey.ts
Normal file
67
back/services/sshKey.ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import { Service, Inject } from 'typedi';
|
||||||
|
import winston from 'winston';
|
||||||
|
import fs from 'fs';
|
||||||
|
import os from 'os';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class SshKeyService {
|
||||||
|
private homedir = os.homedir();
|
||||||
|
private sshPath = path.resolve(this.homedir, '.ssh');
|
||||||
|
private sshConfigFilePath = path.resolve(this.homedir, '.ssh/config');
|
||||||
|
|
||||||
|
constructor(@Inject('logger') private logger: winston.Logger) {}
|
||||||
|
|
||||||
|
private generatePrivateKeyFile(alias: string, key: string): void {
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(`${this.sshPath}/${alias}`, key, { encoding: 'utf8' });
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('生成私钥文件失败', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private removePrivateKeyFile(alias: string): void {
|
||||||
|
try {
|
||||||
|
fs.unlinkSync(`${this.sshPath}/${alias}`);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('删除私钥文件失败', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateSingleSshConfig(alias: string, host: string): string {
|
||||||
|
return `\nHost ${alias}\n Hostname ${host}\n IdentityFile=${this.sshPath}/${alias}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateSshConfig(configs: string[]) {
|
||||||
|
try {
|
||||||
|
for (const config of configs) {
|
||||||
|
fs.appendFileSync(this.sshConfigFilePath, config, { encoding: 'utf8' });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error('写入ssh配置文件失败', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeSshConfig(config: string) {
|
||||||
|
try {
|
||||||
|
fs.readFileSync(this.sshConfigFilePath, { encoding: 'utf8' }).replace(
|
||||||
|
config,
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`删除ssh配置文件${config}失败`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public addSSHKey(key: string, alias: string, host: string): void {
|
||||||
|
this.generatePrivateKeyFile(alias, key);
|
||||||
|
const config = this.generateSingleSshConfig(alias, host);
|
||||||
|
this.generateSshConfig([config]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeSSHKey(alias: string, host: string): void {
|
||||||
|
this.removePrivateKeyFile(alias);
|
||||||
|
const config = this.generateSingleSshConfig(alias, host);
|
||||||
|
this.removeSshConfig(config);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user