mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2026-02-10 19:50:28 +08:00
feat: add startup prompt for core permission check
This commit is contained in:
parent
cb3eedfcb8
commit
f005a4f4cd
@ -417,6 +417,125 @@ export async function checkMihomoCorePermissions(): Promise<boolean> {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检测高权限内核
|
||||||
|
export async function checkHighPrivilegeCore(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const { core = 'mihomo' } = await getAppConfig()
|
||||||
|
const corePath = mihomoCorePath(core)
|
||||||
|
|
||||||
|
await managerLogger.info(`Checking high privilege core: ${corePath}`)
|
||||||
|
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
const { existsSync } = await import('fs')
|
||||||
|
if (!existsSync(corePath)) {
|
||||||
|
await managerLogger.info('Core file does not exist')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasHighPrivilegeProcess = await checkHighPrivilegeMihomoProcess()
|
||||||
|
if (hasHighPrivilegeProcess) {
|
||||||
|
await managerLogger.info('Found high privilege mihomo process running')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAdmin = await checkAdminPrivileges()
|
||||||
|
await managerLogger.info(`Current process admin privileges: ${isAdmin}`)
|
||||||
|
return isAdmin
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'darwin' || process.platform === 'linux') {
|
||||||
|
const { stat, existsSync } = await import('fs')
|
||||||
|
const { promisify } = await import('util')
|
||||||
|
const statAsync = promisify(stat)
|
||||||
|
|
||||||
|
if (!existsSync(corePath)) {
|
||||||
|
await managerLogger.info('Core file does not exist')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const stats = await statAsync(corePath)
|
||||||
|
const hasSetuid = (stats.mode & 0o4000) !== 0
|
||||||
|
const isOwnedByRoot = stats.uid === 0
|
||||||
|
|
||||||
|
await managerLogger.info(`Core file stats - setuid: ${hasSetuid}, owned by root: ${isOwnedByRoot}, mode: ${stats.mode.toString(8)}`)
|
||||||
|
|
||||||
|
return hasSetuid && isOwnedByRoot
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await managerLogger.error('Failed to check high privilege core', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkHighPrivilegeMihomoProcess(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
const execPromise = promisify(exec)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout } = await execPromise('tasklist /FI "IMAGENAME eq mihomo.exe" /FO CSV')
|
||||||
|
const lines = stdout.split('\n').filter(line => line.includes('mihomo.exe'))
|
||||||
|
|
||||||
|
if (lines.length > 0) {
|
||||||
|
await managerLogger.info(`Found ${lines.length} mihomo processes running`)
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const parts = line.split(',')
|
||||||
|
if (parts.length >= 2) {
|
||||||
|
const pid = parts[1].replace(/"/g, '').trim()
|
||||||
|
try {
|
||||||
|
const { stdout: processInfo } = await execPromise(`wmic process where "ProcessId=${pid}" get Name,ProcessId,ExecutablePath,CommandLine /format:csv`)
|
||||||
|
await managerLogger.info(`Process ${pid} info: ${processInfo.substring(0, 200)}`)
|
||||||
|
|
||||||
|
if (processInfo.includes('mihomo')) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await managerLogger.info(`Cannot get info for process ${pid}, might be high privilege`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await managerLogger.error('Failed to check mihomo processes', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'darwin' || process.platform === 'linux') {
|
||||||
|
const execPromise = promisify(exec)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout } = await execPromise('ps aux | grep mihomo | grep -v grep')
|
||||||
|
const lines = stdout.split('\n').filter(line => line.trim() && line.includes('mihomo'))
|
||||||
|
|
||||||
|
if (lines.length > 0) {
|
||||||
|
await managerLogger.info(`Found ${lines.length} mihomo processes running`)
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const parts = line.trim().split(/\s+/)
|
||||||
|
if (parts.length >= 1) {
|
||||||
|
const user = parts[0]
|
||||||
|
await managerLogger.info(`Mihomo process running as user: ${user}`)
|
||||||
|
|
||||||
|
if (user === 'root') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await managerLogger.error('Failed to check mihomo processes on Unix', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
await managerLogger.error('Failed to check high privilege mihomo process', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// TUN模式获取权限
|
// TUN模式获取权限
|
||||||
export async function requestTunPermissions(): Promise<void> {
|
export async function requestTunPermissions(): Promise<void> {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
|
|||||||
@ -3,11 +3,11 @@ import { registerIpcMainHandlers } from './utils/ipc'
|
|||||||
import windowStateKeeper from 'electron-window-state'
|
import windowStateKeeper from 'electron-window-state'
|
||||||
import { app, shell, BrowserWindow, Menu, dialog, Notification, powerMonitor } from 'electron'
|
import { app, shell, BrowserWindow, Menu, dialog, Notification, powerMonitor } from 'electron'
|
||||||
import { addProfileItem, getAppConfig, patchAppConfig } from './config'
|
import { addProfileItem, getAppConfig, patchAppConfig } from './config'
|
||||||
import { quitWithoutCore, startCore, stopCore, checkAdminRestartForTun } from './core/manager'
|
import { quitWithoutCore, startCore, stopCore, checkAdminRestartForTun, checkHighPrivilegeCore, restartAsAdmin } from './core/manager'
|
||||||
import { triggerSysProxy } from './sys/sysproxy'
|
import { triggerSysProxy } from './sys/sysproxy'
|
||||||
import icon from '../../resources/icon.png?asset'
|
import icon from '../../resources/icon.png?asset'
|
||||||
import { createTray, hideDockIcon, showDockIcon } from './resolve/tray'
|
import { createTray, hideDockIcon, showDockIcon } from './resolve/tray'
|
||||||
import { init } from './utils/init'
|
import { init, initBasic } from './utils/init'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { initShortcut } from './resolve/shortcut'
|
import { initShortcut } from './resolve/shortcut'
|
||||||
import { spawn, exec } from 'child_process'
|
import { spawn, exec } from 'child_process'
|
||||||
@ -113,7 +113,62 @@ if (process.platform === 'win32' && !exePath().startsWith('C')) {
|
|||||||
app.commandLine.appendSwitch('in-process-gpu')
|
app.commandLine.appendSwitch('in-process-gpu')
|
||||||
}
|
}
|
||||||
|
|
||||||
const initPromise = init()
|
// 内核检测
|
||||||
|
async function checkHighPrivilegeCoreEarly(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await initBasic()
|
||||||
|
|
||||||
|
// 应用管理员权限运行,跳过检测
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
const { checkAdminPrivileges } = await import('./core/manager')
|
||||||
|
const isCurrentAppAdmin = await checkAdminPrivileges()
|
||||||
|
|
||||||
|
if (isCurrentAppAdmin) {
|
||||||
|
console.log('Current app is running as administrator, skipping privilege check')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if (process.platform === 'darwin' || process.platform === 'linux') {
|
||||||
|
if (process.getuid && process.getuid() === 0) {
|
||||||
|
console.log('Current app is running as root, skipping privilege check')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasHighPrivilegeCore = await checkHighPrivilegeCore()
|
||||||
|
if (hasHighPrivilegeCore) {
|
||||||
|
try {
|
||||||
|
const appConfig = await getAppConfig()
|
||||||
|
const language = appConfig.language || (app.getLocale().startsWith('zh') ? 'zh-CN' : 'en-US')
|
||||||
|
await initI18n({ lng: language })
|
||||||
|
} catch {
|
||||||
|
await initI18n({ lng: 'zh-CN' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const choice = dialog.showMessageBoxSync({
|
||||||
|
type: 'warning',
|
||||||
|
title: i18next.t('core.highPrivilege.title'),
|
||||||
|
message: i18next.t('core.highPrivilege.message'),
|
||||||
|
buttons: [i18next.t('common.confirm'), i18next.t('common.cancel')],
|
||||||
|
defaultId: 0,
|
||||||
|
cancelId: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
if (choice === 0) {
|
||||||
|
try {
|
||||||
|
await restartAsAdmin()
|
||||||
|
process.exit(0)
|
||||||
|
} catch (error) {
|
||||||
|
showSafeErrorBox('common.error.adminRequired', `${error}`)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to check high privilege core:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
app.on('second-instance', async (_event, commandline) => {
|
app.on('second-instance', async (_event, commandline) => {
|
||||||
showMainWindow()
|
showMainWindow()
|
||||||
@ -154,9 +209,10 @@ app.whenReady().then(async () => {
|
|||||||
// Set app user model id for windows
|
// Set app user model id for windows
|
||||||
electronApp.setAppUserModelId('party.mihomo.app')
|
electronApp.setAppUserModelId('party.mihomo.app')
|
||||||
|
|
||||||
|
await checkHighPrivilegeCoreEarly()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 首先等待初始化完成,确保配置文件和目录都已创建
|
await init()
|
||||||
await initPromise
|
|
||||||
|
|
||||||
const appConfig = await getAppConfig()
|
const appConfig = await getAppConfig()
|
||||||
// 如果配置中没有语言设置,则使用系统语言
|
// 如果配置中没有语言设置,则使用系统语言
|
||||||
@ -170,6 +226,7 @@ app.whenReady().then(async () => {
|
|||||||
showSafeErrorBox('common.error.initFailed', `${e}`)
|
showSafeErrorBox('common.error.initFailed', `${e}`)
|
||||||
app.quit()
|
app.quit()
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [startPromise] = await startCore()
|
const [startPromise] = await startCore()
|
||||||
startPromise.then(async () => {
|
startPromise.then(async () => {
|
||||||
|
|||||||
@ -318,12 +318,17 @@ function initDeeplink(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function init(): Promise<void> {
|
// 基础初始化
|
||||||
|
export async function initBasic(): Promise<void> {
|
||||||
await initDirs()
|
await initDirs()
|
||||||
await initConfig()
|
await initConfig()
|
||||||
await migration()
|
await migration()
|
||||||
await initFiles()
|
await initFiles()
|
||||||
await cleanup()
|
await cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function init(): Promise<void> {
|
||||||
|
await initBasic()
|
||||||
await startSubStoreFrontendServer()
|
await startSubStoreFrontendServer()
|
||||||
await startSubStoreBackendServer()
|
await startSubStoreBackendServer()
|
||||||
const { sysProxy } = await getAppConfig()
|
const { sysProxy } = await getAppConfig()
|
||||||
|
|||||||
@ -65,7 +65,8 @@ import {
|
|||||||
checkAdminPrivileges,
|
checkAdminPrivileges,
|
||||||
restartAsAdmin,
|
restartAsAdmin,
|
||||||
checkMihomoCorePermissions,
|
checkMihomoCorePermissions,
|
||||||
requestTunPermissions
|
requestTunPermissions,
|
||||||
|
checkHighPrivilegeCore
|
||||||
} from '../core/manager'
|
} from '../core/manager'
|
||||||
import { triggerSysProxy } from '../sys/sysproxy'
|
import { triggerSysProxy } from '../sys/sysproxy'
|
||||||
import { checkUpdate, downloadAndInstallUpdate } from '../resolve/autoUpdater'
|
import { checkUpdate, downloadAndInstallUpdate } from '../resolve/autoUpdater'
|
||||||
@ -200,6 +201,7 @@ export function registerIpcMainHandlers(): void {
|
|||||||
ipcMain.handle('restartAsAdmin', () => ipcErrorWrapper(restartAsAdmin)())
|
ipcMain.handle('restartAsAdmin', () => ipcErrorWrapper(restartAsAdmin)())
|
||||||
ipcMain.handle('checkMihomoCorePermissions', () => ipcErrorWrapper(checkMihomoCorePermissions)())
|
ipcMain.handle('checkMihomoCorePermissions', () => ipcErrorWrapper(checkMihomoCorePermissions)())
|
||||||
ipcMain.handle('requestTunPermissions', () => ipcErrorWrapper(requestTunPermissions)())
|
ipcMain.handle('requestTunPermissions', () => ipcErrorWrapper(requestTunPermissions)())
|
||||||
|
ipcMain.handle('checkHighPrivilegeCore', () => ipcErrorWrapper(checkHighPrivilegeCore)())
|
||||||
|
|
||||||
ipcMain.handle('checkTunPermissions', () => ipcErrorWrapper(checkTunPermissions)())
|
ipcMain.handle('checkTunPermissions', () => ipcErrorWrapper(checkTunPermissions)())
|
||||||
ipcMain.handle('grantTunPermissions', () => ipcErrorWrapper(grantTunPermissions)())
|
ipcMain.handle('grantTunPermissions', () => ipcErrorWrapper(grantTunPermissions)())
|
||||||
|
|||||||
@ -36,6 +36,8 @@
|
|||||||
"common.error.shortcutRegistrationFailedWithError": "Failed to register shortcut: {{error}}",
|
"common.error.shortcutRegistrationFailedWithError": "Failed to register shortcut: {{error}}",
|
||||||
"common.error.adminRequired": "Please run with administrator privileges for first launch",
|
"common.error.adminRequired": "Please run with administrator privileges for first launch",
|
||||||
"common.error.initFailed": "Application initialization failed",
|
"common.error.initFailed": "Application initialization failed",
|
||||||
|
"core.highPrivilege.title": "High Privilege Core Detected",
|
||||||
|
"core.highPrivilege.message": "A high privilege core (administrator privileges or setuid bit) has been detected. For security reasons, it is recommended to restart the application with administrator privileges. Restart now?",
|
||||||
"common.updater.versionReady": "v{{version}} Version Ready",
|
"common.updater.versionReady": "v{{version}} Version Ready",
|
||||||
"common.updater.goToDownload": "Go to Download",
|
"common.updater.goToDownload": "Go to Download",
|
||||||
"common.updater.update": "Update",
|
"common.updater.update": "Update",
|
||||||
|
|||||||
@ -36,6 +36,8 @@
|
|||||||
"common.error.shortcutRegistrationFailedWithError": "快捷键注册失败:{{error}}",
|
"common.error.shortcutRegistrationFailedWithError": "快捷键注册失败:{{error}}",
|
||||||
"common.error.adminRequired": "首次启动请以管理员权限运行",
|
"common.error.adminRequired": "首次启动请以管理员权限运行",
|
||||||
"common.error.initFailed": "应用初始化失败",
|
"common.error.initFailed": "应用初始化失败",
|
||||||
|
"core.highPrivilege.title": "检测到高权限内核",
|
||||||
|
"core.highPrivilege.message": "检测到当前内核具有高权限(管理员权限或 setuid 位),为了安全起见,建议以管理员权限重启应用。是否现在重启?",
|
||||||
"common.updater.versionReady": "v{{version}} 版本就绪",
|
"common.updater.versionReady": "v{{version}} 版本就绪",
|
||||||
"common.updater.goToDownload": "前往下载",
|
"common.updater.goToDownload": "前往下载",
|
||||||
"common.updater.update": "更新",
|
"common.updater.update": "更新",
|
||||||
|
|||||||
@ -241,6 +241,18 @@ export async function manualGrantCorePermition(): Promise<void> {
|
|||||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('manualGrantCorePermition'))
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('manualGrantCorePermition'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function checkHighPrivilegeCore(): Promise<boolean> {
|
||||||
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('checkHighPrivilegeCore'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkAdminPrivileges(): Promise<boolean> {
|
||||||
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('checkAdminPrivileges'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function restartAsAdmin(): Promise<void> {
|
||||||
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('restartAsAdmin'))
|
||||||
|
}
|
||||||
|
|
||||||
export async function getFilePath(ext: string[]): Promise<string[] | undefined> {
|
export async function getFilePath(ext: string[]): Promise<string[] | undefined> {
|
||||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getFilePath', ext))
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('getFilePath', ext))
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user