mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 21:20:29 +08:00
refactor: improve core permission management
This commit is contained in:
parent
a8f8cd0fd3
commit
1e3f31d1a7
@ -203,12 +203,22 @@ export async function stopCore(force = false): Promise<void> {
|
|||||||
child.removeAllListeners()
|
child.removeAllListeners()
|
||||||
child.kill('SIGINT')
|
child.kill('SIGINT')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await cleanupCoreFiles()
|
||||||
stopMihomoTraffic()
|
stopMihomoTraffic()
|
||||||
stopMihomoConnections()
|
stopMihomoConnections()
|
||||||
stopMihomoLogs()
|
stopMihomoLogs()
|
||||||
stopMihomoMemory()
|
stopMihomoMemory()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function cleanupCoreFiles(): Promise<void> {
|
||||||
|
const pidFile = path.join(dataDir(), 'core.pid')
|
||||||
|
const stateFile = path.join(dataDir(), 'core.state')
|
||||||
|
|
||||||
|
await rm(pidFile).catch(() => {})
|
||||||
|
await rm(stateFile).catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
export async function restartCore(): Promise<void> {
|
export async function restartCore(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await startCore()
|
await startCore()
|
||||||
@ -226,13 +236,25 @@ export async function keepCoreAlive(): Promise<void> {
|
|||||||
try {
|
try {
|
||||||
await startCore(true)
|
await startCore(true)
|
||||||
if (child && child.pid) {
|
if (child && child.pid) {
|
||||||
await writeFile(path.join(dataDir(), 'core.pid'), child.pid.toString())
|
await writeCoreStateFile(child.pid)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
|
safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function writeCoreStateFile(pid: number): Promise<void> {
|
||||||
|
const isAdmin = await checkAdminPrivileges()
|
||||||
|
const stateData = {
|
||||||
|
pid,
|
||||||
|
isAdmin,
|
||||||
|
startTime: Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeFile(path.join(dataDir(), 'core.pid'), pid.toString())
|
||||||
|
await writeFile(path.join(dataDir(), 'core.state'), JSON.stringify(stateData))
|
||||||
|
}
|
||||||
|
|
||||||
export async function quitWithoutCore(): Promise<void> {
|
export async function quitWithoutCore(): Promise<void> {
|
||||||
await keepCoreAlive()
|
await keepCoreAlive()
|
||||||
await startMonitor(true)
|
await startMonitor(true)
|
||||||
@ -460,7 +482,228 @@ export async function checkAdminRestartForTun(): Promise<void> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 检查TUN配置与权限的匹配
|
// 检查TUN配置与权限的匹配
|
||||||
|
await checkAndAlignPermissions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function checkAndAlignPermissions(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const runningCoreInfo = await getRunningCoreInfo()
|
||||||
|
const currentUserIsAdmin = await checkAdminPrivileges()
|
||||||
|
|
||||||
|
if (runningCoreInfo) {
|
||||||
|
const coreIsAdmin = runningCoreInfo.isAdmin
|
||||||
|
|
||||||
|
if (currentUserIsAdmin !== coreIsAdmin) {
|
||||||
|
await handlePermissionMismatch(currentUserIsAdmin, coreIsAdmin)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await validateTunPermissionsOnStartup()
|
await validateTunPermissionsOnStartup()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to check and align permissions:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getRunningCoreInfo(): Promise<{ pid: number; isAdmin: boolean } | null> {
|
||||||
|
const stateFile = path.join(dataDir(), 'core.state')
|
||||||
|
const pidFile = path.join(dataDir(), 'core.pid')
|
||||||
|
|
||||||
|
if (existsSync(stateFile)) {
|
||||||
|
try {
|
||||||
|
const stateData = JSON.parse(await readFile(stateFile, 'utf-8'))
|
||||||
|
const { pid, isAdmin } = stateData
|
||||||
|
|
||||||
|
if (isProcessRunning(pid)) {
|
||||||
|
return { pid, isAdmin }
|
||||||
|
} else {
|
||||||
|
await rm(stateFile).catch(() => {})
|
||||||
|
await rm(pidFile).catch(() => {})
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existsSync(pidFile)) {
|
||||||
|
try {
|
||||||
|
const pidStr = await readFile(pidFile, 'utf-8')
|
||||||
|
const pid = parseInt(pidStr.trim())
|
||||||
|
|
||||||
|
if (!isProcessRunning(pid)) {
|
||||||
|
await rm(pidFile)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAdmin = await isProcessRunningAsAdmin(pid)
|
||||||
|
return { pid, isAdmin }
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function isProcessRunning(pid: number): boolean {
|
||||||
|
try {
|
||||||
|
process.kill(pid, 0)
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isProcessRunningAsAdmin(_pid: number): Promise<boolean> {
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return await checkAdminPrivileges()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePermissionMismatch(guiIsAdmin: boolean, coreIsAdmin: boolean): Promise<void> {
|
||||||
|
if (guiIsAdmin && !coreIsAdmin) {
|
||||||
|
console.log('GUI is admin but core is not, restarting core with elevated permissions')
|
||||||
|
await restartCore()
|
||||||
|
} else if (!guiIsAdmin && coreIsAdmin) {
|
||||||
|
console.log('GUI is not admin but core is elevated, attempting graceful handling')
|
||||||
|
await handleElevatedCoreScenario()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleElevatedCoreScenario(): Promise<void> {
|
||||||
|
const { tun } = await getControledMihomoConfig()
|
||||||
|
|
||||||
|
if (tun?.enable) {
|
||||||
|
console.log('TUN mode detected with elevated core, attempting graceful shutdown')
|
||||||
|
const success = await attemptGracefulCoreShutdown()
|
||||||
|
if (success) {
|
||||||
|
await patchControledMihomoConfig({ tun: { enable: false } })
|
||||||
|
const { mainWindow } = await import('../index')
|
||||||
|
mainWindow?.webContents.send('controledMihomoConfigUpdated')
|
||||||
|
ipcMain.emit('updateTrayMenu')
|
||||||
|
} else {
|
||||||
|
console.warn('Cannot shutdown elevated core gracefully, user intervention required')
|
||||||
|
throw new Error('Elevated core process requires manual intervention')
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Non-TUN mode, attempting graceful shutdown of elevated core')
|
||||||
|
const success = await attemptGracefulCoreShutdown()
|
||||||
|
if (!success) {
|
||||||
|
console.warn('Cannot shutdown elevated core gracefully')
|
||||||
|
throw new Error('Cannot manage elevated core process')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function attemptGracefulCoreShutdown(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const success = await shutdownViaMihomoAPI()
|
||||||
|
if (success) {
|
||||||
|
console.log('Core shutdown gracefully via API')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('API shutdown failed:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const success = await forceStopRunningCore()
|
||||||
|
if (success) {
|
||||||
|
console.log('Core stopped via process termination')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Force stop failed:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function shutdownViaMihomoAPI(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const { mihomoRestart } = await import('./mihomoApi')
|
||||||
|
|
||||||
|
await mihomoRestart()
|
||||||
|
|
||||||
|
await waitForProcessExit(3000)
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Mihomo API shutdown failed:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function forceStopRunningCore(): Promise<boolean> {
|
||||||
|
const pidFile = path.join(dataDir(), 'core.pid')
|
||||||
|
const stateFile = path.join(dataDir(), 'core.state')
|
||||||
|
|
||||||
|
if (!existsSync(pidFile)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pidStr = await readFile(pidFile, 'utf-8')
|
||||||
|
const pid = parseInt(pidStr.trim())
|
||||||
|
|
||||||
|
if (!isProcessRunning(pid)) {
|
||||||
|
await rm(pidFile).catch(() => {})
|
||||||
|
await rm(stateFile).catch(() => {})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
const currentIsAdmin = await checkAdminPrivileges()
|
||||||
|
if (!currentIsAdmin) {
|
||||||
|
console.warn('Cannot terminate elevated process without admin privileges')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.kill(pid, 'SIGTERM')
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||||
|
|
||||||
|
if (isProcessRunning(pid)) {
|
||||||
|
process.kill(pid, 'SIGKILL')
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500))
|
||||||
|
}
|
||||||
|
|
||||||
|
await rm(pidFile).catch(() => {})
|
||||||
|
await rm(stateFile).catch(() => {})
|
||||||
|
|
||||||
|
return !isProcessRunning(pid)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to force stop running core:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function waitForProcessExit(timeoutMs: number): Promise<boolean> {
|
||||||
|
const pidFile = path.join(dataDir(), 'core.pid')
|
||||||
|
|
||||||
|
if (!existsSync(pidFile)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const pidStr = await readFile(pidFile, 'utf-8')
|
||||||
|
const pid = parseInt(pidStr.trim())
|
||||||
|
|
||||||
|
const startTime = Date.now()
|
||||||
|
while (Date.now() - startTime < timeoutMs) {
|
||||||
|
if (!isProcessRunning(pid)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100))
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
} catch {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -51,6 +51,11 @@ export const patchMihomoConfig = async (patch: Partial<IMihomoConfig>): Promise<
|
|||||||
return await instance.patch('/configs', patch)
|
return await instance.patch('/configs', patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const mihomoRestart = async (): Promise<void> => {
|
||||||
|
const instance = await getAxios()
|
||||||
|
return await instance.post('/restart', {})
|
||||||
|
}
|
||||||
|
|
||||||
export const mihomoCloseConnection = async (id: string): Promise<void> => {
|
export const mihomoCloseConnection = async (id: string): Promise<void> => {
|
||||||
const instance = await getAxios()
|
const instance = await getAxios()
|
||||||
return await instance.delete(`/connections/${encodeURIComponent(id)}`)
|
return await instance.delete(`/connections/${encodeURIComponent(id)}`)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user