From c0d93fc3f89d314d28b49c9e8a0b5c14e00e4f8d Mon Sep 17 00:00:00 2001 From: whyour Date: Sun, 30 Jun 2024 00:47:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=89=E8=A3=85=20linux=20=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E8=AF=86=E5=88=AB=20alpine=20=E5=92=8C=20deb?= =?UTF-8?q?ian?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- back/config/const.ts | 35 +++++++++ back/config/util.ts | 147 +++++++++++++++++++++++++++++++++++- back/services/dependence.ts | 48 +++++++++--- back/services/system.ts | 34 +++------ shell/bot.sh | 10 ++- shell/start.sh | 3 - 6 files changed, 237 insertions(+), 40 deletions(-) diff --git a/back/config/const.ts b/back/config/const.ts index 1a696604..5a6830d2 100644 --- a/back/config/const.ts +++ b/back/config/const.ts @@ -25,3 +25,38 @@ export const SAMPLE_FILES = [ ]; export const PYTHON_INSTALL_DIR = process.env.PYTHON_HOME; + +export const LINUX_DEPENDENCE_COMMAND: Record< + 'Debian' | 'Ubuntu' | 'Alpine', + { + install: string; + uninstall: string; + info: string; + check(info: string): boolean; + } +> = { + Debian: { + install: 'apt install -y', + uninstall: 'apt remove -y', + info: 'apt info', + check(info: string) { + return info.includes('apt-manual-installed'); + }, + }, + Ubuntu: { + install: 'apt install -y', + uninstall: 'apt remove -y', + info: 'apt info', + check(info: string) { + return info.includes('apt-manual-installed'); + }, + }, + Alpine: { + install: 'apk add --no-check-certificate', + uninstall: 'apk del', + info: 'apk info -es', + check(info: string) { + return info.includes('installed'); + }, + }, +}; diff --git a/back/config/util.ts b/back/config/util.ts index e6a77219..d485052b 100644 --- a/back/config/util.ts +++ b/back/config/util.ts @@ -10,9 +10,12 @@ import Logger from '../loaders/logger'; import { writeFileWithLock } from '../shared/utils'; import { DependenceTypes } from '../data/dependence'; import { FormData } from 'undici'; +import os from 'os'; export * from './share'; +let osType: 'Debian' | 'Ubuntu' | 'Alpine' | undefined; + export async function getFileContentByName(fileName: string) { const _exsit = await fileExist(fileName); if (_exsit) { @@ -533,7 +536,7 @@ export function getInstallCommand(type: DependenceTypes, name: string): string { [DependenceTypes.nodejs]: 'pnpm add -g', [DependenceTypes.python3]: 'pip3 install --disable-pip-version-check --root-user-action=ignore', - [DependenceTypes.linux]: 'apk add --no-check-certificate', + [DependenceTypes.linux]: 'apt install -y', }; let command = baseCommands[type]; @@ -553,7 +556,7 @@ export function getUninstallCommand( [DependenceTypes.nodejs]: 'pnpm remove -g', [DependenceTypes.python3]: 'pip3 uninstall --disable-pip-version-check --root-user-action=ignore -y', - [DependenceTypes.linux]: 'apk del', + [DependenceTypes.linux]: 'apt remove -y', }; return `${baseCommands[type]} ${name.trim()}`; @@ -562,3 +565,143 @@ export function getUninstallCommand( export function isDemoEnv() { return process.env.DeployEnv === 'demo'; } + +async function getOSReleaseInfo(): Promise { + const osRelease = await fs.readFile('/etc/os-release', 'utf8'); + return osRelease; +} + +function isDebian(osReleaseInfo: string): boolean { + return osReleaseInfo.includes('Debian'); +} + +function isUbuntu(osReleaseInfo: string): boolean { + return osReleaseInfo.includes('Ubuntu'); +} + +function isCentOS(osReleaseInfo: string): boolean { + return osReleaseInfo.includes('CentOS') || osReleaseInfo.includes('Red Hat'); +} + +function isAlpine(osReleaseInfo: string): boolean { + return osReleaseInfo.includes('Alpine'); +} + +export async function detectOS(): Promise< + 'Debian' | 'Ubuntu' | 'Alpine' | undefined +> { + if (osType) return osType; + const platform = os.platform(); + + if (platform === 'linux') { + const osReleaseInfo = await getOSReleaseInfo(); + if (isDebian(osReleaseInfo)) { + osType = 'Debian'; + } else if (isUbuntu(osReleaseInfo)) { + osType = 'Ubuntu'; + } else if (isAlpine(osReleaseInfo)) { + osType = 'Alpine'; + } else { + Logger.error(`Unknown Linux Distribution: ${osReleaseInfo}`); + console.error(`Unknown Linux Distribution: ${osReleaseInfo}`); + } + } else { + Logger.error(`Unsupported platform: ${platform}`); + console.error(`Unsupported platform: ${platform}`); + } + + return osType; +} + +async function getCurrentMirrorDomain( + filePath: string, +): Promise { + const fileContent = await fs.readFile(filePath, 'utf8'); + const lines = fileContent.split('\n'); + for (const line of lines) { + if (line.trim().startsWith('#')) { + continue; + } + const match = line.match(/https?:\/\/[^\/]+/); + if (match) { + return match[0]; + } + } + return null; +} + +async function replaceDomainInFile( + filePath: string, + oldDomainWithScheme: string, + newDomainWithScheme: string, +): Promise { + let fileContent = await fs.readFile(filePath, 'utf8'); + let updatedContent = fileContent.replace( + new RegExp(oldDomainWithScheme, 'g'), + newDomainWithScheme, + ); + + if (!newDomainWithScheme.endsWith('/')) { + newDomainWithScheme += '/'; + } + + await fs.writeFile(filePath, updatedContent, 'utf8'); +} + +async function _updateLinuxMirror( + osType: string, + mirrorDomainWithScheme: string, +): Promise { + let filePath: string, currentDomainWithScheme: string | null; + switch (osType) { + case 'Debian': + filePath = '/etc/apt/sources.list'; + currentDomainWithScheme = await getCurrentMirrorDomain(filePath); + if (currentDomainWithScheme) { + await replaceDomainInFile( + filePath, + currentDomainWithScheme, + mirrorDomainWithScheme || 'http://deb.debian.org', + ); + return 'apt update'; + } else { + throw Error(`Current mirror domain not found.`); + } + case 'Ubuntu': + filePath = '/etc/apt/sources.list'; + currentDomainWithScheme = await getCurrentMirrorDomain(filePath); + if (currentDomainWithScheme) { + await replaceDomainInFile( + filePath, + currentDomainWithScheme, + mirrorDomainWithScheme || 'http://archive.ubuntu.com', + ); + return 'apt update'; + } else { + throw Error(`Current mirror domain not found.`); + } + case 'Alpine': + filePath = '/etc/apk/repositories'; + currentDomainWithScheme = await getCurrentMirrorDomain(filePath); + if (currentDomainWithScheme) { + await replaceDomainInFile( + filePath, + currentDomainWithScheme, + mirrorDomainWithScheme || 'http://dl-cdn.alpinelinux.org', + ); + return 'apk update'; + } else { + throw Error(`Current mirror domain not found.`); + } + default: + throw Error('Unsupported OS type for updating mirrors.'); + } +} + +export async function updateLinuxMirrorFile(mirror: string): Promise { + const detectedOS = await detectOS(); + if (!detectedOS) { + throw Error(`Unknown Linux Distribution`); + } + return await _updateLinuxMirror(detectedOS, mirror); +} diff --git a/back/services/dependence.ts b/back/services/dependence.ts index fc676445..1d887657 100644 --- a/back/services/dependence.ts +++ b/back/services/dependence.ts @@ -22,6 +22,8 @@ import { } from '../config/util'; import dayjs from 'dayjs'; import taskLimit from '../shared/pLimit'; +import { detectOS } from '../config/util'; +import { LINUX_DEPENDENCE_COMMAND } from '../config/const'; @Service() export default class DependenceService { @@ -161,8 +163,19 @@ export default class DependenceService { const docs = await DependenceModel.findAll({ where: { id: ids } }); for (const doc of docs) { taskLimit.removeQueuedDependency(doc); - const depInstallCommand = getInstallCommand(doc.type, doc.name); - const depUnInstallCommand = getUninstallCommand(doc.type, doc.name); + let depInstallCommand = getInstallCommand(doc.type, doc.name); + let depUnInstallCommand = getUninstallCommand(doc.type, doc.name); + const isLinuxDependence = doc.type === DependenceTypes.linux; + + if (isLinuxDependence) { + const osType = await detectOS(); + if (!osType) { + continue; + } + const linuxCommand = LINUX_DEPENDENCE_COMMAND[osType]; + depInstallCommand = linuxCommand.install; + depUnInstallCommand = linuxCommand.uninstall; + } const installCmd = `${depInstallCommand} ${doc.name.trim()}`; const unInstallCmd = `${depUnInstallCommand} ${doc.name.trim()}`; const pids = await Promise.all([ @@ -221,7 +234,17 @@ export default class DependenceService { if (taskLimit.firstDependencyId !== dependency.id) { return resolve(null); } - + const isNodeDependence = dependency.type === DependenceTypes.nodejs; + const isLinuxDependence = dependency.type === DependenceTypes.linux; + const isPythonDependence = dependency.type === DependenceTypes.python3; + const osType = await detectOS(); + let linuxCommand = {} as typeof LINUX_DEPENDENCE_COMMAND.Alpine; + if (isLinuxDependence) { + if (!osType) { + return resolve(null); + } + linuxCommand = LINUX_DEPENDENCE_COMMAND[osType]; + } taskLimit.removeQueuedDependency(dependency); const depIds = [dependency.id!]; @@ -234,9 +257,14 @@ export default class DependenceService { ? 'installDependence' : 'uninstallDependence'; let depName = dependency.name.trim(); - const command = isInstall + let depRunCommand = isInstall ? getInstallCommand(dependency.type, depName) : getUninstallCommand(dependency.type, depName); + if (isLinuxDependence) { + depRunCommand = isInstall + ? linuxCommand.install + : linuxCommand.uninstall; + } const actionText = isInstall ? '安装' : '删除'; const startTime = dayjs(); @@ -252,8 +280,12 @@ export default class DependenceService { // 判断是否已经安装过依赖 if (isInstall && !force) { - const getCommand = getGetCommand(dependency.type, depName); + let getCommand = getGetCommand(dependency.type, depName); const depVersionStr = versionDependenceCommandTypes[dependency.type]; + if (isLinuxDependence) { + getCommand = `${linuxCommand.info} ${depName}`; + } + let depVersion = ''; if (depName.includes(depVersionStr)) { const symbolRegx = new RegExp( @@ -265,10 +297,6 @@ export default class DependenceService { depVersion = _depVersion; } } - const isNodeDependence = dependency.type === DependenceTypes.nodejs; - const isLinuxDependence = dependency.type === DependenceTypes.linux; - const isPythonDependence = - dependency.type === DependenceTypes.python3; const depInfo = (await promiseExecSuccess(getCommand)) .replace(/\s{2,}/, ' ') .replace(/\s+$/, ''); @@ -277,7 +305,7 @@ export default class DependenceService { depInfo && ((isNodeDependence && depInfo.split(' ')?.[0] === depName) || (isLinuxDependence && - depInfo.toLocaleLowerCase().includes('apt-manual-installed')) || + linuxCommand.check(depInfo.toLocaleLowerCase())) || isPythonDependence) && (!depVersion || depInfo.includes(depVersion)) ) { diff --git a/back/services/system.ts b/back/services/system.ts index e4057ceb..8bc6cf15 100644 --- a/back/services/system.ts +++ b/back/services/system.ts @@ -37,6 +37,7 @@ import ScheduleService, { TaskCallbacks } from './schedule'; import SockService from './sock'; import os from 'os'; import dayjs from 'dayjs'; +import { updateLinuxMirrorFile } from '../config/util'; @Service() export default class SystemService { @@ -214,33 +215,11 @@ export default class SystemService { onEnd?: () => void, ) { const oDoc = await this.getSystemConfig(); - await this.updateAuthDb({ - ...oDoc, - info: { ...oDoc.info, ...info }, - }); - let defaultDomain = 'http://deb.debian.org'; - let targetDomain = 'http://deb.debian.org'; if (os.platform() !== 'linux') { return; } - const content = await fs.promises.readFile('/etc/apt/sources.list', { - encoding: 'utf-8', - }); - const domainMatch = content.match(/(http.*)\/debian /); - if (domainMatch) { - defaultDomain = domainMatch[1]; - } - if (info.linuxMirror) { - targetDomain = info.linuxMirror; - } - const command = `sed -i 's/${defaultDomain.replace( - /\//g, - '\\/', - )}/${targetDomain.replace( - /\//g, - '\\/', - )}/g' /etc/apt/sources.list && apt update`; - + const command = await updateLinuxMirrorFile(info.linuxMirror || ''); + let hasError = false; this.scheduleService.runTask( command, { @@ -254,8 +233,15 @@ export default class SystemService { message: 'update linux mirror end', }); onEnd?.(); + if (!hasError) { + await this.updateAuthDb({ + ...oDoc, + info: { ...oDoc.info, ...info }, + }); + } }, onError: async (message: string) => { + hasError = true; this.sockService.sendMessage({ type: 'updateLinuxMirror', message }); }, onLog: async (message: string) => { diff --git a/shell/bot.sh b/shell/bot.sh index b32a5c49..7656901e 100755 --- a/shell/bot.sh +++ b/shell/bot.sh @@ -9,7 +9,15 @@ else fi echo -e "\n1、安装bot依赖...\n" -apt install -y gcc python3-dev musl-dev +os_name=$(source /etc/os-release && echo "$ID") +if [[ $os_name == 'alpine' ]]; then + apk --no-cache add -f zlib-dev gcc jpeg-dev python3-dev musl-dev freetype-dev +elif [[ $os_name == 'debian' ]] || [[ $os_name == 'ubuntu' ]]; then + apt install -y gcc python3-dev musl-dev +else + echo -e "暂不支持此系统 $os_name" + exit 1 +fi echo -e "\nbot依赖安装成功...\n" echo -e "2、下载bot所需文件...\n" diff --git a/shell/start.sh b/shell/start.sh index d8396366..57ad3689 100644 --- a/shell/start.sh +++ b/shell/start.sh @@ -48,9 +48,6 @@ if [[ $os_name == 'alpine' ]]; then elif [[ $os_name == 'debian' ]] || [[ $os_name == 'ubuntu' ]]; then apt update apt install -y git curl wget tzdata perl openssl jq nginx procps netcat-openbsd openssh-client -elif [[ $os_name == 'centos' ]]; then - yum update - yum install -y epel-release git curl wget tzdata perl openssl jq nginx procps nc openssh-client else echo -e "暂不支持此系统部署 $os_name" exit 1