From 2c9b283b756657222a9fece6092ff84b47f65970 Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 8 May 2022 22:41:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0ssh=20key=E6=93=8D=E4=BD=9Cse?= =?UTF-8?q?rvice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back/app.ts | 6 +--- back/data/subscription.ts | 71 +++++++++++++++++++++++++++++++++++++++ back/loaders/app.ts | 6 +++- back/loaders/initData.ts | 34 ------------------- back/loaders/initTask.ts | 23 +++++++++++++ back/public.ts | 6 +--- back/services/schedule.ts | 12 +++---- back/services/sshKey.ts | 67 ++++++++++++++++++++++++++++++++++++ 8 files changed, 174 insertions(+), 51 deletions(-) create mode 100644 back/data/subscription.ts create mode 100644 back/loaders/initTask.ts create mode 100644 back/services/sshKey.ts diff --git a/back/app.ts b/back/app.ts index 68676bd8..e689b541 100644 --- a/back/app.ts +++ b/back/app.ts @@ -19,11 +19,7 @@ async function startServer() { const server = app .listen(config.port, () => { - Logger.info(` - ################################################ - 🛡️ Server listening on port: ${config.port} 🛡️ - ################################################ - `); + Logger.debug(`✌️ Back server launched on port ${config.port}`); }) .on('error', (err) => { Logger.error(err); diff --git a/back/data/subscription.ts b/back/data/subscription.ts new file mode 100644 index 00000000..f66f3916 --- /dev/null +++ b/back/data/subscription.ts @@ -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 {} +export const CrontabModel = sequelize.define( + '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, + }, +); diff --git a/back/loaders/app.ts b/back/loaders/app.ts index b2bea108..0798e28e 100644 --- a/back/loaders/app.ts +++ b/back/loaders/app.ts @@ -4,6 +4,7 @@ import Logger from './logger'; import initData from './initData'; import { Application } from 'express'; import linkDeps from './deps'; +import initTask from './initTask'; export default async ({ expressApp }: { expressApp: Application }) => { await depInjectorLoader(); @@ -16,5 +17,8 @@ export default async ({ expressApp }: { expressApp: Application }) => { Logger.info('✌️ init data loaded'); await linkDeps(); - Logger.info('✌️ link deps'); + Logger.info('✌️ link deps loaded'); + + initTask(); + Logger.info('✌️ init task loaded'); }; diff --git a/back/loaders/initData.ts b/back/loaders/initData.ts index 4d431d4a..a41fb6c9 100644 --- a/back/loaders/initData.ts +++ b/back/loaders/initData.ts @@ -7,16 +7,12 @@ import EnvService from '../services/env'; import _ from 'lodash'; import { DependenceModel } from '../data/dependence'; import { Op } from 'sequelize'; -import SystemService from '../services/system'; -import ScheduleService from '../services/schedule'; import config from '../config'; export default async () => { const cronService = Container.get(CronService); const envService = Container.get(EnvService); const dependenceService = Container.get(DependenceService); - const systemService = Container.get(SystemService); - const scheduleService = Container.get(ScheduleService); // 初始化更新所有任务状态为空闲 await CrontabModel.update( @@ -119,34 +115,4 @@ export default async () => { // 初始化保存一次ck和定时任务数据 await cronService.autosave_crontab(); 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; -} diff --git a/back/loaders/initTask.ts b/back/loaders/initTask.ts new file mode 100644 index 00000000..ede7f526 --- /dev/null +++ b/back/loaders/initTask.ts @@ -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, + }); + } +}; diff --git a/back/public.ts b/back/public.ts index 4bcc13a5..8faf9e6d 100644 --- a/back/public.ts +++ b/back/public.ts @@ -22,11 +22,7 @@ app await require('./loaders/sentry').default({ expressApp: app }); await require('./loaders/db').default(); - Logger.info(` - ################################################ - 🛡️ Public listening on port: ${config.publicPort} 🛡️ - ################################################ - `); + Logger.debug(`✌️ Back server launched on port ${config.publicPort}`); }) .on('error', (err) => { Logger.error(err); diff --git a/back/services/schedule.ts b/back/services/schedule.ts index 94a05ff9..96253b2f 100644 --- a/back/services/schedule.ts +++ b/back/services/schedule.ts @@ -39,7 +39,7 @@ export default class ScheduleService { { maxBuffer: this.maxBuffer }, async (error, stdout, stderr) => { if (error) { - await this.logger.info( + await this.logger.error( '执行任务%s失败,时间:%s, 错误信息:%j', command, new Date().toLocaleString(), @@ -48,7 +48,7 @@ export default class ScheduleService { } if (stderr) { - await this.logger.info( + await this.logger.error( '执行任务%s失败,时间:%s, 错误信息:%j', command, new Date().toLocaleString(), @@ -58,7 +58,7 @@ export default class ScheduleService { }, ); } catch (error) { - await this.logger.info( + await this.logger.error( '执行任务%s失败,时间:%s, 错误信息:%j', command, new Date().toLocaleString(), @@ -97,7 +97,7 @@ export default class ScheduleService { { maxBuffer: this.maxBuffer }, async (error, stdout, stderr) => { if (error) { - await this.logger.info( + await this.logger.error( '执行任务%s失败,时间:%s, 错误信息:%j', command, new Date().toLocaleString(), @@ -106,7 +106,7 @@ export default class ScheduleService { } if (stderr) { - await this.logger.info( + await this.logger.error( '执行任务%s失败,时间:%s, 错误信息:%j', command, new Date().toLocaleString(), @@ -122,7 +122,7 @@ export default class ScheduleService { }); }, (err) => { - this.logger.info( + this.logger.error( '执行任务%s失败,时间:%s, 错误信息:%j', command, new Date().toLocaleString(), diff --git a/back/services/sshKey.ts b/back/services/sshKey.ts new file mode 100644 index 00000000..aeea7afd --- /dev/null +++ b/back/services/sshKey.ts @@ -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); + } +}