From 9c238086d250934aef1b6dd6fd01abcf6b1d32c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B2=E9=B8=BF=E5=AE=87?= Date: Mon, 9 Jun 2025 11:49:35 +0800 Subject: [PATCH 01/14] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=8E=AF?= =?UTF-8?q?=E5=A2=83=E5=8F=98=E9=87=8F=E6=94=AF=E6=8C=81=E5=92=8C=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E9=87=8D=E5=AE=9A=E5=90=91=E4=BB=A5=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E6=89=A7=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 史鸿宇 --- .../main/deploy/core/DeploymentService.ts | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/electron/main/deploy/core/DeploymentService.ts b/electron/main/deploy/core/DeploymentService.ts index fde1c3a..d0da63c 100644 --- a/electron/main/deploy/core/DeploymentService.ts +++ b/electron/main/deploy/core/DeploymentService.ts @@ -252,6 +252,7 @@ export class DeploymentService { step: 'install-databases', progressStart: 30, progressEnd: 50, + envVars: {}, }, { name: '7-install-authhub', @@ -260,6 +261,11 @@ export class DeploymentService { step: 'install-authhub', progressStart: 50, progressEnd: 75, + envVars: { + // 通过环境变量或输入重定向避免交互 + AUTHHUB_DOMAIN: 'authhub.eulercopilot.local', + }, + useInputRedirection: true, // 标记需要输入重定向 }, { name: '8-install-EulerCopilot', @@ -268,6 +274,14 @@ export class DeploymentService { step: 'install-intelligence', progressStart: 75, progressEnd: 95, + envVars: { + // install_eulercopilot.sh 已支持这些环境变量 + EULERCOPILOT_DOMAIN: 'www.eulercopilot.local', + AUTHHUB_DOMAIN: 'authhub.eulercopilot.local', + // 设置非交互模式标志 + CI: 'true', + DEBIAN_FRONTEND: 'noninteractive', + }, }, ]; @@ -288,10 +302,28 @@ export class DeploymentService { throw new Error(`脚本文件不存在: ${scriptPath}`); } + // 准备环境变量 + const execEnv = { + ...process.env, + ...script.envVars, + }; + + // 构建执行命令 + let command = `chmod +x "${scriptPath}" && `; + + if (script.useInputRedirection) { + // 对于需要用户输入的脚本,使用输入重定向提供默认值 + // 为 install_authhub.sh 提供默认域名输入 + command += `echo "authhub.eulercopilot.local" | bash "${scriptPath}"`; + } else { + command += `bash "${scriptPath}"`; + } + // 给脚本添加执行权限并执行 - await execAsync(`chmod +x "${scriptPath}" && bash "${scriptPath}"`, { + await execAsync(command, { cwd: scriptsPath, timeout: 300000, // 5分钟超时 + env: execEnv, }); // 更新完成状态 -- Gitee From b91ae413d25e2bbf9efc429960ee7069550083b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8F=B2=E9=B8=BF=E5=AE=87?= Date: Mon, 9 Jun 2025 14:53:17 +0800 Subject: [PATCH 02/14] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20root=20?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=A3=80=E6=9F=A5=E5=92=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E6=9C=AC=E5=9C=B0=E9=83=A8=E7=BD=B2=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 史鸿宇 --- .../main/deploy/core/DeploymentService.ts | 142 +++++++++++++++--- electron/welcome/index.vue | 27 ++-- 2 files changed, 133 insertions(+), 36 deletions(-) diff --git a/electron/main/deploy/core/DeploymentService.ts b/electron/main/deploy/core/DeploymentService.ts index d0da63c..2213bec 100644 --- a/electron/main/deploy/core/DeploymentService.ts +++ b/electron/main/deploy/core/DeploymentService.ts @@ -128,6 +128,9 @@ export class DeploymentService { progress: 10, }); + // 检查 root 权限(仅限 Linux) + await this.checkRootPermission(); + const checkResult = await this.environmentChecker.checkAll(); if (!checkResult.success) { throw new Error(`环境检查失败: ${checkResult.errors.join(', ')}`); @@ -221,14 +224,14 @@ export class DeploymentService { // 检查脚本文件是否存在 if (fs.existsSync(toolsScriptPath)) { - // 给脚本添加执行权限并执行 - await execAsync( - `chmod +x "${toolsScriptPath}" && bash "${toolsScriptPath}"`, - { - cwd: scriptsPath, - timeout: 300000, // 5分钟超时 - }, - ); + // 构建需要权限的命令 + const command = this.buildRootCommand(toolsScriptPath); + + // 执行脚本 + await execAsync(command, { + cwd: scriptsPath, + timeout: 300000, // 5分钟超时 + }); } this.updateStatus({ @@ -308,16 +311,12 @@ export class DeploymentService { ...script.envVars, }; - // 构建执行命令 - let command = `chmod +x "${scriptPath}" && `; - - if (script.useInputRedirection) { - // 对于需要用户输入的脚本,使用输入重定向提供默认值 - // 为 install_authhub.sh 提供默认域名输入 - command += `echo "authhub.eulercopilot.local" | bash "${scriptPath}"`; - } else { - command += `bash "${scriptPath}"`; - } + // 构建需要权限的命令 + const command = this.buildRootCommand( + scriptPath, + script.useInputRedirection, + script.useInputRedirection ? 'authhub.eulercopilot.local' : undefined, + ); // 给脚本添加执行权限并执行 await execAsync(command, { @@ -334,6 +333,113 @@ export class DeploymentService { } } + /** + * 检查并确保有 root 权限或 sudo 权限(仅限 Linux 系统) + */ + private async checkRootPermission(): Promise { + // 只在 Linux 系统上检查权限 + if (process.platform !== 'linux') { + return; + } + + try { + // 检查当前用户 ID,0 表示 root + const { stdout } = await execAsync('id -u'); + const uid = parseInt(stdout.trim(), 10); + + // 如果是 root 用户,直接通过 + if (uid === 0) { + return; + } + + // 如果不是 root 用户,检查是否有 sudo 权限 + try { + // 检查用户是否在管理员组中(sudo、wheel、admin) + const { stdout: groupsOutput } = await execAsync('groups'); + const userGroups = groupsOutput.trim().split(/\s+/); + + // 检查常见的管理员组 + const adminGroups = ['sudo', 'wheel', 'admin']; + const hasAdminGroup = adminGroups.some((group) => + userGroups.includes(group), + ); + + if (hasAdminGroup) { + // 用户在管理员组中,具有 sudo 权限 + // 在实际执行时,buildRootCommand 会使用适当的图形化 sudo 工具 + return; + } + + // 如果不在管理员组中,尝试检查是否有无密码 sudo 权限 + try { + await execAsync('sudo -n true', { timeout: 3000 }); + // 如果成功,说明用户有无密码 sudo 权限 + return; + } catch { + // 用户既不在管理员组中,也没有无密码 sudo 权限 + throw new Error( + '部署脚本需要管理员权限才能执行。请确保当前用户具有 sudo 权限。', + ); + } + } catch (error) { + if ( + error instanceof Error && + error.message.includes('部署脚本需要管理员权限') + ) { + throw error; + } + // 无法检查组信息,假设用户可能有权限,在实际执行时再处理 + // 这样避免过于严格的权限检查阻止部署 + return; + } + } catch (error) { + if ( + error instanceof Error && + (error.message.includes('部署脚本需要 root 权限') || + error.message.includes('用户具有管理员权限')) + ) { + throw error; + } + throw new Error('无法检查用户权限'); + } + } + + /** + * 构建需要 root 权限的命令 + */ + private buildRootCommand( + scriptPath: string, + useInputRedirection?: boolean, + inputData?: string, + ): string { + // 在 Linux 系统上,如果不是 root 用户,使用图形化 sudo 工具 + const needsSudo = + process.platform === 'linux' && process.getuid && process.getuid() !== 0; + + // 获取合适的图形化 sudo 工具 + const getSudoCommand = (): string => { + if (!needsSudo) return ''; + + // 优先使用 pkexec(现代 Linux 桌面环境的标准) + // 如果没有,回退到传统的 sudo + return 'pkexec env DISPLAY=$DISPLAY XAUTHORITY=$XAUTHORITY '; + }; + + const sudoCommand = getSudoCommand(); + + let command = sudoCommand; + command += `chmod +x "${scriptPath}" && `; + command += sudoCommand; + + if (useInputRedirection && inputData) { + command += `bash -c 'echo "${inputData}" | bash "${scriptPath}"'`; + } else { + command += `bash "${scriptPath}"`; + } + + return command; + } + /** * 停止部署 */ diff --git a/electron/welcome/index.vue b/electron/welcome/index.vue index 84be092..4d8f8e9 100644 --- a/electron/welcome/index.vue +++ b/electron/welcome/index.vue @@ -10,7 +10,11 @@
-
+
{{ $t('welcome.localDeploy') }}
@@ -32,7 +36,7 @@