add postinstall for macOS

This commit is contained in:
pompurin404 2024-10-16 17:16:54 +08:00
parent 64ed2864dc
commit 2164aa7520
No known key found for this signature in database
10 changed files with 44 additions and 147 deletions

View File

@ -199,6 +199,7 @@ jobs:
npm_config_target_arch: ${{ matrix.arch }}
run: |
sed -i "" -e "s/productName: mihomo-party/productName: Mihomo Party/" electron-builder.yml
chmod +x build/pkg-scripts/postinstall
pnpm build:mac --${{ matrix.arch }}
- name: Generate checksums
run: pnpm checksum .pkg

View File

@ -0,0 +1,6 @@
#!/bin/sh
chown root:admin $2/Mihomo\ Party.app/Contents/Resources/sidecar/mihomo
chown root:admin $2/Mihomo\ Party.app/Contents/Resources/sidecar/mihomo-alpha
chmod +s $2/Mihomo\ Party.app/Contents/Resources/sidecar/mihomo
chmod +s $2/Mihomo\ Party.app/Contents/Resources/sidecar/mihomo-alpha
exit 0

View File

@ -1,3 +1,11 @@
### Breaking Changes
- macOS 改用 pkg 安装方式,不再支持 dmg 安装方式,因此本次更新需要手动下载安装包进行安装
### New Features
- Linux 不再存储 root 密码
- macOS/Linux 均不再存储 root 密码
### Bug Fixes
- 修复 macOS 10.15 无法安装的问题

View File

