- 提取 Nginx 检测为独立函数 isNginxInstalled - 为 select 函数添加 defaultIndex 参数支持默认选项 - 数据库配置保留用户上次输入值作为默认值 - 运行环境和部署方式选择支持记忆上次配置
427 lines
15 KiB
JavaScript
427 lines
15 KiB
JavaScript
const fs = require('fs');
|
||
const { execSync } = require('child_process');
|
||
const { MIN_NODE_VERSION } = require('./constants');
|
||
const { colors, log } = require('./logger');
|
||
const { runCommand, commandExists } = require('./utils');
|
||
const { ask } = require('./ui');
|
||
|
||
function checkNodeVersion() {
|
||
const version = process.version;
|
||
const major = parseInt(version.slice(1).split('.')[0]);
|
||
return major >= MIN_NODE_VERSION;
|
||
}
|
||
|
||
function isNginxInstalled() {
|
||
try {
|
||
execSync('nginx -v', { stdio: 'pipe', shell: true });
|
||
return true;
|
||
} catch {
|
||
if (process.platform === 'win32') {
|
||
const commonPaths = [
|
||
'C:\\nginx\\nginx.exe',
|
||
'C:\\Program Files\\nginx\\nginx.exe',
|
||
'C:\\Program Files (x86)\\nginx\\nginx.exe'
|
||
];
|
||
for (const p of commonPaths) {
|
||
if (fs.existsSync(p)) return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
}
|
||
|
||
function checkNodeAndNpm() {
|
||
let nodeInstalled = false;
|
||
let npmInstalled = false;
|
||
let version = '';
|
||
|
||
try {
|
||
version = process.version;
|
||
nodeInstalled = true;
|
||
} catch {
|
||
nodeInstalled = false;
|
||
}
|
||
|
||
npmInstalled = commandExists('npm');
|
||
|
||
return { nodeInstalled, npmInstalled, version };
|
||
}
|
||
|
||
function detectLinuxDistro() {
|
||
try {
|
||
if (fs.existsSync('/etc/os-release')) {
|
||
const content = fs.readFileSync('/etc/os-release', 'utf8');
|
||
const idMatch = content.match(/^ID=(.*)$/m);
|
||
if (idMatch) {
|
||
const id = idMatch[1].replace(/"/g, '').toLowerCase();
|
||
if (['ubuntu', 'debian'].includes(id)) return 'ubuntu';
|
||
if (['centos', 'rhel', 'rocky', 'almalinux', 'fedora'].includes(id)) return 'centos';
|
||
return id;
|
||
}
|
||
}
|
||
if (fs.existsSync('/etc/redhat-release')) return 'centos';
|
||
if (fs.existsSync('/etc/debian_version')) return 'ubuntu';
|
||
} catch {}
|
||
return 'unknown';
|
||
}
|
||
|
||
function isRootUser() {
|
||
return process.getuid && process.getuid() === 0;
|
||
}
|
||
|
||
function showNodeInstallGuide() {
|
||
const platform = process.platform;
|
||
const isWindows = platform === 'win32';
|
||
const isMac = platform === 'darwin';
|
||
const isLinux = platform === 'linux';
|
||
|
||
console.log('\n' + colors.bright + 'Node.js 安装指引:' + colors.reset);
|
||
|
||
if (isLinux) {
|
||
const distro = detectLinuxDistro();
|
||
|
||
console.log('\n' + colors.green + '【Linux 推荐安装方式】' + colors.reset);
|
||
|
||
switch (distro) {
|
||
case 'ubuntu':
|
||
case 'debian':
|
||
console.log('\n' + colors.yellow + `Ubuntu/Debian (${distro}):` + colors.reset);
|
||
console.log(` ${colors.cyan}# 使用 NodeSource 源安装 Node.js 20.x` + colors.reset);
|
||
console.log(` ${colors.cyan}curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -${colors.reset}`);
|
||
console.log(` ${colors.cyan}sudo apt-get install -y nodejs${colors.reset}`);
|
||
break;
|
||
|
||
case 'centos':
|
||
case 'rhel':
|
||
case 'fedora':
|
||
console.log('\n' + colors.yellow + `CentOS/RHEL/Fedora (${distro}):` + colors.reset);
|
||
console.log(` ${colors.cyan}# 使用 NodeSource 源安装 Node.js 20.x` + colors.reset);
|
||
console.log(` ${colors.cyan}curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -${colors.reset}`);
|
||
console.log(` ${colors.cyan}sudo yum install -y nodejs${colors.reset}`);
|
||
break;
|
||
|
||
case 'arch':
|
||
console.log('\n' + colors.yellow + 'Arch Linux:' + colors.reset);
|
||
console.log(` ${colors.cyan}sudo pacman -S nodejs npm${colors.reset}`);
|
||
break;
|
||
|
||
default:
|
||
console.log('\n' + colors.yellow + '通用 Linux 安装方式:' + colors.reset);
|
||
console.log(` ${colors.cyan}# Ubuntu/Debian${colors.reset}`);
|
||
console.log(` curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -`);
|
||
console.log(` sudo apt-get install -y nodejs`);
|
||
console.log(`\n ${colors.cyan}# CentOS/RHEL/Fedora${colors.reset}`);
|
||
console.log(` curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -`);
|
||
console.log(` sudo yum install -y nodejs`);
|
||
}
|
||
|
||
console.log('\n' + colors.yellow + '【开发者推荐】使用 nvm(Node Version Manager):' + colors.reset);
|
||
console.log(` ${colors.cyan}# 1. 安装 nvm${colors.reset}`);
|
||
console.log(` curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash`);
|
||
console.log(` ${colors.cyan}# 2. 加载 nvm(或重新打开终端)${colors.reset}`);
|
||
console.log(` export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"`);
|
||
console.log(` ${colors.cyan}# 3. 安装并使用 Node.js 20${colors.reset}`);
|
||
console.log(` nvm install 20`);
|
||
console.log(` nvm use 20`);
|
||
console.log(` ${colors.cyan}# 4. 验证安装${colors.reset}`);
|
||
console.log(` node -v && npm -v`);
|
||
|
||
console.log('\n' + colors.yellow + '【Docker 方式】' + colors.reset);
|
||
console.log(` ${colors.cyan}# 使用官方 Node 镜像${colors.reset}`);
|
||
console.log(` docker run -it --rm node:20-alpine node -v`);
|
||
|
||
} else if (isWindows) {
|
||
console.log('\n' + colors.green + '【Windows 安装方式】' + colors.reset);
|
||
|
||
console.log('\n' + colors.yellow + '方式一:使用官方安装包(推荐新手)' + colors.reset);
|
||
console.log(` 1. 访问: ${colors.cyan}https://nodejs.org/${colors.reset}`);
|
||
console.log(` 2. 下载 LTS 版本(推荐 ${colors.cyan}v20.x${colors.reset})`);
|
||
console.log(` 3. 运行安装包,按向导完成安装`);
|
||
console.log(` 4. 安装完成后,${colors.yellow}重新打开终端${colors.reset}并再次运行此脚本`);
|
||
|
||
console.log('\n' + colors.yellow + '方式二:使用 nvm-windows(推荐开发者)' + colors.reset);
|
||
console.log(` 1. 下载安装 nvm-windows: ${colors.cyan}https://github.com/coreybutler/nvm-windows/releases${colors.reset}`);
|
||
console.log(` 2. 打开新的 PowerShell 或 CMD 窗口`);
|
||
console.log(` 3. 执行: ${colors.cyan}nvm install 20${colors.reset}`);
|
||
console.log(` 4. 执行: ${colors.cyan}nvm use 20${colors.reset}`);
|
||
console.log(` 5. 验证: ${colors.cyan}node -v${colors.reset}`);
|
||
|
||
console.log('\n' + colors.yellow + '方式三:使用 Winget(Windows 10/11 自带)' + colors.reset);
|
||
console.log(` ${colors.cyan}winget install OpenJS.NodeJS.LTS${colors.reset}`);
|
||
|
||
} else if (isMac) {
|
||
console.log('\n' + colors.green + '【macOS 安装方式】' + colors.reset);
|
||
|
||
console.log('\n' + colors.yellow + '方式一:使用 Homebrew(推荐)' + colors.reset);
|
||
console.log(` ${colors.cyan}brew install node@${colors.reset}`);
|
||
|
||
console.log('\n' + colors.yellow + '方式二:使用官方安装包' + colors.reset);
|
||
console.log(` 1. 访问: ${colors.cyan}https://nodejs.org/${colors.reset}`);
|
||
console.log(` 2. 下载 macOS 安装包(.pkg)`);
|
||
console.log(` 3. 双击安装包按向导完成安装`);
|
||
|
||
console.log('\n' + colors.yellow + '方式三:使用 nvm(推荐开发者)' + colors.reset);
|
||
console.log(` ${colors.cyan}# 1. 安装 nvm${colors.reset}`);
|
||
console.log(` curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash`);
|
||
console.log(` ${colors.cyan}# 2. 重新加载 shell 配置${colors.reset}`);
|
||
console.log(` source ~/.bashrc # 或 source ~/.zshrc`);
|
||
console.log(` ${colors.cyan}# 3. 安装 Node.js 20${colors.reset}`);
|
||
console.log(` nvm install 20`);
|
||
console.log(` nvm use 20`);
|
||
}
|
||
|
||
console.log('\n' + colors.green + '【验证安装】' + colors.reset);
|
||
console.log(` 安装完成后,运行以下命令验证:`);
|
||
console.log(` ${colors.cyan}node -v${colors.reset} # 应显示 v20.x.x 或更高版本`);
|
||
console.log(` ${colors.cyan}npm -v${colors.reset} # 应显示 10.x.x 或更高版本`);
|
||
|
||
console.log('\n' + colors.gray + '安装完成后,请重新运行此部署脚本。' + colors.reset);
|
||
}
|
||
|
||
async function autoInstallNodeLinux() {
|
||
const distro = detectLinuxDistro();
|
||
|
||
log.step('自动安装 Node.js');
|
||
log.info(`检测到 Linux 发行版: ${distro}`);
|
||
|
||
try {
|
||
switch (distro) {
|
||
case 'ubuntu':
|
||
case 'debian':
|
||
log.info('使用 NodeSource 源安装 Node.js 20.x...');
|
||
log.info('执行: curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -');
|
||
const debianSetup = runCommand('curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -');
|
||
if (!debianSetup.success) {
|
||
log.error('NodeSource 源配置失败');
|
||
return false;
|
||
}
|
||
|
||
log.info('执行: sudo apt-get install -y nodejs');
|
||
const debianInstall = runCommand('sudo apt-get install -y nodejs');
|
||
if (!debianInstall.success) {
|
||
log.error('Node.js 安装失败');
|
||
return false;
|
||
}
|
||
break;
|
||
|
||
case 'centos':
|
||
case 'rhel':
|
||
case 'fedora':
|
||
log.info('使用 NodeSource 源安装 Node.js 20.x...');
|
||
log.info('执行: curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -');
|
||
const rpmSetup = runCommand('curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -');
|
||
if (!rpmSetup.success) {
|
||
log.error('NodeSource 源配置失败');
|
||
return false;
|
||
}
|
||
|
||
log.info('执行: sudo yum install -y nodejs');
|
||
const rpmInstall = runCommand('sudo yum install -y nodejs');
|
||
if (!rpmInstall.success) {
|
||
log.error('Node.js 安装失败');
|
||
return false;
|
||
}
|
||
break;
|
||
|
||
case 'arch':
|
||
log.info('执行: sudo pacman -S nodejs npm');
|
||
const archInstall = runCommand('sudo pacman -S --noconfirm nodejs npm');
|
||
if (!archInstall.success) {
|
||
log.error('Node.js 安装失败');
|
||
return false;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
log.info('未知发行版,尝试使用 nvm 安装...');
|
||
return await installNodeViaNvm();
|
||
}
|
||
|
||
const checkResult = runCommand('node -v', { silent: true });
|
||
if (checkResult.success) {
|
||
log.success(`Node.js 安装成功: ${checkResult.output.trim()}`);
|
||
const npmCheck = runCommand('npm -v', { silent: true });
|
||
if (npmCheck.success) {
|
||
log.success(`npm 安装成功: ${npmCheck.output.trim()}`);
|
||
}
|
||
return true;
|
||
} else {
|
||
log.error('Node.js 安装后验证失败');
|
||
return false;
|
||
}
|
||
|
||
} catch (error) {
|
||
log.error(`自动安装失败: ${error.message}`);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
async function installNodeViaNvm() {
|
||
log.info('使用 nvm 安装 Node.js...');
|
||
|
||
try {
|
||
const nvmCheck = runCommand('source ~/.nvm/nvm.sh && nvm --version', { silent: true });
|
||
|
||
if (!nvmCheck.success) {
|
||
log.info('安装 nvm...');
|
||
const nvmInstall = runCommand('curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash');
|
||
if (!nvmInstall.success) {
|
||
log.error('nvm 安装失败');
|
||
return false;
|
||
}
|
||
}
|
||
|
||
log.info('使用 nvm 安装 Node.js 20...');
|
||
const nodeInstall = runCommand('export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && nvm install 20');
|
||
if (!nodeInstall.success) {
|
||
log.error('Node.js 安装失败');
|
||
return false;
|
||
}
|
||
|
||
log.info('设置 Node.js 20 为默认版本...');
|
||
runCommand('export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && nvm alias default 20', { silent: true });
|
||
|
||
log.success('Node.js 安装成功');
|
||
return true;
|
||
|
||
} catch (error) {
|
||
log.error(`nvm 安装失败: ${error.message}`);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
async function handleNodeNotInstalledLinux() {
|
||
log.error('未检测到 Node.js');
|
||
|
||
console.log('\n' + colors.yellow + '您可以选择:' + colors.reset);
|
||
console.log(` ${colors.cyan}1.${colors.reset} 自动安装 Node.js 20.x(推荐,需要 sudo 权限)`);
|
||
console.log(` ${colors.cyan}2.${colors.reset} 显示手动安装指引,退出脚本`);
|
||
|
||
const choice = await ask('请选择', '1');
|
||
|
||
if (choice === '1') {
|
||
const installed = await autoInstallNodeLinux();
|
||
if (installed) {
|
||
log.success('Node.js 和 npm 安装完成!');
|
||
log.info('请重新运行此脚本以继续部署');
|
||
console.log(colors.cyan + ' node install.js' + colors.reset);
|
||
|
||
const rerun = await ask('\n是否立即重新运行部署脚本? (Y/n)', 'Y');
|
||
if (rerun.toLowerCase() === 'y') {
|
||
return true;
|
||
}
|
||
} else {
|
||
log.error('自动安装失败,请尝试手动安装');
|
||
showNodeInstallGuide();
|
||
}
|
||
process.exit(1);
|
||
} else {
|
||
showNodeInstallGuide();
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
async function checkEnvironment() {
|
||
log.step('环境检测');
|
||
|
||
const { nodeInstalled, npmInstalled, version } = checkNodeAndNpm();
|
||
const isLinux = process.platform === 'linux';
|
||
|
||
if (!nodeInstalled) {
|
||
if (isLinux) {
|
||
const shouldContinue = await handleNodeNotInstalledLinux();
|
||
if (shouldContinue) {
|
||
return await checkEnvironment();
|
||
}
|
||
} else {
|
||
log.error('未检测到 Node.js,请先安装');
|
||
showNodeInstallGuide();
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
if (!npmInstalled) {
|
||
if (isLinux && nodeInstalled) {
|
||
log.warning('未检测到 npm,尝试修复...');
|
||
const fixed = await autoInstallNodeLinux();
|
||
if (!fixed) {
|
||
log.error('npm 修复失败,请手动安装');
|
||
showNodeInstallGuide();
|
||
process.exit(1);
|
||
}
|
||
} else {
|
||
log.error('未检测到 npm,请重新安装 Node.js(npm 会随 Node.js 一起安装)');
|
||
showNodeInstallGuide();
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
if (checkNodeVersion()) {
|
||
log.success(`Node.js ${version}`);
|
||
} else {
|
||
log.error(`Node.js 版本过低 (${version}),需要 >= 14.0.0`);
|
||
|
||
if (isLinux) {
|
||
console.log('\n' + colors.yellow + '您可以选择:' + colors.reset);
|
||
console.log(` ${colors.cyan}1.${colors.reset} 自动升级到 Node.js 20.x`);
|
||
console.log(` ${colors.cyan}2.${colors.reset} 显示手动升级指引,退出脚本`);
|
||
|
||
const upgradeChoice = await ask('请选择', '1');
|
||
if (upgradeChoice === '1') {
|
||
const upgraded = await autoInstallNodeLinux();
|
||
if (upgraded) {
|
||
log.success('Node.js 升级完成!请重新运行此脚本');
|
||
} else {
|
||
log.error('自动升级失败');
|
||
showNodeInstallGuide();
|
||
}
|
||
} else {
|
||
console.log('\n' + colors.yellow + '建议升级到 Node.js 20 LTS 版本:' + colors.reset);
|
||
showNodeInstallGuide();
|
||
}
|
||
} else {
|
||
console.log('\n' + colors.yellow + '建议升级到 Node.js 20 LTS 版本:' + colors.reset);
|
||
showNodeInstallGuide();
|
||
}
|
||
process.exit(1);
|
||
}
|
||
|
||
const npmResult = runCommand('npm --version', { silent: true });
|
||
if (npmResult.success) {
|
||
log.success(`npm ${npmResult.output.trim()}`);
|
||
}
|
||
|
||
if (commandExists('pm2')) {
|
||
log.success('PM2 已安装');
|
||
} else {
|
||
log.warning('未检测到 PM2,将自动安装');
|
||
const result = runCommand('npm install -g pm2');
|
||
if (result.success) {
|
||
log.success('PM2 安装完成');
|
||
} else {
|
||
log.error('PM2 安装失败,请手动执行: npm install -g pm2');
|
||
}
|
||
}
|
||
|
||
if (isNginxInstalled()) {
|
||
log.success('Nginx 已安装');
|
||
} else {
|
||
log.warning('未检测到 Nginx(可选,仅在使用Nginx部署时需要)');
|
||
}
|
||
|
||
log.divider();
|
||
}
|
||
|
||
module.exports = {
|
||
checkNodeVersion,
|
||
isNginxInstalled,
|
||
checkNodeAndNpm,
|
||
detectLinuxDistro,
|
||
isRootUser,
|
||
showNodeInstallGuide,
|
||
autoInstallNodeLinux,
|
||
installNodeViaNvm,
|
||
handleNodeNotInstalledLinux,
|
||
checkEnvironment,
|
||
};
|