Compare commits

..

No commits in common. "674cefcc29a4aef227fac86726cda7ca61279d42" and "151726fcce7b23a07d8948c49ec5c8bcb25fc01d" have entirely different histories.

10 changed files with 14 additions and 181 deletions

View File

@ -151,23 +151,4 @@ else
fi fi
log "Installation completed successfully" log "Installation completed successfully"
# Fix user data directory permissions
log "Fixing user data directory permissions..."
for user_home in /Users/*; do
if [ -d "$user_home" ] && [ "$(basename "$user_home")" != "Shared" ] && [ "$(basename "$user_home")" != ".localized" ]; then
username=$(basename "$user_home")
user_data_dir="$user_home/Library/Application Support/mihomo-party"
if [ -d "$user_data_dir" ]; then
current_owner=$(stat -f "%Su" "$user_data_dir" 2>/dev/null || echo "unknown")
if [ "$current_owner" = "root" ]; then
log "Fixing ownership for user: $username"
chown -R "$username:staff" "$user_data_dir" 2>/dev/null || true
chmod -R u+rwX "$user_data_dir" 2>/dev/null || true
fi
fi
fi
done
exit 0 exit 0

View File

@ -10,10 +10,8 @@ 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 { execSync, spawn } from 'child_process'
import { createElevateTask } from './sys/misc' import { createElevateTask } from './sys/misc'
import { promisify } from 'util'
import { stat } from 'fs/promises'
import { initProfileUpdater } from './core/profileUpdater' import { initProfileUpdater } from './core/profileUpdater'
import { existsSync, writeFileSync } from 'fs' import { existsSync, writeFileSync } from 'fs'
import { exePath, taskDir } from './utils/dirs' import { exePath, taskDir } from './utils/dirs'
@ -24,29 +22,6 @@ import iconv from 'iconv-lite'
import { initI18n } from '../shared/i18n' import { initI18n } from '../shared/i18n'
import i18next from 'i18next' import i18next from 'i18next'
async function fixUserDataPermissions(): Promise<void> {
if (process.platform !== 'darwin') return
const userDataPath = app.getPath('userData')
if (!existsSync(userDataPath)) return
try {
const stats = await stat(userDataPath)
const currentUid = process.getuid?.() || 0
if (stats.uid === 0 && currentUid !== 0) {
const execPromise = promisify(exec)
const username = process.env.USER || process.env.LOGNAME
if (username) {
await execPromise(`chown -R "${username}:staff" "${userDataPath}"`)
await execPromise(`chmod -R u+rwX "${userDataPath}"`)
}
}
} catch {
// ignore
}
}
let quitTimeout: NodeJS.Timeout | null = null let quitTimeout: NodeJS.Timeout | null = null
export let mainWindow: BrowserWindow | null = null export let mainWindow: BrowserWindow | null = null
@ -84,26 +59,11 @@ if (process.platform === 'win32' && !is.dev && !process.argv.includes('noadmin')
} }
} }
async function initApp(): Promise<void> {
await fixUserDataPermissions()
}
initApp()
.then(() => {
const gotTheLock = app.requestSingleInstanceLock() const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) { if (!gotTheLock) {
app.quit() app.quit()
} }
})
.catch(() => {
// ignore permission fix errors
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
app.quit()
}
})
export function customRelaunch(): void { export function customRelaunch(): void {
const script = `while kill -0 ${process.pid} 2>/dev/null; do const script = `while kill -0 ${process.pid} 2>/dev/null; do

View File

@ -19,8 +19,7 @@ export async function webdavBackup(): Promise<boolean> {
webdavUrl = '', webdavUrl = '',
webdavUsername = '', webdavUsername = '',
webdavPassword = '', webdavPassword = '',
webdavDir = 'mihomo-party', webdavDir = 'mihomo-party'
webdavMaxBackups = 0
} = await getAppConfig() } = await getAppConfig()
const zip = new AdmZip() const zip = new AdmZip()
@ -45,41 +44,7 @@ export async function webdavBackup(): Promise<boolean> {
// ignore // ignore
} }
const result = await client.putFileContents(`${webdavDir}/${zipFileName}`, zip.toBuffer()) return await client.putFileContents(`${webdavDir}/${zipFileName}`, zip.toBuffer())
if (webdavMaxBackups > 0) {
try {
const files = await client.getDirectoryContents(webdavDir, { glob: '*.zip' })
const fileList = Array.isArray(files) ? files : files.data
const currentPlatformFiles = fileList.filter((file) => {
return file.basename.startsWith(`${process.platform}_`)
})
currentPlatformFiles.sort((a, b) => {
const timeA = a.basename.match(/_(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})\.zip$/)?.[1] || ''
const timeB = b.basename.match(/_(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})\.zip$/)?.[1] || ''
return timeB.localeCompare(timeA)
})
if (currentPlatformFiles.length > webdavMaxBackups) {
const filesToDelete = currentPlatformFiles.slice(webdavMaxBackups)
for (let i = 0; i < filesToDelete.length; i++) {
const file = filesToDelete[i]
await client.deleteFile(`${webdavDir}/${file.basename}`)
if (i < filesToDelete.length - 1) {
await new Promise((resolve) => setTimeout(resolve, 500))
}
}
}
} catch (error) {
console.error('Failed to clean up old backup files:', error)
}
}
return result
} }
export async function webdavRestore(filename: string): Promise<void> { export async function webdavRestore(filename: string): Promise<void> {

View File

@ -22,10 +22,8 @@ import {
defaultProfileConfig defaultProfileConfig
} from './template' } from './template'
import yaml from 'yaml' import yaml from 'yaml'
import { mkdir, writeFile, rm, readdir, cp, stat } from 'fs/promises' import { mkdir, writeFile,rm, readdir, cp } from 'fs/promises'
import { existsSync } from 'fs' import { existsSync } from 'fs'
import { exec } from 'child_process'
import { promisify } from 'util'
import path from 'path' import path from 'path'
import { import {
startPacServer, startPacServer,
@ -42,32 +40,7 @@ import {
import { app } from 'electron' import { app } from 'electron'
import { startSSIDCheck } from '../sys/ssid' import { startSSIDCheck } from '../sys/ssid'
async function fixDataDirPermissions(): Promise<void> {
if (process.platform !== 'darwin') return
const dataDirPath = dataDir()
if (!existsSync(dataDirPath)) return
try {
const stats = await stat(dataDirPath)
const currentUid = process.getuid?.() || 0
if (stats.uid === 0 && currentUid !== 0) {
const execPromise = promisify(exec)
const username = process.env.USER || process.env.LOGNAME
if (username) {
await execPromise(`chown -R "${username}:staff" "${dataDirPath}"`)
await execPromise(`chmod -R u+rwX "${dataDirPath}"`)
}
}
} catch {
// ignore
}
}
async function initDirs(): Promise<void> { async function initDirs(): Promise<void> {
await fixDataDirPermissions()
if (!existsSync(dataDir())) { if (!existsSync(dataDir())) {
await mkdir(dataDir()) await mkdir(dataDir())
} }

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import SettingCard from '../base/base-setting-card' import SettingCard from '../base/base-setting-card'
import SettingItem from '../base/base-setting-item' import SettingItem from '../base/base-setting-item'
import { Button, Input, Select, SelectItem } from '@heroui/react' import { Button, Input } from '@heroui/react'
import { listWebdavBackups, webdavBackup } from '@renderer/utils/ipc' import { listWebdavBackups, webdavBackup } from '@renderer/utils/ipc'
import WebdavRestoreModal from './webdav-restore-modal' import WebdavRestoreModal from './webdav-restore-modal'
import debounce from '@renderer/utils/debounce' import debounce from '@renderer/utils/debounce'
@ -11,31 +11,16 @@ import { useTranslation } from 'react-i18next'
const WebdavConfig: React.FC = () => { const WebdavConfig: React.FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const { appConfig, patchAppConfig } = useAppConfig() const { appConfig, patchAppConfig } = useAppConfig()
const { const { webdavUrl, webdavUsername, webdavPassword, webdavDir = 'mihomo-party' } = appConfig || {}
webdavUrl,
webdavUsername,
webdavPassword,
webdavDir = 'mihomo-party',
webdavMaxBackups = 0
} = appConfig || {}
const [backuping, setBackuping] = useState(false) const [backuping, setBackuping] = useState(false)
const [restoring, setRestoring] = useState(false) const [restoring, setRestoring] = useState(false)
const [filenames, setFilenames] = useState<string[]>([]) const [filenames, setFilenames] = useState<string[]>([])
const [restoreOpen, setRestoreOpen] = useState(false) const [restoreOpen, setRestoreOpen] = useState(false)
const [webdav, setWebdav] = useState({ const [webdav, setWebdav] = useState({ webdavUrl, webdavUsername, webdavPassword, webdavDir })
webdavUrl, const setWebdavDebounce = debounce(({ webdavUrl, webdavUsername, webdavPassword, webdavDir }) => {
webdavUsername, patchAppConfig({ webdavUrl, webdavUsername, webdavPassword, webdavDir })
webdavPassword, }, 500)
webdavDir,
webdavMaxBackups
})
const setWebdavDebounce = debounce(
({ webdavUrl, webdavUsername, webdavPassword, webdavDir, webdavMaxBackups }) => {
patchAppConfig({ webdavUrl, webdavUsername, webdavPassword, webdavDir, webdavMaxBackups })
},
500
)
const handleBackup = async (): Promise<void> => { const handleBackup = async (): Promise<void> => {
setBackuping(true) setBackuping(true)
try { try {
@ -113,28 +98,6 @@ const WebdavConfig: React.FC = () => {
}} }}
/> />
</SettingItem> </SettingItem>
<SettingItem title={t('webdav.maxBackups')} divider>
<Select
classNames={{ trigger: 'data-[hover=true]:bg-default-200' }}
className="w-[150px]"
size="sm"
selectedKeys={new Set([webdav.webdavMaxBackups.toString()])}
aria-label={t('webdav.maxBackups')}
onSelectionChange={(v) => {
const value = Number.parseInt(Array.from(v)[0] as string, 10)
setWebdav({ ...webdav, webdavMaxBackups: value })
setWebdavDebounce({ ...webdav, webdavMaxBackups: value })
}}
>
<SelectItem key="0">{t('webdav.noLimit')}</SelectItem>
<SelectItem key="1">1</SelectItem>
<SelectItem key="3">3</SelectItem>
<SelectItem key="5">5</SelectItem>
<SelectItem key="10">10</SelectItem>
<SelectItem key="15">15</SelectItem>
<SelectItem key="20">20</SelectItem>
</Select>
</SettingItem>
<div className="flex justify0between"> <div className="flex justify0between">
<Button isLoading={backuping} fullWidth size="sm" className="mr-1" onPress={handleBackup}> <Button isLoading={backuping} fullWidth size="sm" className="mr-1" onPress={handleBackup}>
{t('webdav.backup')} {t('webdav.backup')}

View File

@ -179,8 +179,6 @@
"webdav.dir": "WebDAV Backup Directory", "webdav.dir": "WebDAV Backup Directory",
"webdav.username": "WebDAV Username", "webdav.username": "WebDAV Username",
"webdav.password": "WebDAV Password", "webdav.password": "WebDAV Password",
"webdav.maxBackups": "Max Backups",
"webdav.noLimit": "No Limit",
"webdav.backup": "Backup", "webdav.backup": "Backup",
"webdav.restore.title": "Restore Backup", "webdav.restore.title": "Restore Backup",
"webdav.restore.noBackups": "No backups available", "webdav.restore.noBackups": "No backups available",

View File

@ -179,8 +179,6 @@
"webdav.dir": "پوشه پشتیبان‌گیری WebDAV", "webdav.dir": "پوشه پشتیبان‌گیری WebDAV",
"webdav.username": "نام کاربری WebDAV", "webdav.username": "نام کاربری WebDAV",
"webdav.password": "رمز عبور WebDAV", "webdav.password": "رمز عبور WebDAV",
"webdav.maxBackups": "حداکثر نسخه پشتیبان",
"webdav.noLimit": "بدون محدودیت",
"webdav.backup": "پشتیبان‌گیری", "webdav.backup": "پشتیبان‌گیری",
"webdav.restore.title": "بازیابی پشتیبان", "webdav.restore.title": "بازیابی پشتیبان",
"webdav.restore.noBackups": "هیچ پشتیبانی موجود نیست", "webdav.restore.noBackups": "هیچ پشتیبانی موجود نیست",

View File

@ -179,8 +179,6 @@
"webdav.dir": "Каталог резервных копий WebDAV", "webdav.dir": "Каталог резервных копий WebDAV",
"webdav.username": "Имя пользователя WebDAV", "webdav.username": "Имя пользователя WebDAV",
"webdav.password": "Пароль WebDAV", "webdav.password": "Пароль WebDAV",
"webdav.maxBackups": "Максимум резервных копий",
"webdav.noLimit": "Без ограничений",
"webdav.backup": "Резервное копирование", "webdav.backup": "Резервное копирование",
"webdav.restore.title": "Восстановление резервной копии", "webdav.restore.title": "Восстановление резервной копии",
"webdav.restore.noBackups": "Нет доступных резервных копий", "webdav.restore.noBackups": "Нет доступных резервных копий",

View File

@ -179,8 +179,6 @@
"webdav.dir": "WebDAV 备份目录", "webdav.dir": "WebDAV 备份目录",
"webdav.username": "WebDAV 用户名", "webdav.username": "WebDAV 用户名",
"webdav.password": "WebDAV 密码", "webdav.password": "WebDAV 密码",
"webdav.maxBackups": "最大备份数",
"webdav.noLimit": "不限制",
"webdav.backup": "备份", "webdav.backup": "备份",
"webdav.restore.title": "恢复备份", "webdav.restore.title": "恢复备份",
"webdav.restore.noBackups": "还没有备份", "webdav.restore.noBackups": "还没有备份",

View File

@ -284,7 +284,6 @@ interface IAppConfig {
webdavDir?: string webdavDir?: string
webdavUsername?: string webdavUsername?: string
webdavPassword?: string webdavPassword?: string
webdavMaxBackups?: number
useNameserverPolicy: boolean useNameserverPolicy: boolean
nameserverPolicy: { [key: string]: string | string[] } nameserverPolicy: { [key: string]: string | string[] }
showWindowShortcut?: string showWindowShortcut?: string