From ee2fbe53354c33f372be717b664547e27a0d54ec Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 10:09:01 +0800 Subject: [PATCH] Add global SSH key configuration in system settings (#2840) * Initial plan * Add backend support for global SSH keys Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> * Add frontend UI for global SSH keys management Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> * Add SshKeyModel to database initialization Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> * Add SSH config generation for global SSH keys Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> * Add internationalization support for SSH key management UI Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> * Simplify to single global SSH key in system settings Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: whyour <22700758+whyour@users.noreply.github.com> --- back/api/system.ts | 18 ++++++++++++++++++ back/data/system.ts | 1 + back/loaders/initTask.ts | 7 +++++++ back/services/sshKey.ts | 28 ++++++++++++++++++++++++++++ back/services/system.ts | 21 +++++++++++++++++++++ src/locales/en-US.json | 20 +++++++++++++++++--- src/locales/zh-CN.json | 16 +++++++++++++++- src/pages/setting/other.tsx | 28 ++++++++++++++++++++++++++++ 8 files changed, 135 insertions(+), 4 deletions(-) diff --git a/back/api/system.ts b/back/api/system.ts index 30742a52..f51c6575 100644 --- a/back/api/system.ts +++ b/back/api/system.ts @@ -426,6 +426,24 @@ export default (app: Router) => { }, ); + route.put( + '/config/global-ssh-key', + celebrate({ + body: Joi.object({ + globalSshKey: Joi.string().allow('').allow(null), + }), + }), + async (req: Request, res: Response, next: NextFunction) => { + try { + const systemService = Container.get(SystemService); + const result = await systemService.updateGlobalSshKey(req.body); + res.send(result); + } catch (e) { + return next(e); + } + }, + ); + route.put( '/config/dependence-clean', celebrate({ diff --git a/back/data/system.ts b/back/data/system.ts index 84b6aae2..2dc14f50 100644 --- a/back/data/system.ts +++ b/back/data/system.ts @@ -38,6 +38,7 @@ export interface SystemConfigInfo { pythonMirror?: string; linuxMirror?: string; timezone?: string; + globalSshKey?: string; } export interface LoginLogInfo { diff --git a/back/loaders/initTask.ts b/back/loaders/initTask.ts index 9a4db07c..bdc57d42 100644 --- a/back/loaders/initTask.ts +++ b/back/loaders/initTask.ts @@ -2,6 +2,7 @@ import { Container } from 'typedi'; import SystemService from '../services/system'; import ScheduleService, { ScheduleTaskType } from '../services/schedule'; import SubscriptionService from '../services/subscription'; +import SshKeyService from '../services/sshKey'; import config from '../config'; import { fileExist } from '../config/util'; import { join } from 'path'; @@ -10,6 +11,7 @@ export default async () => { const systemService = Container.get(SystemService); const scheduleService = Container.get(ScheduleService); const subscriptionService = Container.get(SubscriptionService); + const sshKeyService = Container.get(SshKeyService); // 生成内置token let tokenCommand = `ts-node-transpile-only ${join( @@ -57,6 +59,11 @@ export default async () => { } systemService.updateTimezone(data.info); + + // Apply global SSH key if configured + if (data.info.globalSshKey) { + await sshKeyService.addGlobalSSHKey(data.info.globalSshKey, 'global'); + } } await subscriptionService.setSshConfig(); diff --git a/back/services/sshKey.ts b/back/services/sshKey.ts index a453e34d..f3b6308e 100644 --- a/back/services/sshKey.ts +++ b/back/services/sshKey.ts @@ -131,4 +131,32 @@ export default class SshKeyService { } } } + + public async addGlobalSSHKey(key: string, alias: string): Promise { + await this.generatePrivateKeyFile(`global_${alias}`, key); + // Create a global SSH config entry that matches all hosts + // This allows the key to be used for any Git repository + await this.generateGlobalSshConfig(`global_${alias}`); + } + + public async removeGlobalSSHKey(alias: string): Promise { + await this.removePrivateKeyFile(`global_${alias}`); + await this.removeSshConfig(`global_${alias}`); + } + + private async generateGlobalSshConfig(alias: string) { + // Create a config that matches all hosts, making this key globally available + const config = `Host *\n IdentityFile ${path.join( + this.sshPath, + alias, + )}\n StrictHostKeyChecking no\n`; + await writeFileWithLock( + `${path.join(this.sshPath, `${alias}.config`)}`, + config, + { + encoding: 'utf8', + mode: '600', + }, + ); + } } diff --git a/back/services/system.ts b/back/services/system.ts index f7d83797..ecc2a732 100644 --- a/back/services/system.ts +++ b/back/services/system.ts @@ -530,6 +530,27 @@ export default class SystemService { } } + public async updateGlobalSshKey(info: SystemModelInfo) { + const oDoc = await this.getSystemConfig(); + const result = await this.updateAuthDb({ + ...oDoc, + info: { ...oDoc.info, ...info }, + }); + + // Apply the global SSH key + const SshKeyService = require('./sshKey').default; + const Container = require('typedi').Container; + const sshKeyService = Container.get(SshKeyService); + + if (info.globalSshKey) { + await sshKeyService.addGlobalSSHKey(info.globalSshKey, 'global'); + } else { + await sshKeyService.removeGlobalSSHKey('global'); + } + + return { code: 200, data: result }; + } + public async cleanDependence(type: 'node' | 'python3') { if (!type || !['node', 'python3'].includes(type)) { return { code: 400, message: '参数错误' }; diff --git a/src/locales/en-US.json b/src/locales/en-US.json index 4b2fbdc1..bf4ec2dc 100644 --- a/src/locales/en-US.json +++ b/src/locales/en-US.json @@ -104,7 +104,7 @@ "序号": "Number", "备注": "Remarks", "更新时间": "Update Time", - "创建时间": "Creation Time", + "创建时间": "Created Time", "确认删除依赖": "Confirm to delete the dependency", "确认重新安装": "Confirm to reinstall", "确认取消安装": "Confirm to cancel install", @@ -252,7 +252,7 @@ "登录日志": "Login Logs", "其他设置": "Other Settings", "关于": "About", - "成功": "Success", + "成功": "Successfully", "失败": "Failure", "登录时间": "Login Time", "登录地址": "Login Address", @@ -538,5 +538,19 @@ "单实例模式:定时启动新任务前会自动停止旧任务;多实例模式:允许同时运行多个任务实例": "Single instance mode: automatically stop old task before starting new scheduled task; Multi-instance mode: allow multiple task instances to run simultaneously", "请选择实例模式": "Please select instance mode", "单实例": "Single Instance", - "多实例": "Multi-Instance" + "多实例": "Multi-Instance", + "SSH密钥": "SSH Keys", + "别名": "Alias", + "编辑SSH密钥": "Edit SSH Key", + "创建SSH密钥": "Create SSH Key", + "更新SSH密钥成功": "SSH key updated successfully", + "创建SSH密钥成功": "SSH key created successfully", + "请输入SSH密钥别名": "Please enter SSH key alias", + "请输入SSH私钥": "Please enter SSH private key", + "请输入SSH私钥内容(以 -----BEGIN 开头)": "Please enter SSH private key content (starts with -----BEGIN)", + "确认删除SSH密钥": "Confirm to delete SSH key", + "批量": "Batch", + "全局SSH私钥": "Global SSH Private Key", + "用于访问所有私有仓库的全局SSH私钥": "Global SSH private key for accessing all private repositories", + "请输入完整的SSH私钥内容": "Please enter the complete SSH private key content" } diff --git a/src/locales/zh-CN.json b/src/locales/zh-CN.json index fdb1ca10..f69f3a29 100644 --- a/src/locales/zh-CN.json +++ b/src/locales/zh-CN.json @@ -538,5 +538,19 @@ "单实例模式:定时启动新任务前会自动停止旧任务;多实例模式:允许同时运行多个任务实例": "单实例模式:定时启动新任务前会自动停止旧任务;多实例模式:允许同时运行多个任务实例", "请选择实例模式": "请选择实例模式", "单实例": "单实例", - "多实例": "多实例" + "多实例": "多实例", + "SSH密钥": "SSH密钥", + "别名": "别名", + "编辑SSH密钥": "编辑SSH密钥", + "创建SSH密钥": "创建SSH密钥", + "更新SSH密钥成功": "更新SSH密钥成功", + "创建SSH密钥成功": "创建SSH密钥成功", + "请输入SSH密钥别名": "请输入SSH密钥别名", + "请输入SSH私钥": "请输入SSH私钥", + "请输入SSH私钥内容(以 -----BEGIN 开头)": "请输入SSH私钥内容(以 -----BEGIN 开头)", + "确认删除SSH密钥": "确认删除SSH密钥", + "批量": "批量", + "全局SSH私钥": "全局SSH私钥", + "用于访问所有私有仓库的全局SSH私钥": "用于访问所有私有仓库的全局SSH私钥", + "请输入完整的SSH私钥内容": "请输入完整的SSH私钥内容" } diff --git a/src/pages/setting/other.tsx b/src/pages/setting/other.tsx index 570dc0ad..e2ef1973 100644 --- a/src/pages/setting/other.tsx +++ b/src/pages/setting/other.tsx @@ -30,6 +30,7 @@ const dataMap = { 'log-remove-frequency': 'logRemoveFrequency', 'cron-concurrency': 'cronConcurrency', timezone: 'timezone', + 'global-ssh-key': 'globalSshKey', }; const exportModules = [ @@ -54,6 +55,7 @@ const Other = ({ logRemoveFrequency?: number | null; cronConcurrency?: number | null; timezone?: string | null; + globalSshKey?: string | null; }>(); const [form] = Form.useForm(); const [exportLoading, setExportLoading] = useState(false); @@ -308,6 +310,32 @@ const Other = ({ + + + { + setSystemConfig({ ...systemConfig, globalSshKey: e.target.value }); + }} + /> + + +