mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 05:00:30 +08:00
fix: privilege check and elevation restart logic
This commit is contained in:
parent
e27ddbd16e
commit
6b93a59616
@ -18,7 +18,7 @@ import {
|
||||
patchControledMihomoConfig,
|
||||
manageSmartOverride
|
||||
} from '../config'
|
||||
import { app, dialog, ipcMain, net } from 'electron'
|
||||
import { app, ipcMain, net } from 'electron'
|
||||
import {
|
||||
startMihomoTraffic,
|
||||
startMihomoConnections,
|
||||
@ -290,10 +290,6 @@ async function checkProfile(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查TUN模式所需的权限
|
||||
* @returns Promise<boolean> 是否有足够权限
|
||||
*/
|
||||
export async function checkTunPermissions(): Promise<boolean> {
|
||||
const { core = 'mihomo' } = await getAppConfig()
|
||||
const corePath = mihomoCorePath(core)
|
||||
@ -310,7 +306,6 @@ export async function checkTunPermissions(): Promise<boolean> {
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin' || process.platform === 'linux') {
|
||||
// Unix系统检查核心文件是否有setuid权限
|
||||
const { stat } = await import('fs/promises')
|
||||
const stats = await stat(corePath)
|
||||
return (stats.mode & 0o4000) !== 0 && stats.uid === 0
|
||||
@ -322,9 +317,6 @@ export async function checkTunPermissions(): Promise<boolean> {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 为TUN模式获取必要的权限
|
||||
*/
|
||||
export async function grantTunPermissions(): Promise<void> {
|
||||
const { core = 'mihomo' } = await getAppConfig()
|
||||
const corePath = mihomoCorePath(core)
|
||||
@ -350,8 +342,6 @@ export async function grantTunPermissions(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// Windows 管理员权限
|
||||
|
||||
export async function checkAdminPrivileges(): Promise<boolean> {
|
||||
if (process.platform !== 'win32') {
|
||||
return true
|
||||
@ -366,7 +356,7 @@ export async function checkAdminPrivileges(): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function requestAdminPrivileges(): Promise<void> {
|
||||
export async function restartAsAdmin(): Promise<void> {
|
||||
if (process.platform !== 'win32') {
|
||||
throw new Error('This function is only available on Windows')
|
||||
}
|
||||
@ -376,27 +366,101 @@ export async function requestAdminPrivileges(): Promise<void> {
|
||||
const args = process.argv.slice(1)
|
||||
|
||||
try {
|
||||
const restartArgs = [...args, '--admin-restart-for-tun']
|
||||
const escapedExePath = exePath.replace(/'/g, "''")
|
||||
const escapedArgs = args.map(arg => `'${arg.replace(/'/g, "''")}'`).join(', ')
|
||||
const escapedArgs = restartArgs.map(arg => `'${arg.replace(/'/g, "''")}'`).join(', ')
|
||||
|
||||
let command: string
|
||||
if (args.length > 0) {
|
||||
if (restartArgs.length > 0) {
|
||||
command = `powershell -WindowStyle Hidden -Command "Start-Process -FilePath '${escapedExePath}' -ArgumentList ${escapedArgs} -Verb RunAs"`
|
||||
} else {
|
||||
command = `powershell -WindowStyle Hidden -Command "Start-Process -FilePath '${escapedExePath}' -Verb RunAs"`
|
||||
}
|
||||
|
||||
console.log('Requesting admin privileges with command:', command)
|
||||
|
||||
console.log('Restarting as administrator with command:', command)
|
||||
await execPromise(command, { windowsHide: true })
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
await new Promise(resolve => setTimeout(resolve, 1500))
|
||||
|
||||
const { app } = await import('electron')
|
||||
app.quit()
|
||||
} catch (error) {
|
||||
console.error('Admin privilege request failed:', error)
|
||||
throw new Error(`Failed to request admin privileges: ${error}`)
|
||||
console.error('Failed to restart as administrator:', error)
|
||||
throw new Error(`Failed to restart as administrator: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function checkMihomoCorePermissions(): Promise<boolean> {
|
||||
const { core = 'mihomo' } = await getAppConfig()
|
||||
const corePath = mihomoCorePath(core)
|
||||
|
||||
try {
|
||||
if (process.platform === 'win32') {
|
||||
// Windows权限检查
|
||||
return await checkAdminPrivileges()
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin' || process.platform === 'linux') {
|
||||
const { stat } = await import('fs/promises')
|
||||
const stats = await stat(corePath)
|
||||
return (stats.mode & 0o4000) !== 0 && stats.uid === 0
|
||||
}
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// TUN模式获取权限
|
||||
export async function requestTunPermissions(): Promise<void> {
|
||||
if (process.platform === 'win32') {
|
||||
await restartAsAdmin()
|
||||
} else {
|
||||
const hasPermissions = await checkMihomoCorePermissions()
|
||||
if (!hasPermissions) {
|
||||
await grantTunPermissions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkAdminRestartForTun(): Promise<void> {
|
||||
if (process.argv.includes('--admin-restart-for-tun')) {
|
||||
console.log('Detected admin restart for TUN mode, auto-enabling TUN...')
|
||||
|
||||
try {
|
||||
if (process.platform === 'win32') {
|
||||
const hasAdminPrivileges = await checkAdminPrivileges()
|
||||
if (hasAdminPrivileges) {
|
||||
await patchControledMihomoConfig({ tun: { enable: true }, dns: { enable: true } })
|
||||
await restartCore()
|
||||
|
||||
console.log('TUN mode auto-enabled after admin restart')
|
||||
|
||||
const { mainWindow } = await import('../index')
|
||||
mainWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||
ipcMain.emit('updateTrayMenu')
|
||||
} else {
|
||||
console.warn('Admin restart detected but no admin privileges found')
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to auto-enable TUN after admin restart:', error)
|
||||
}
|
||||
} else if (process.platform === 'win32') {
|
||||
try {
|
||||
const hasAdminPrivileges = await checkAdminPrivileges()
|
||||
const { tun } = await getControledMihomoConfig()
|
||||
|
||||
if (hasAdminPrivileges && !tun?.enable) {
|
||||
console.log('Running with admin privileges but TUN is disabled')
|
||||
const { mainWindow } = await import('../index')
|
||||
mainWindow?.webContents.send('adminPrivilegesDetected', { tunEnabled: false })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to check admin privileges on startup:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import { registerIpcMainHandlers } from './utils/ipc'
|
||||
import windowStateKeeper from 'electron-window-state'
|
||||
import { app, shell, BrowserWindow, Menu, dialog, Notification, powerMonitor } from 'electron'
|
||||
import { addProfileItem, getAppConfig, patchAppConfig } from './config'
|
||||
import { quitWithoutCore, startCore, stopCore } from './core/manager'
|
||||
import { quitWithoutCore, startCore, stopCore, checkAdminRestartForTun } from './core/manager'
|
||||
import { triggerSysProxy } from './sys/sysproxy'
|
||||
import icon from '../../resources/icon.png?asset'
|
||||
import { createTray, hideDockIcon, showDockIcon } from './resolve/tray'
|
||||
@ -173,6 +173,8 @@ app.whenReady().then(async () => {
|
||||
const [startPromise] = await startCore()
|
||||
startPromise.then(async () => {
|
||||
await initProfileUpdater()
|
||||
// 上次是否为了开启 TUN 而重启
|
||||
await checkAdminRestartForTun()
|
||||
})
|
||||
} catch (e) {
|
||||
showSafeErrorBox('mihomo.error.coreStartFailed', `${e}`)
|
||||
|
||||
@ -19,7 +19,7 @@ import { mainWindow, showMainWindow, triggerMainWindow } from '..'
|
||||
import { app, clipboard, ipcMain, Menu, nativeImage, shell, Tray } from 'electron'
|
||||
import { dataDir, logDir, mihomoCoreDir, mihomoWorkDir } from '../utils/dirs'
|
||||
import { triggerSysProxy } from '../sys/sysproxy'
|
||||
import { quitWithoutCore, restartCore, checkTunPermissions, grantTunPermissions, checkAdminPrivileges, requestAdminPrivileges } from '../core/manager'
|
||||
import { quitWithoutCore, restartCore, checkMihomoCorePermissions, requestTunPermissions, restartAsAdmin } from '../core/manager'
|
||||
import { floatingWindow, triggerFloatingWindow } from './floatingWindow'
|
||||
import { t } from 'i18next'
|
||||
|
||||
@ -178,42 +178,33 @@ export const buildContextMenu = async (): Promise<Menu> => {
|
||||
const enable = item.checked
|
||||
try {
|
||||
if (enable) {
|
||||
// Windows 管理员权限
|
||||
// 检查权限
|
||||
try {
|
||||
const hasPermissions = await checkMihomoCorePermissions()
|
||||
|
||||
if (!hasPermissions) {
|
||||
if (process.platform === 'win32') {
|
||||
try {
|
||||
const isAdmin = await checkAdminPrivileges()
|
||||
if (!isAdmin) {
|
||||
try {
|
||||
await requestAdminPrivileges()
|
||||
return
|
||||
await restartAsAdmin()
|
||||
} catch (error) {
|
||||
console.error('Failed to request admin privileges:', error)
|
||||
console.error('Failed to restart as admin from tray:', error)
|
||||
item.checked = false
|
||||
ipcMain.emit('updateTrayMenu')
|
||||
return
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await requestTunPermissions()
|
||||
} catch (error) {
|
||||
console.error('Failed to grant TUN permissions from tray:', error)
|
||||
item.checked = false
|
||||
ipcMain.emit('updateTrayMenu')
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Admin check failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform !== 'win32') {
|
||||
try {
|
||||
const hasPermissions = await checkTunPermissions()
|
||||
if (!hasPermissions) {
|
||||
try {
|
||||
await grantTunPermissions()
|
||||
} catch (error) {
|
||||
console.error('Failed to grant TUN permissions:', error)
|
||||
item.checked = false
|
||||
ipcMain.emit('updateTrayMenu')
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Permission check failed:', error)
|
||||
}
|
||||
console.warn('Permission check failed in tray:', error)
|
||||
}
|
||||
|
||||
await patchControledMihomoConfig({ tun: { enable }, dns: { enable: true } })
|
||||
|
||||
@ -63,7 +63,9 @@ import {
|
||||
grantTunPermissions,
|
||||
manualGrantCorePermition,
|
||||
checkAdminPrivileges,
|
||||
requestAdminPrivileges
|
||||
restartAsAdmin,
|
||||
checkMihomoCorePermissions,
|
||||
requestTunPermissions
|
||||
} from '../core/manager'
|
||||
import { triggerSysProxy } from '../sys/sysproxy'
|
||||
import { checkUpdate, downloadAndInstallUpdate } from '../resolve/autoUpdater'
|
||||
@ -195,7 +197,9 @@ export function registerIpcMainHandlers(): void {
|
||||
ipcMain.handle('triggerSysProxy', (_e, enable) => ipcErrorWrapper(triggerSysProxy)(enable))
|
||||
ipcMain.handle('manualGrantCorePermition', () => ipcErrorWrapper(manualGrantCorePermition)())
|
||||
ipcMain.handle('checkAdminPrivileges', () => ipcErrorWrapper(checkAdminPrivileges)())
|
||||
ipcMain.handle('requestAdminPrivileges', () => ipcErrorWrapper(requestAdminPrivileges)())
|
||||
ipcMain.handle('restartAsAdmin', () => ipcErrorWrapper(restartAsAdmin)())
|
||||
ipcMain.handle('checkMihomoCorePermissions', () => ipcErrorWrapper(checkMihomoCorePermissions)())
|
||||
ipcMain.handle('requestTunPermissions', () => ipcErrorWrapper(requestTunPermissions)())
|
||||
|
||||
ipcMain.handle('checkTunPermissions', () => ipcErrorWrapper(checkTunPermissions)())
|
||||
ipcMain.handle('grantTunPermissions', () => ipcErrorWrapper(grantTunPermissions)())
|
||||
|
||||
@ -38,41 +38,41 @@ const TunSwitcher: React.FC<Props> = (props) => {
|
||||
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
|
||||
const onChange = async (enable: boolean): Promise<void> => {
|
||||
if (enable) {
|
||||
// Windows下检查管理员权限
|
||||
if (window.electron.process.platform === 'win32') {
|
||||
try {
|
||||
const isAdmin = await window.electron.ipcRenderer.invoke('checkAdminPrivileges')
|
||||
if (!isAdmin) {
|
||||
// 检查内核权限
|
||||
const hasPermissions = await window.electron.ipcRenderer.invoke('checkMihomoCorePermissions')
|
||||
|
||||
if (!hasPermissions) {
|
||||
if (window.electron.process.platform === 'win32') {
|
||||
const confirmed = confirm(t('tun.permissions.required'))
|
||||
if (confirmed) {
|
||||
try {
|
||||
const notification = new Notification(t('tun.permissions.requesting'))
|
||||
|
||||
await window.electron.ipcRenderer.invoke('requestAdminPrivileges')
|
||||
const notification = new Notification(t('tun.permissions.restarting'))
|
||||
await window.electron.ipcRenderer.invoke('restartAsAdmin')
|
||||
notification.close()
|
||||
return
|
||||
} catch (error) {
|
||||
console.error('Failed to request admin privileges:', error)
|
||||
console.error('Failed to restart as admin:', error)
|
||||
alert(t('tun.permissions.failed') + ': ' + error)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Admin check failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// macOS/Linux 获取权限
|
||||
if (window.electron.process.platform !== 'win32') {
|
||||
} else {
|
||||
// macOS/Linux下尝试自动获取权限
|
||||
try {
|
||||
await window.electron.ipcRenderer.invoke('manualGrantCorePermition')
|
||||
await window.electron.ipcRenderer.invoke('requestTunPermissions')
|
||||
} catch (error) {
|
||||
console.warn('Permission grant failed:', error)
|
||||
alert(t('tun.permissions.failed') + ': ' + error)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Permission check failed:', error)
|
||||
}
|
||||
|
||||
await patchControledMihomoConfig({ tun: { enable }, dns: { enable: true } })
|
||||
} else {
|
||||
|
||||
@ -316,10 +316,11 @@
|
||||
"tun.notifications.coreAuthSuccess": "Core Authorization Successful",
|
||||
"tun.notifications.firewallResetSuccess": "Firewall Reset Successful",
|
||||
"tun.error.tunPermissionDenied": "TUN interface start failed, please try to manually grant core permissions",
|
||||
"tun.permissions.required": "TUN mode requires administrator privileges. Grant permissions now?",
|
||||
"tun.permissions.required": "TUN mode requires administrator privileges. Restart the application now to get permissions?",
|
||||
"tun.permissions.failed": "Permission authorization failed",
|
||||
"tun.permissions.windowsRestart": "On Windows, you need to restart the application as administrator to use TUN mode",
|
||||
"tun.permissions.requesting": "Requesting administrator privileges, please click 'Yes' in the UAC dialog...",
|
||||
"tun.permissions.restarting": "Restarting application with administrator privileges, please click 'Yes' in the UAC dialog...",
|
||||
"dns.title": "DNS Settings",
|
||||
"dns.enable": "Enable DNS",
|
||||
"dns.enhancedMode.title": "Domain Mapping Mode",
|
||||
|
||||
@ -316,10 +316,11 @@
|
||||
"tun.notifications.coreAuthSuccess": "内核授权成功",
|
||||
"tun.notifications.firewallResetSuccess": "防火墙重设成功",
|
||||
"tun.error.tunPermissionDenied": "虚拟网卡启动失败,请尝试手动授予内核权限",
|
||||
"tun.permissions.required": "启用TUN模式需要管理员权限,是否现在授权?",
|
||||
"tun.permissions.required": "启用TUN模式需要管理员权限,是否现在重启应用获取权限?",
|
||||
"tun.permissions.failed": "权限授权失败",
|
||||
"tun.permissions.windowsRestart": "Windows下需要以管理员身份重新启动应用才能使用TUN模式",
|
||||
"tun.permissions.requesting": "正在请求管理员权限,请在UAC对话框中点击'是'...",
|
||||
"tun.permissions.restarting": "正在以管理员权限重启应用,请在UAC对话框中点击'是'...",
|
||||
"dns.title": "DNS 设置",
|
||||
"dns.enable": "启用 DNS",
|
||||
"dns.enhancedMode.title": "域名映射模式",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user