mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 21:20:29 +08:00
feat: remove enforced admin mode requirement
This commit is contained in:
parent
45484ffff2
commit
5c1d30b454
@ -322,32 +322,7 @@ const resolveSysproxy = () =>
|
|||||||
file: 'sysproxy.exe',
|
file: 'sysproxy.exe',
|
||||||
downloadURL: `https://github.com/mihomo-party-org/sysproxy/releases/download/${arch}/sysproxy.exe`
|
downloadURL: `https://github.com/mihomo-party-org/sysproxy/releases/download/${arch}/sysproxy.exe`
|
||||||
})
|
})
|
||||||
const resolveRunner = () =>
|
|
||||||
resolveResource({
|
|
||||||
file: 'mihomo-party-run.exe',
|
|
||||||
downloadURL: `https://github.com/mihomo-party-org/mihomo-party-run/releases/download/${arch}/mihomo-party-run.exe`
|
|
||||||
})
|
|
||||||
|
|
||||||
const resolveMonitor = async () => {
|
|
||||||
const tempDir = path.join(TEMP_DIR, 'TrafficMonitor')
|
|
||||||
const tempZip = path.join(tempDir, `${arch}.zip`)
|
|
||||||
if (!fs.existsSync(tempDir)) {
|
|
||||||
fs.mkdirSync(tempDir, { recursive: true })
|
|
||||||
}
|
|
||||||
await downloadFile(
|
|
||||||
`https://github.com/mihomo-party-org/mihomo-party-run/releases/download/monitor/${arch}.zip`,
|
|
||||||
tempZip
|
|
||||||
)
|
|
||||||
const zip = new AdmZip(tempZip)
|
|
||||||
const resDir = path.join(cwd, 'extra', 'files')
|
|
||||||
const targetPath = path.join(resDir, 'TrafficMonitor')
|
|
||||||
if (fs.existsSync(targetPath)) {
|
|
||||||
fs.rmSync(targetPath, { recursive: true })
|
|
||||||
}
|
|
||||||
zip.extractAllTo(targetPath, true)
|
|
||||||
|
|
||||||
console.log(`[INFO]: TrafficMonitor finished`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolve7zip = () =>
|
const resolve7zip = () =>
|
||||||
resolveResource({
|
resolveResource({
|
||||||
@ -438,18 +413,7 @@ const tasks = [
|
|||||||
retry: 5,
|
retry: 5,
|
||||||
winOnly: true
|
winOnly: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'runner',
|
|
||||||
func: resolveRunner,
|
|
||||||
retry: 5,
|
|
||||||
winOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'monitor',
|
|
||||||
func: resolveMonitor,
|
|
||||||
retry: 5,
|
|
||||||
winOnly: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'substore',
|
name: 'substore',
|
||||||
func: resolveSubstore,
|
func: resolveSubstore,
|
||||||
|
|||||||
@ -290,16 +290,53 @@ async function checkProfile(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function manualGrantCorePermition(): Promise<void> {
|
/**
|
||||||
|
* 检查TUN模式所需的权限
|
||||||
|
* @returns Promise<boolean> 是否有足够权限
|
||||||
|
*/
|
||||||
|
export async function checkTunPermissions(): Promise<boolean> {
|
||||||
|
const { core = 'mihomo' } = await getAppConfig()
|
||||||
|
const corePath = mihomoCorePath(core)
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
const execPromise = promisify(exec)
|
||||||
|
try {
|
||||||
|
await execPromise('net session')
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为TUN模式获取必要的权限
|
||||||
|
*/
|
||||||
|
export async function grantTunPermissions(): Promise<void> {
|
||||||
const { core = 'mihomo' } = await getAppConfig()
|
const { core = 'mihomo' } = await getAppConfig()
|
||||||
const corePath = mihomoCorePath(core)
|
const corePath = mihomoCorePath(core)
|
||||||
const execPromise = promisify(exec)
|
const execPromise = promisify(exec)
|
||||||
const execFilePromise = promisify(execFile)
|
const execFilePromise = promisify(execFile)
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
const shell = `chown root:admin ${corePath.replace(' ', '\\\\ ')}\nchmod +sx ${corePath.replace(' ', '\\\\ ')}`
|
const shell = `chown root:admin ${corePath.replace(' ', '\\\\ ')}\nchmod +sx ${corePath.replace(' ', '\\\\ ')}`
|
||||||
const command = `do shell script "${shell}" with administrator privileges`
|
const command = `do shell script "${shell}" with administrator privileges`
|
||||||
await execPromise(`osascript -e '${command}'`)
|
await execPromise(`osascript -e '${command}'`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.platform === 'linux') {
|
if (process.platform === 'linux') {
|
||||||
await execFilePromise('pkexec', [
|
await execFilePromise('pkexec', [
|
||||||
'bash',
|
'bash',
|
||||||
@ -307,6 +344,10 @@ export async function manualGrantCorePermition(): Promise<void> {
|
|||||||
`chown root:root "${corePath}" && chmod +sx "${corePath}"`
|
`chown root:root "${corePath}" && chmod +sx "${corePath}"`
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
throw new Error('Windows platform requires running as administrator')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDefaultDevice(): Promise<string> {
|
export async function getDefaultDevice(): Promise<string> {
|
||||||
|
|||||||
@ -10,15 +10,12 @@ import { createTray, hideDockIcon, showDockIcon } from './resolve/tray'
|
|||||||
import { init } from './utils/init'
|
import { init } from './utils/init'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { initShortcut } from './resolve/shortcut'
|
import { initShortcut } from './resolve/shortcut'
|
||||||
import { execSync, spawn, exec } from 'child_process'
|
import { spawn, exec } from 'child_process'
|
||||||
import { createElevateTask } from './sys/misc'
|
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import { stat } from 'fs/promises'
|
import { stat } from 'fs/promises'
|
||||||
import { initProfileUpdater } from './core/profileUpdater'
|
import { initProfileUpdater } from './core/profileUpdater'
|
||||||
import { existsSync, writeFileSync } from 'fs'
|
import { existsSync } from 'fs'
|
||||||
import { exePath, taskDir } from './utils/dirs'
|
import { exePath } from './utils/dirs'
|
||||||
import path from 'path'
|
|
||||||
import iconv from 'iconv-lite'
|
|
||||||
import { startMonitor } from './resolve/trafficMonitor'
|
import { startMonitor } from './resolve/trafficMonitor'
|
||||||
import { showFloatingWindow } from './resolve/floatingWindow'
|
import { showFloatingWindow } from './resolve/floatingWindow'
|
||||||
import { initI18n } from '../shared/i18n'
|
import { initI18n } from '../shared/i18n'
|
||||||
@ -69,37 +66,6 @@ async function fixUserDataPermissions(): Promise<void> {
|
|||||||
let quitTimeout: NodeJS.Timeout | null = null
|
let quitTimeout: NodeJS.Timeout | null = null
|
||||||
export let mainWindow: BrowserWindow | null = null
|
export let mainWindow: BrowserWindow | null = null
|
||||||
|
|
||||||
// Windows 管理员权限检查(仅在生产模式下)
|
|
||||||
if (process.platform === 'win32' && !is.dev && !process.argv.includes('noadmin')) {
|
|
||||||
try {
|
|
||||||
createElevateTask()
|
|
||||||
} catch (createError) {
|
|
||||||
try {
|
|
||||||
if (process.argv.slice(1).length > 0) {
|
|
||||||
writeFileSync(path.join(taskDir(), 'param.txt'), process.argv.slice(1).join(' '))
|
|
||||||
} else {
|
|
||||||
writeFileSync(path.join(taskDir(), 'param.txt'), 'empty')
|
|
||||||
}
|
|
||||||
if (!existsSync(path.join(taskDir(), 'mihomo-party-run.exe'))) {
|
|
||||||
throw new Error('mihomo-party-run.exe not found')
|
|
||||||
} else {
|
|
||||||
execSync('%SystemRoot%\\System32\\schtasks.exe /run /tn mihomo-party-run')
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
let createErrorStr = `${createError}`
|
|
||||||
let eStr = `${e}`
|
|
||||||
try {
|
|
||||||
createErrorStr = iconv.decode((createError as { stderr: Buffer }).stderr, 'gbk')
|
|
||||||
eStr = iconv.decode((e as { stderr: Buffer }).stderr, 'gbk')
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
showSafeErrorBox('common.error.adminRequired', `${createErrorStr}\n${eStr}`)
|
|
||||||
} finally {
|
|
||||||
app.exit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initApp(): Promise<void> {
|
async function initApp(): Promise<void> {
|
||||||
await fixUserDataPermissions()
|
await fixUserDataPermissions()
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import { mainWindow, showMainWindow, triggerMainWindow } from '..'
|
|||||||
import { app, clipboard, ipcMain, Menu, nativeImage, shell, Tray } from 'electron'
|
import { app, clipboard, ipcMain, Menu, nativeImage, shell, Tray } from 'electron'
|
||||||
import { dataDir, logDir, mihomoCoreDir, mihomoWorkDir } from '../utils/dirs'
|
import { dataDir, logDir, mihomoCoreDir, mihomoWorkDir } from '../utils/dirs'
|
||||||
import { triggerSysProxy } from '../sys/sysproxy'
|
import { triggerSysProxy } from '../sys/sysproxy'
|
||||||
import { quitWithoutCore, restartCore } from '../core/manager'
|
import { quitWithoutCore, restartCore, checkTunPermissions, grantTunPermissions } from '../core/manager'
|
||||||
import { floatingWindow, triggerFloatingWindow } from './floatingWindow'
|
import { floatingWindow, triggerFloatingWindow } from './floatingWindow'
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
|
|
||||||
@ -178,6 +178,23 @@ export const buildContextMenu = async (): Promise<Menu> => {
|
|||||||
const enable = item.checked
|
const enable = item.checked
|
||||||
try {
|
try {
|
||||||
if (enable) {
|
if (enable) {
|
||||||
|
// 检查TUN权限
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
await patchControledMihomoConfig({ tun: { enable }, dns: { enable: true } })
|
await patchControledMihomoConfig({ tun: { enable }, dns: { enable: true } })
|
||||||
} else {
|
} else {
|
||||||
await patchControledMihomoConfig({ tun: { enable } })
|
await patchControledMihomoConfig({ tun: { enable } })
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { exePath, homeDir, taskDir } from '../utils/dirs'
|
import { exePath, homeDir } from '../utils/dirs'
|
||||||
|
import { tmpdir } from 'os'
|
||||||
import { mkdir, readFile, rm, writeFile } from 'fs/promises'
|
import { mkdir, readFile, rm, writeFile } from 'fs/promises'
|
||||||
import { exec } from 'child_process'
|
import { exec } from 'child_process'
|
||||||
import { existsSync } from 'fs'
|
import { existsSync } from 'fs'
|
||||||
@ -19,7 +20,7 @@ function getTaskXml(): string {
|
|||||||
<Principals>
|
<Principals>
|
||||||
<Principal id="Author">
|
<Principal id="Author">
|
||||||
<LogonType>InteractiveToken</LogonType>
|
<LogonType>InteractiveToken</LogonType>
|
||||||
<RunLevel>HighestAvailable</RunLevel>
|
<RunLevel>LeastPrivilege</RunLevel>
|
||||||
</Principal>
|
</Principal>
|
||||||
</Principals>
|
</Principals>
|
||||||
<Settings>
|
<Settings>
|
||||||
@ -43,8 +44,7 @@ function getTaskXml(): string {
|
|||||||
</Settings>
|
</Settings>
|
||||||
<Actions Context="Author">
|
<Actions Context="Author">
|
||||||
<Exec>
|
<Exec>
|
||||||
<Command>"${path.join(taskDir(), `mihomo-party-run.exe`)}"</Command>
|
<Command>"${exePath()}"</Command>
|
||||||
<Arguments>"${exePath()}"</Arguments>
|
|
||||||
</Exec>
|
</Exec>
|
||||||
</Actions>
|
</Actions>
|
||||||
</Task>
|
</Task>
|
||||||
@ -81,7 +81,7 @@ export async function checkAutoRun(): Promise<boolean> {
|
|||||||
export async function enableAutoRun(): Promise<void> {
|
export async function enableAutoRun(): Promise<void> {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
const execPromise = promisify(exec)
|
const execPromise = promisify(exec)
|
||||||
const taskFilePath = path.join(taskDir(), `${appName}.xml`)
|
const taskFilePath = path.join(tmpdir(), `${appName}.xml`)
|
||||||
await writeFile(taskFilePath, Buffer.from(`\ufeff${getTaskXml()}`, 'utf-16le'))
|
await writeFile(taskFilePath, Buffer.from(`\ufeff${getTaskXml()}`, 'utf-16le'))
|
||||||
await execPromise(
|
await execPromise(
|
||||||
`%SystemRoot%\\System32\\schtasks.exe /create /tn "${appName}" /xml "${taskFilePath}" /f`
|
`%SystemRoot%\\System32\\schtasks.exe /create /tn "${appName}" /xml "${taskFilePath}" /f`
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { exec, execFile, execSync, spawn } from 'child_process'
|
import { exec, execFile, spawn } from 'child_process'
|
||||||
import { app, dialog, nativeTheme, shell } from 'electron'
|
import { app, dialog, nativeTheme, shell } from 'electron'
|
||||||
import { readFile } from 'fs/promises'
|
import { readFile } from 'fs/promises'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
@ -9,11 +9,8 @@ import {
|
|||||||
mihomoCorePath,
|
mihomoCorePath,
|
||||||
overridePath,
|
overridePath,
|
||||||
profilePath,
|
profilePath,
|
||||||
resourcesDir,
|
resourcesDir
|
||||||
resourcesFilesDir,
|
|
||||||
taskDir
|
|
||||||
} from '../utils/dirs'
|
} from '../utils/dirs'
|
||||||
import { copyFileSync, writeFileSync } from 'fs'
|
|
||||||
|
|
||||||
export function getFilePath(ext: string[]): string[] | undefined {
|
export function getFilePath(ext: string[]): string[] | undefined {
|
||||||
return dialog.showOpenDialogSync({
|
return dialog.showOpenDialogSync({
|
||||||
@ -68,56 +65,7 @@ export function setNativeTheme(theme: 'system' | 'light' | 'dark'): void {
|
|||||||
nativeTheme.themeSource = theme
|
nativeTheme.themeSource = theme
|
||||||
}
|
}
|
||||||
|
|
||||||
function getElevateTaskXml(): string {
|
|
||||||
return `<?xml version="1.0" encoding="UTF-16"?>
|
|
||||||
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
||||||
<Triggers />
|
|
||||||
<Principals>
|
|
||||||
<Principal id="Author">
|
|
||||||
<LogonType>InteractiveToken</LogonType>
|
|
||||||
<RunLevel>HighestAvailable</RunLevel>
|
|
||||||
</Principal>
|
|
||||||
</Principals>
|
|
||||||
<Settings>
|
|
||||||
<MultipleInstancesPolicy>Parallel</MultipleInstancesPolicy>
|
|
||||||
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
|
|
||||||
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
|
|
||||||
<AllowHardTerminate>false</AllowHardTerminate>
|
|
||||||
<StartWhenAvailable>false</StartWhenAvailable>
|
|
||||||
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
|
|
||||||
<IdleSettings>
|
|
||||||
<StopOnIdleEnd>false</StopOnIdleEnd>
|
|
||||||
<RestartOnIdle>false</RestartOnIdle>
|
|
||||||
</IdleSettings>
|
|
||||||
<AllowStartOnDemand>true</AllowStartOnDemand>
|
|
||||||
<Enabled>true</Enabled>
|
|
||||||
<Hidden>false</Hidden>
|
|
||||||
<RunOnlyIfIdle>false</RunOnlyIfIdle>
|
|
||||||
<WakeToRun>false</WakeToRun>
|
|
||||||
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
|
|
||||||
<Priority>3</Priority>
|
|
||||||
</Settings>
|
|
||||||
<Actions Context="Author">
|
|
||||||
<Exec>
|
|
||||||
<Command>"${path.join(taskDir(), `mihomo-party-run.exe`)}"</Command>
|
|
||||||
<Arguments>"${exePath()}"</Arguments>
|
|
||||||
</Exec>
|
|
||||||
</Actions>
|
|
||||||
</Task>
|
|
||||||
`
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createElevateTask(): void {
|
|
||||||
const taskFilePath = path.join(taskDir(), `mihomo-party-run.xml`)
|
|
||||||
writeFileSync(taskFilePath, Buffer.from(`\ufeff${getElevateTaskXml()}`, 'utf-16le'))
|
|
||||||
copyFileSync(
|
|
||||||
path.join(resourcesFilesDir(), 'mihomo-party-run.exe'),
|
|
||||||
path.join(taskDir(), 'mihomo-party-run.exe')
|
|
||||||
)
|
|
||||||
execSync(
|
|
||||||
`%SystemRoot%\\System32\\schtasks.exe /create /tn "mihomo-party-run" /xml "${taskFilePath}" /f`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resetAppConfig(): void {
|
export function resetAppConfig(): void {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
|
|||||||
@ -167,7 +167,7 @@ async function requestSocketRecreation(): Promise<void> {
|
|||||||
const { promisify } = require('util')
|
const { promisify } = require('util')
|
||||||
const execPromise = promisify(exec)
|
const execPromise = promisify(exec)
|
||||||
|
|
||||||
// Use osascript with administrator privileges (same pattern as manualGrantCorePermition)
|
// Use osascript with administrator privileges (same pattern as grantTunPermissions)
|
||||||
const shell = `pkill -USR1 -f party.mihomo.helper`
|
const shell = `pkill -USR1 -f party.mihomo.helper`
|
||||||
const command = `do shell script "${shell}" with administrator privileges`
|
const command = `do shell script "${shell}" with administrator privileges`
|
||||||
await execPromise(`osascript -e '${command}'`)
|
await execPromise(`osascript -e '${command}'`)
|
||||||
|
|||||||
@ -56,7 +56,12 @@ import {
|
|||||||
subStoreFrontendPort,
|
subStoreFrontendPort,
|
||||||
subStorePort
|
subStorePort
|
||||||
} from '../resolve/server'
|
} from '../resolve/server'
|
||||||
import { manualGrantCorePermition, quitWithoutCore, restartCore } from '../core/manager'
|
import {
|
||||||
|
quitWithoutCore,
|
||||||
|
restartCore,
|
||||||
|
checkTunPermissions,
|
||||||
|
grantTunPermissions
|
||||||
|
} 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'
|
||||||
import {
|
import {
|
||||||
@ -185,7 +190,9 @@ export function registerIpcMainHandlers(): void {
|
|||||||
ipcMain.handle('restartCore', ipcErrorWrapper(restartCore))
|
ipcMain.handle('restartCore', ipcErrorWrapper(restartCore))
|
||||||
ipcMain.handle('startMonitor', (_e, detached) => ipcErrorWrapper(startMonitor)(detached))
|
ipcMain.handle('startMonitor', (_e, detached) => ipcErrorWrapper(startMonitor)(detached))
|
||||||
ipcMain.handle('triggerSysProxy', (_e, enable) => ipcErrorWrapper(triggerSysProxy)(enable))
|
ipcMain.handle('triggerSysProxy', (_e, enable) => ipcErrorWrapper(triggerSysProxy)(enable))
|
||||||
ipcMain.handle('manualGrantCorePermition', () => ipcErrorWrapper(manualGrantCorePermition)())
|
|
||||||
|
ipcMain.handle('checkTunPermissions', () => ipcErrorWrapper(checkTunPermissions)())
|
||||||
|
ipcMain.handle('grantTunPermissions', () => ipcErrorWrapper(grantTunPermissions)())
|
||||||
ipcMain.handle('getFilePath', (_e, ext) => getFilePath(ext))
|
ipcMain.handle('getFilePath', (_e, ext) => getFilePath(ext))
|
||||||
ipcMain.handle('readTextFile', (_e, filePath) => ipcErrorWrapper(readTextFile)(filePath))
|
ipcMain.handle('readTextFile', (_e, filePath) => ipcErrorWrapper(readTextFile)(filePath))
|
||||||
ipcMain.handle('getRuntimeConfigStr', ipcErrorWrapper(getRuntimeConfigStr))
|
ipcMain.handle('getRuntimeConfigStr', ipcErrorWrapper(getRuntimeConfigStr))
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-c
|
|||||||
import BorderSwitch from '@renderer/components/base/border-swtich'
|
import BorderSwitch from '@renderer/components/base/border-swtich'
|
||||||
import { TbDeviceIpadHorizontalBolt } from 'react-icons/tb'
|
import { TbDeviceIpadHorizontalBolt } from 'react-icons/tb'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { restartCore } from '@renderer/utils/ipc'
|
import { restartCore, checkTunPermissions, grantTunPermissions } from '@renderer/utils/ipc'
|
||||||
import { useSortable } from '@dnd-kit/sortable'
|
import { useSortable } from '@dnd-kit/sortable'
|
||||||
import { CSS } from '@dnd-kit/utilities'
|
import { CSS } from '@dnd-kit/utilities'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
@ -38,6 +38,26 @@ const TunSwitcher: React.FC<Props> = (props) => {
|
|||||||
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
|
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
|
||||||
const onChange = async (enable: boolean): Promise<void> => {
|
const onChange = async (enable: boolean): Promise<void> => {
|
||||||
if (enable) {
|
if (enable) {
|
||||||
|
// 检查TUN权限
|
||||||
|
try {
|
||||||
|
const hasPermissions = await checkTunPermissions()
|
||||||
|
if (!hasPermissions) {
|
||||||
|
const confirmed = confirm(t('tun.permissions.required'))
|
||||||
|
if (confirmed) {
|
||||||
|
try {
|
||||||
|
await grantTunPermissions()
|
||||||
|
} catch (error) {
|
||||||
|
alert(t('tun.permissions.failed') + ': ' + error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Permission check failed:', error)
|
||||||
|
}
|
||||||
|
|
||||||
await patchControledMihomoConfig({ tun: { enable }, dns: { enable: true } })
|
await patchControledMihomoConfig({ tun: { enable }, dns: { enable: true } })
|
||||||
} else {
|
} else {
|
||||||
await patchControledMihomoConfig({ tun: { enable } })
|
await patchControledMihomoConfig({ tun: { enable } })
|
||||||
|
|||||||
@ -316,6 +316,8 @@
|
|||||||
"tun.notifications.coreAuthSuccess": "Core Authorization Successful",
|
"tun.notifications.coreAuthSuccess": "Core Authorization Successful",
|
||||||
"tun.notifications.firewallResetSuccess": "Firewall Reset Successful",
|
"tun.notifications.firewallResetSuccess": "Firewall Reset Successful",
|
||||||
"tun.error.tunPermissionDenied": "TUN interface start failed, please try to manually grant core permissions",
|
"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.failed": "Permission authorization failed",
|
||||||
"dns.title": "DNS Settings",
|
"dns.title": "DNS Settings",
|
||||||
"dns.enable": "Enable DNS",
|
"dns.enable": "Enable DNS",
|
||||||
"dns.enhancedMode.title": "Domain Mapping Mode",
|
"dns.enhancedMode.title": "Domain Mapping Mode",
|
||||||
|
|||||||
@ -316,6 +316,8 @@
|
|||||||
"tun.notifications.coreAuthSuccess": "内核授权成功",
|
"tun.notifications.coreAuthSuccess": "内核授权成功",
|
||||||
"tun.notifications.firewallResetSuccess": "防火墙重设成功",
|
"tun.notifications.firewallResetSuccess": "防火墙重设成功",
|
||||||
"tun.error.tunPermissionDenied": "虚拟网卡启动失败,请尝试手动授予内核权限",
|
"tun.error.tunPermissionDenied": "虚拟网卡启动失败,请尝试手动授予内核权限",
|
||||||
|
"tun.permissions.required": "启用TUN模式需要管理员权限,是否现在授权?",
|
||||||
|
"tun.permissions.failed": "权限授权失败",
|
||||||
"dns.title": "DNS 设置",
|
"dns.title": "DNS 设置",
|
||||||
"dns.enable": "启用 DNS",
|
"dns.enable": "启用 DNS",
|
||||||
"dns.enhancedMode.title": "域名映射模式",
|
"dns.enhancedMode.title": "域名映射模式",
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import BasePage from '@renderer/components/base/base-page'
|
|||||||
import SettingCard from '@renderer/components/base/base-setting-card'
|
import SettingCard from '@renderer/components/base/base-setting-card'
|
||||||
import SettingItem from '@renderer/components/base/base-setting-item'
|
import SettingItem from '@renderer/components/base/base-setting-item'
|
||||||
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-config'
|
||||||
import { manualGrantCorePermition, restartCore, setupFirewall } from '@renderer/utils/ipc'
|
import { grantTunPermissions, restartCore, setupFirewall } from '@renderer/utils/ipc'
|
||||||
import { platform } from '@renderer/utils/init'
|
import { platform } from '@renderer/utils/init'
|
||||||
import React, { Key, useState } from 'react'
|
import React, { Key, useState } from 'react'
|
||||||
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
import { useAppConfig } from '@renderer/hooks/use-app-config'
|
||||||
@ -129,7 +129,7 @@ const Tun: React.FC = () => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
onPress={async () => {
|
onPress={async () => {
|
||||||
try {
|
try {
|
||||||
await manualGrantCorePermition()
|
await grantTunPermissions()
|
||||||
new Notification(t('tun.notifications.coreAuthSuccess'))
|
new Notification(t('tun.notifications.coreAuthSuccess'))
|
||||||
await restartCore()
|
await restartCore()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@ -227,8 +227,14 @@ export async function triggerSysProxy(enable: boolean): Promise<void> {
|
|||||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('triggerSysProxy', enable))
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('triggerSysProxy', enable))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function manualGrantCorePermition(): Promise<void> {
|
|
||||||
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('manualGrantCorePermition'))
|
|
||||||
|
export async function checkTunPermissions(): Promise<boolean> {
|
||||||
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('checkTunPermissions'))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function grantTunPermissions(): Promise<void> {
|
||||||
|
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('grantTunPermissions'))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFilePath(ext: string[]): Promise<string[] | undefined> {
|
export async function getFilePath(ext: string[]): Promise<string[] | undefined> {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user