mirror of
https://github.com/OpenListTeam/OpenList-Desktop.git
synced 2025-11-25 03:14:56 +08:00
267 lines
8.5 KiB
JavaScript
267 lines
8.5 KiB
JavaScript
import { execSync } from 'node:child_process'
|
|
import crypto from 'node:crypto'
|
|
import fsp from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
|
|
import AdmZip from 'adm-zip'
|
|
import fs from 'fs-extra'
|
|
import fetch from 'node-fetch'
|
|
import { extract } from 'tar'
|
|
|
|
import { downloadFile, getFetchOptions, getOpenlistArchMap, getRcloneArchMap, platformIdentifierMap } from './utils.js'
|
|
|
|
const cwd = process.cwd()
|
|
const target = process.argv.find(arg => arg.startsWith('--target='))?.slice(9)
|
|
const platformArch = target ? platformIdentifierMap[target] : `${process.platform}-${process.arch}`
|
|
const [platform, arch] = platformArch.split('-')
|
|
const isWin = platform === 'win32'
|
|
|
|
console.log(`platform: ${platform}, arch: ${arch}`)
|
|
|
|
const sidecarHost =
|
|
target ||
|
|
execSync('rustc -vV')
|
|
.toString()
|
|
.match(/(?<=host: ).+(?=\s*)/g)[0]
|
|
|
|
if (!getOpenlistArchMap[platformArch]) {
|
|
throw new Error(`Unsupported target: ${platformArch}. Supported: ${Object.keys(getOpenlistArchMap).join(', ')}`)
|
|
}
|
|
|
|
// Rclone version management
|
|
let rcloneVersion = 'v1.70.3'
|
|
const rcloneVersionUrl = 'https://github.com/rclone/rclone/releases/latest/download/version.txt'
|
|
|
|
async function getLatestRcloneVersion() {
|
|
try {
|
|
const response = await fetch(rcloneVersionUrl, getFetchOptions())
|
|
rcloneVersion = (await response.text()).trim().replace('rclone ', '') || '1.70.1'
|
|
console.log(`Latest rclone version: ${rcloneVersion}`)
|
|
} catch (error) {
|
|
console.log('Error fetching latest rclone version:', error.message)
|
|
}
|
|
}
|
|
|
|
// openlist version management
|
|
let openlistVersion = 'v4.0.8'
|
|
|
|
async function getLatestOpenlistVersion() {
|
|
try {
|
|
const response = await fetch(
|
|
'https://api.github.com/repos/OpenListTeam/OpenList/releases/latest',
|
|
getFetchOptions()
|
|
)
|
|
const data = await response.json()
|
|
openlistVersion = data.tag_name || 'v4.0.8'
|
|
console.log(`Latest OpenList version: ${openlistVersion}`)
|
|
} catch (error) {
|
|
console.log('Error fetching latest OpenList version:', error.message)
|
|
}
|
|
}
|
|
|
|
const createBinaryInfo = (name, archMap, baseUrl, version = '') => {
|
|
const zipName = archMap[platformArch]
|
|
return {
|
|
name,
|
|
targetFile: `${name}-${sidecarHost}${isWin ? '.exe' : ''}`,
|
|
exeFile: `${name}${isWin ? '.exe' : ''}`,
|
|
zipFile: zipName,
|
|
downloadURL: `${baseUrl}${version ? `/${version}` : ''}/${zipName}`
|
|
}
|
|
}
|
|
|
|
// OpenList service URL
|
|
const serviceUrl = `https://github.com/OpenListTeam/openlist-desktop-service/releases/download/${sidecarHost}`
|
|
|
|
const getServiceInfo = exeName => {
|
|
const ext = isWin ? '.exe' : ''
|
|
const suffix = '-' + sidecarHost
|
|
return {
|
|
file: exeName + suffix + ext,
|
|
downloadURL: `${serviceUrl}/${exeName}${ext}`
|
|
}
|
|
}
|
|
|
|
const resolvePlugins = async () => {
|
|
const pluginDir = path.join(process.env.APPDATA, 'Local/NSIS')
|
|
await fs.mkdir(pluginDir, { recursive: true })
|
|
await resolveSimpleServicePlugin(pluginDir)
|
|
await resolveAccessControlPlugin(pluginDir)
|
|
}
|
|
|
|
const resolveSimpleServicePlugin = async pluginDir => {
|
|
const url = 'https://nsis.sourceforge.io/mediawiki/images/e/ef/NSIS_Simple_Service_Plugin_Unicode_1.30.zip'
|
|
const TEMP_DIR = path.join(cwd, 'temp')
|
|
const tempDir = path.join(TEMP_DIR, 'SimpleSC')
|
|
const tempZip = path.join(tempDir, 'NSIS_Simple_Service_Plugin_Unicode_1.30.zip')
|
|
const tempDll = path.join(tempDir, 'SimpleSC.dll')
|
|
const pluginPath = path.join(pluginDir, 'SimpleSC.dll')
|
|
|
|
await fs.mkdir(tempDir, { recursive: true })
|
|
if (fs.existsSync(pluginPath)) return
|
|
|
|
try {
|
|
if (!fs.existsSync(tempZip)) {
|
|
await downloadFile(url, tempZip)
|
|
}
|
|
const zip = new AdmZip(tempZip)
|
|
zip.extractAllTo(tempDir, true)
|
|
await fsp.cp(tempDll, pluginPath, { recursive: true, force: true })
|
|
console.log(`SimpleSC.dll copied to ${pluginPath}`)
|
|
} finally {
|
|
await fsp.rm(tempDir, { recursive: true, force: true })
|
|
}
|
|
}
|
|
|
|
const calculateSha256 = async filePath => {
|
|
const hash = crypto.createHash('sha256')
|
|
const fileStream = fs.createReadStream(filePath)
|
|
fileStream.on('data', chunk => hash.update(chunk))
|
|
fileStream.on('end', () => {
|
|
const digest = hash.digest('hex')
|
|
console.log(`SHA-256 hash of ${filePath}: ${digest}`)
|
|
})
|
|
}
|
|
|
|
const resolveAccessControlPlugin = async pluginDir => {
|
|
const url = 'https://nsis.sourceforge.io/mediawiki/images/4/4a/AccessControl.zip'
|
|
const TEMP_DIR = path.join(cwd, 'temp')
|
|
const tempDir = path.join(TEMP_DIR, 'AccessControl')
|
|
const tempZip = path.join(tempDir, 'AccessControl.zip')
|
|
const tempDll = path.join(tempDir, 'Plugins', 'AccessControl.dll')
|
|
const pluginPath = path.join(pluginDir, 'Plugins', 'x86-unicode', 'AccessControl.dll')
|
|
const pluginPathB = path.join(pluginDir, 'AccessControl.dll')
|
|
|
|
await fs.mkdir(tempDir, { recursive: true })
|
|
if (fs.existsSync(pluginPath)) return
|
|
|
|
try {
|
|
if (!fs.existsSync(tempZip)) {
|
|
await downloadFile(url, tempZip)
|
|
}
|
|
const zip = new AdmZip(tempZip)
|
|
zip.extractAllTo(tempDir, true)
|
|
|
|
let sourcePath = tempDll
|
|
if (!fs.existsSync(sourcePath)) {
|
|
const altPaths = [
|
|
path.join(tempDir, 'AccessControl.dll'),
|
|
path.join(tempDir, 'Plugins', 'i386-unicode', 'AccessControl.dll')
|
|
]
|
|
for (const altPath of altPaths) {
|
|
if (fs.existsSync(altPath)) {
|
|
sourcePath = altPath
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if (fs.existsSync(sourcePath)) {
|
|
await fsp.cp(sourcePath, pluginPath, { recursive: true, force: true })
|
|
await fsp.cp(sourcePath, pluginPathB, { recursive: true, force: true })
|
|
console.log(`AccessControl.dll copied to ${pluginPath}`)
|
|
} else {
|
|
console.warn('AccessControl.dll not found in the extracted archive')
|
|
}
|
|
} finally {
|
|
await fsp.rm(tempDir, { recursive: true, force: true })
|
|
}
|
|
}
|
|
|
|
async function resolveSidecar(binInfo) {
|
|
const { name, targetFile, zipFile, exeFile, downloadURL } = binInfo
|
|
const binaryDir = path.join(cwd, 'src-tauri', 'binary')
|
|
const binaryPath = path.join(binaryDir, targetFile)
|
|
|
|
await fs.mkdir(binaryDir, { recursive: true })
|
|
const zipPath = path.join(binaryDir, zipFile)
|
|
|
|
try {
|
|
await downloadFile(downloadURL, zipPath)
|
|
|
|
if (zipFile.endsWith('.zip')) {
|
|
const zip = new AdmZip(zipPath)
|
|
zip.extractAllTo(binaryDir, true)
|
|
|
|
if (name === 'rclone') {
|
|
const extractedDir = path.join(binaryDir, zipFile.replace('.zip', ''))
|
|
await fs.rename(path.join(extractedDir, exeFile), binaryPath)
|
|
await fs.remove(extractedDir)
|
|
} else {
|
|
await fs.rename(path.join(binaryDir, exeFile), binaryPath)
|
|
}
|
|
} else {
|
|
await extract({ cwd: binaryDir, file: zipPath })
|
|
await fs.rename(path.join(binaryDir, exeFile), binaryPath)
|
|
}
|
|
await fs.remove(zipPath)
|
|
await fs.chmod(binaryPath, 0o755)
|
|
await calculateSha256(binaryPath)
|
|
} catch (err) {
|
|
console.error(`Error preparing "${name}":`, err.message)
|
|
await fs.rm(binaryPath, { recursive: true, force: true })
|
|
throw err
|
|
}
|
|
}
|
|
|
|
async function resolveService(resourceInfo, isChmod = true, defaultMode = 0o755) {
|
|
const { file, downloadURL } = resourceInfo
|
|
const resourceDir = path.join(cwd, 'src-tauri', 'binary')
|
|
const resourcePath = path.join(resourceDir, file)
|
|
|
|
await fs.mkdir(resourceDir, { recursive: true })
|
|
|
|
try {
|
|
await downloadFile(downloadURL, resourcePath)
|
|
if (isChmod) {
|
|
await fs.chmod(resourcePath, defaultMode)
|
|
}
|
|
console.log(`"${file}" downloaded to ${resourcePath}`)
|
|
} catch (err) {
|
|
console.error(`Error preparing "${file}":`, err.message)
|
|
await fs.rm(resourcePath, { recursive: true, force: true })
|
|
throw err
|
|
}
|
|
}
|
|
|
|
async function retryTask(name, fn, maxRetries = 5) {
|
|
for (let i = 0; i < maxRetries; i++) {
|
|
try {
|
|
console.log(`task::${name} try ${i}`)
|
|
await fn()
|
|
return
|
|
} catch (err) {
|
|
console.log(`task::${name} try ${i} ==`, err.message)
|
|
if (i === maxRetries - 1) throw err
|
|
}
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
await getLatestOpenlistVersion()
|
|
await retryTask('openlist', () =>
|
|
resolveSidecar(
|
|
createBinaryInfo(
|
|
'openlist',
|
|
getOpenlistArchMap,
|
|
'https://github.com/OpenListTeam/OpenList/releases/download',
|
|
openlistVersion
|
|
)
|
|
)
|
|
)
|
|
|
|
await retryTask('rclone', async () => {
|
|
await getLatestRcloneVersion()
|
|
await resolveSidecar(
|
|
createBinaryInfo('rclone', getRcloneArchMap(rcloneVersion), `https://downloads.rclone.org`, rcloneVersion)
|
|
)
|
|
})
|
|
if (isWin) {
|
|
await resolvePlugins()
|
|
}
|
|
await resolveService(getServiceInfo('install-openlist-service'))
|
|
await resolveService(getServiceInfo('openlist-desktop-service'))
|
|
await resolveService(getServiceInfo('uninstall-openlist-service'))
|
|
}
|
|
|
|
main().catch(console.log)
|