mirror of
https://github.com/pompurin404/mihomo-party.git
synced 2024-11-15 19:22:31 +08:00
add postinstall for macOS
This commit is contained in:
parent
64ed2864dc
commit
2164aa7520
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
|
@ -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
|
||||
|
|
6
build/pkg-scripts/postinstall
Normal file
6
build/pkg-scripts/postinstall
Normal 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
|
10
changelog.md
10
changelog.md
|
@ -1,3 +1,11 @@
|
|||
### Breaking Changes
|
||||
|
||||
- macOS 改用 pkg 安装方式,不再支持 dmg 安装方式,因此本次更新需要手动下载安装包进行安装
|
||||
|
||||
### New Features
|
||||
|
||||
- Linux 不再存储 root 密码
|
||||
- macOS/Linux 均不再存储 root 密码
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- 修复 macOS 10.15 无法安装的问题
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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),请到虚拟网卡页面重新手动授权内核'
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue
Block a user