@ -17,7 +17,7 @@ import {
patchAppConfig,
patchControledMihomoConfig
} from '../config'
import { app, dialog, ipcMain, net, safeStorage } from 'electron'
import { app, dialog, ipcMain, net } from 'electron'
import {
startMihomoTraffic,
startMihomoConnections,
@ -57,27 +57,12 @@ let child: ChildProcess
let retry = 10
export async function startCore(detached = false): Promise<Promise<void>[]> {
const {
core = 'mihomo',
autoSetDNS = true,
encryptedPassword,
diffWorkDir = false
} = await getAppConfig()
const { core = 'mihomo', autoSetDNS = true, diffWorkDir = false } = await getAppConfig()
const { 'log-level': logLevel } = await getControledMihomoConfig()
if (existsSync(path.join(dataDir(), 'core.pid'))) {
const pid = parseInt(await readFile(path.join(dataDir(), 'core.pid'), 'utf-8'))
try {
process.kill(pid, 'SIGINT')
} catch {
if (process.platform === 'darwin' && encryptedPassword && isEncryptionAvailable()) {
const execPromise = promisify(exec)
const password = safeStorage.decryptString(Buffer.from(encryptedPassword))
try {
await execPromise(`echo "${password}" | sudo -S kill ${pid}`)
} catch {
// ignore
}
}
} finally {
await rm(path.join(dataDir(), 'core.pid'))
}
@ -85,7 +70,6 @@ export async function startCore(detached = false): Promise<Promise<void>[]> {
const { current } = await getProfileConfig()
const { tun } = await getControledMihomoConfig()
const corePath = mihomoCorePath(core)
await autoGrantCorePermition(corePath)
await generateProfile()
await checkProfile()
await stopCore()
@ -242,24 +226,6 @@ async function checkProfile(): Promise<void> {
}
}
export async function autoGrantCorePermition(corePath: string): Promise<void> {
if (process.platform !== 'darwin') return
const { encryptedPassword } = await getAppConfig()
const execPromise = promisify(exec)
if (encryptedPassword && isEncryptionAvailable()) {
try {
const password = safeStorage.decryptString(Buffer.from(encryptedPassword))
if (process.platform === 'darwin') {
await execPromise(`echo "${password}" | sudo -S chown root:admin "${corePath}"`)
await execPromise(`echo "${password}" | sudo -S chmod +sx "${corePath}"`)
}
} catch (error) {
patchAppConfig({ encryptedPassword: undefined })
throw error
}
}
}
export async function manualGrantCorePermition(password?: string): Promise<void> {
const { core = 'mihomo' } = await getAppConfig()
const corePath = mihomoCorePath(core)
@ -275,27 +241,19 @@ export async function manualGrantCorePermition(password?: string): Promise<void>
}
}
export function isEncryptionAvailable(): boolean {
return safeStorage.isEncryptionAvailable()
}
export async function getDefaultDevice(password?: string): Promise<string> {
export async function getDefaultDevice(): Promise<string> {
const execPromise = promisify(exec)
let sudo = ''
if (password) sudo = `echo "${password}" | sudo -S `
const { stdout: deviceOut } = await execPromise(`${sudo}route -n get default`)
const { stdout: deviceOut } = await execPromise(`route -n get default`)
let device = deviceOut.split('\n').find((s) => s.includes('interface:'))
device = device?.trim().split(' ').slice(1).join(' ')
if (!device) throw new Error('Get device failed')
return device
}
async function getDefaultService(password?: string): Promise<string> {
async function getDefaultService(): Promise<string> {
const execPromise = promisify(exec)
let sudo = ''
if (password) sudo = `echo "${password}" | sudo -S `
const device = await getDefaultDevice(password)
const { stdout: order } = await execPromise(`${sudo}networksetup -listnetworkserviceorder`)
const device = await getDefaultDevice()
const { stdout: order } = await execPromise(`networksetup -listnetworkserviceorder`)
const block = order.split('\n\n').find((s) => s.includes(`Device: ${device}`))
if (!block) throw new Error('Get networkservice failed')
for (const line of block.split('\n')) {
@ -306,12 +264,10 @@ async function getDefaultService(password?: string): Promise<string> {
throw new Error('Get service failed')
}
async function getOriginDNS(password?: string): Promise<void> {
async function getOriginDNS(): Promise<void> {
const execPromise = promisify(exec)
let sudo = ''
if (password) sudo = `echo "${password}" | sudo -S `
const service = await getDefaultService(password)
const { stdout: dns } = await execPromise(`${sudo}networksetup -getdnsservers "${service}"`)
const service = await getDefaultService()
const { stdout: dns } = await execPromise(`networksetup -getdnsservers "${service}"`)
if (dns.startsWith("There aren't any DNS Servers set on")) {
await patchAppConfig({ originDNS: 'Empty' })
} else {
@ -319,25 +275,19 @@ async function getOriginDNS(password?: string): Promise<void> {
}
}
async function setDNS(dns: string, password?: string): Promise<void> {
const service = await getDefaultService(password)
let sudo = ''
if (password) sudo = `echo "${password}" | sudo -S `
async function setDNS(dns: string): Promise<void> {
const service = await getDefaultService()
const execPromise = promisify(exec)
await execPromise(`${sudo}networksetup -setdnsservers "${service}" ${dns}`)
await execPromise(`networksetup -setdnsservers "${service}" ${dns}`)
}
async function setPublicDNS(): Promise<void> {
if (process.platform !== 'darwin') return
if (net.isOnline()) {
const { originDNS, encryptedPassword } = await getAppConfig()
const { originDNS } = await getAppConfig()
if (!originDNS) {
let password: string | undefined
if (encryptedPassword && isEncryptionAvailable()) {
password = safeStorage.decryptString(Buffer.from(encryptedPassword))
}
await getOriginDNS(password)
await setDNS('223.5.5.5', password)
await getOriginDNS()
await setDNS('223.5.5.5')
}
} else {
if (setPublicDNSTimer) clearTimeout(setPublicDNSTimer)
@ -348,13 +298,9 @@ async function setPublicDNS(): Promise<void> {
async function recoverDNS(): Promise<void> {
if (process.platform !== 'darwin') return
if (net.isOnline()) {
const { originDNS, encryptedPassword } = await getAppConfig()
const { originDNS } = await getAppConfig()
if (originDNS) {
let password: string | undefined
if (encryptedPassword && isEncryptionAvailable()) {
password = safeStorage.decryptString(Buffer.from(encryptedPassword))
}
await setDNS(originDNS, password)
await setDNS(originDNS)
await patchAppConfig({ originDNS: undefined })
}
} else {

View File

@ -91,20 +91,9 @@ export async function downloadAndInstallUpdate(version: string): Promise<void> {
if (file.endsWith('.pkg')) {
try {
const execPromise = promisify(exec)
const name = exePath().split('.app')[0].replace('/Applications/', '')
await execPromise(
`hdiutil attach "${path.join(dataDir(), file)}" -mountpoint "/Volumes/mihomo-party" -nobrowse`
)
try {
await execPromise(`mv "/Applications/${name}.app" /tmp`)
await execPromise('cp -R "/Volumes/mihomo-party/Mihomo Party.app" /Applications/')
await execPromise(`rm -rf "/tmp/${name}.app"`)
} catch (e) {
await execPromise(`mv "/tmp/${name}.app" /Applications`)
throw e
} finally {
await execPromise('hdiutil detach "/Volumes/mihomo-party"')
}
const shell = `installer -pkg ${path.join(dataDir(), file).replace(' ', '\\\\ ')} -target /`
const command = `do shell script "${shell}" with administrator privileges`
await execPromise(`osascript -e '${command}'`)
app.relaunch()
app.quit()
} catch {

View File

@ -216,7 +216,7 @@ async function migration(): Promise<void> {
await patchAppConfig({ disableTray: false })
}
// remove password
if (process.platform === 'linux' && encryptedPassword) {
if (encryptedPassword) {
await patchAppConfig({ encryptedPassword: undefined })
}
}

View File

@ -1,4 +1,4 @@
import { app, dialog, ipcMain, safeStorage } from 'electron'
import { app, dialog, ipcMain } from 'electron'
import {
mihomoChangeProxy,
mihomoCloseAllConnections,
@ -51,12 +51,7 @@ import {
subStoreFrontendPort,
subStorePort
} from '../resolve/server'
import {
isEncryptionAvailable,
manualGrantCorePermition,
quitWithoutCore,
restartCore
} from '../core/manager'
import { manualGrantCorePermition, quitWithoutCore, restartCore } from '../core/manager'
import { triggerSysProxy } from '../sys/sysproxy'
import { checkUpdate, downloadAndInstallUpdate } from '../resolve/autoUpdater'
import {
@ -172,8 +167,6 @@ export function registerIpcMainHandlers(): void {
ipcMain.handle('restartCore', ipcErrorWrapper(restartCore))
ipcMain.handle('startMonitor', (_e, detached) => ipcErrorWrapper(startMonitor)(detached))
ipcMain.handle('triggerSysProxy', (_e, enable) => ipcErrorWrapper(triggerSysProxy)(enable))
ipcMain.handle('isEncryptionAvailable', isEncryptionAvailable)
ipcMain.handle('encryptString', (_e, str) => encryptString(str))
ipcMain.handle('manualGrantCorePermition', (_e, password) =>
ipcErrorWrapper(manualGrantCorePermition)(password)
)
@ -256,7 +249,3 @@ export function registerIpcMainHandlers(): void {
ipcMain.handle('quitWithoutCore', ipcErrorWrapper(quitWithoutCore))
ipcMain.handle('quitApp', () => app.quit())
}
function encryptString(str: string): number[] {
return safeStorage.encryptString(str).toJSON().data
}

View File

@ -3,19 +3,16 @@ import { useControledMihomoConfig } from '@renderer/hooks/use-controled-mihomo-c
import BorderSwitch from '@renderer/components/base/border-swtich'
import { TbDeviceIpadHorizontalBolt } from 'react-icons/tb'
import { useLocation } from 'react-router-dom'
import { encryptString, isEncryptionAvailable, restartCore } from '@renderer/utils/ipc'
import { restartCore } from '@renderer/utils/ipc'
import { useSortable } from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
import { platform } from '@renderer/utils/init'
import React, { useState } from 'react'
import React from 'react'
import { useAppConfig } from '@renderer/hooks/use-app-config'
import BasePasswordModal from '../base/base-password-modal'
const TunSwitcher: React.FC = () => {
const location = useLocation()
const match = location.pathname.includes('/tun') || false
const [openPasswordModal, setOpenPasswordModal] = useState(false)
const { appConfig, patchAppConfig } = useAppConfig()
const { appConfig } = useAppConfig()
const { tunCardStatus = 'col-span-1' } = appConfig || {}
const { controledMihomoConfig, patchControledMihomoConfig } = useControledMihomoConfig()
const { tun } = controledMihomoConfig || {}
@ -32,19 +29,6 @@ const TunSwitcher: React.FC = () => {
})
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null
const onChange = async (enable: boolean): Promise<void> => {
if (enable && platform === 'darwin') {
const encryptionAvailable = await isEncryptionAvailable()
if (!appConfig?.encryptedPassword && encryptionAvailable) {
setOpenPasswordModal(true)
return
}
if (!appConfig?.encryptedPassword && !encryptionAvailable) {
alert('加密不可用,请手动给内核授权')
await patchAppConfig({ encryptedPassword: [] })
return
}
}
if (enable) {
await patchControledMihomoConfig({ tun: { enable }, dns: { enable: true } })
} else {
@ -65,24 +49,6 @@ const TunSwitcher: React.FC = () => {
}}
className={`${tunCardStatus} tun-card`}
>
{openPasswordModal && (
<BasePasswordModal
onCancel={() => setOpenPasswordModal(false)}
onConfirm={async (password: string) => {
try {
const encrypted = await encryptString(password)
await patchAppConfig({ encryptedPassword: encrypted })
await patchControledMihomoConfig({ tun: { enable: true }, dns: { enable: true } })
await restartCore()
window.electron.ipcRenderer.send('updateTrayMenu')
setOpenPasswordModal(false)
} catch (e) {
alert(e)
}
}}
/>
)}
<Card
fullWidth
ref={setNodeRef}

View File

@ -89,7 +89,7 @@ const Mihomo: React.FC = () => {
setTimeout(() => {
PubSub.publish('mihomo-core-changed')
}, 2000)
if (platform === 'linux') {
if (platform !== 'win32') {
new Notification('内核权限丢失', {
body: '内核升级成功若要使用虚拟网卡Tun请到虚拟网卡页面重新手动授权内核'
})

View File

@ -199,14 +199,6 @@ export async function triggerSysProxy(enable: boolean): Promise<void> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('triggerSysProxy', enable))
}
export async function isEncryptionAvailable(): Promise<boolean> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('isEncryptionAvailable'))
}
export async function encryptString(str: string): Promise<number[]> {
return ipcErrorWrapper(await window.electron.ipcRenderer.invoke('encryptString', str))
}
export async function manualGrantCorePermition(password?: string): Promise<void> {
return ipcErrorWrapper(
await window.electron.ipcRenderer.invoke('manualGrantCorePermition', password)