增加ssh key操作service

This commit is contained in:
whyour 2022-05-08 22:41:56 +08:00
parent 0218405998
commit 2c9b283b75
8 changed files with 174 additions and 51 deletions

View File

@ -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
View 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,
},
);

View File

@ -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');
}; };

View File

@ -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
View 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,
});
}
};

View File

@ -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);

View File

@ -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
View 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);
}
}