Files
TrueGrowth/scripts/upload-nginx-config.js

318 lines
10 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Nginx 配置文件上传脚本
*
* ⚠️ 安全警告:
* 1. 确保 .env 文件在 .gitignore 中,不要提交到版本控制
* 2. 强烈建议使用 SSH 密钥认证,而不是密码认证
* 3. 建议配置免密 sudo而不是在 .env 中存储 sudo 密码
* 4. 密码会出现在进程列表中,使用 SSH 密钥更安全
*
* 详细安全指南请参考 scripts/SECURITY.md
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// 加载 .env 配置文件
function loadEnvConfig() {
const envPath = path.join(__dirname, '../.env');
const config = {
DEPLOY_HOST: '',
DEPLOY_USER: '',
DEPLOY_PORT: '22',
DEPLOY_SSH_KEY: '',
DEPLOY_SSH_PASSWORD: '',
DEPLOY_SUDO_PASSWORD: '', // sudo 密码(如果需要)
};
if (fs.existsSync(envPath)) {
const envContent = fs.readFileSync(envPath, 'utf8');
envContent.split('\n').forEach(line => {
line = line.trim();
if (line && !line.startsWith('#')) {
const match = line.match(/^([^=]+)=(.*)$/);
if (match) {
const key = match[1].trim();
let value = match[2].trim();
if ((value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))) {
value = value.slice(1, -1);
}
if (config.hasOwnProperty(key)) {
config[key] = value;
}
}
}
});
}
return config;
}
// 检查 sshpass 是否安装
function checkSshpassInstalled() {
try {
execSync('which sshpass', { stdio: 'ignore' });
return true;
} catch (e) {
return false;
}
}
// 构建 SSH 命令前缀
function buildSSHCommand(config, usePassword) {
let sshCommand = '';
if (usePassword) {
sshCommand = `sshpass -p "${config.DEPLOY_SSH_PASSWORD}" `;
}
sshCommand += 'ssh';
// 添加端口
if (config.DEPLOY_PORT && config.DEPLOY_PORT !== '22') {
sshCommand += ` -p ${config.DEPLOY_PORT}`;
}
// 添加 SSH 密钥(如果没有使用密码)
if (config.DEPLOY_SSH_KEY && !usePassword) {
const sshKeyPath = config.DEPLOY_SSH_KEY.startsWith('/')
? config.DEPLOY_SSH_KEY
: path.join(process.env.HOME || '', config.DEPLOY_SSH_KEY.replace(/^~/, ''));
if (fs.existsSync(sshKeyPath)) {
sshCommand += ` -i "${sshKeyPath}"`;
}
}
// 添加 SSH 选项
sshCommand += ` -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null`;
return sshCommand;
}
// 构建 SCP 命令前缀
function buildSCPCommand(config, usePassword) {
let scpCommand = '';
if (usePassword) {
scpCommand = `sshpass -p "${config.DEPLOY_SSH_PASSWORD}" `;
}
scpCommand += 'scp';
// 添加端口
if (config.DEPLOY_PORT && config.DEPLOY_PORT !== '22') {
scpCommand += ` -P ${config.DEPLOY_PORT}`;
}
// 添加 SSH 密钥(如果没有使用密码)
if (config.DEPLOY_SSH_KEY && !usePassword) {
const sshKeyPath = config.DEPLOY_SSH_KEY.startsWith('/')
? config.DEPLOY_SSH_KEY
: path.join(process.env.HOME || '', config.DEPLOY_SSH_KEY.replace(/^~/, ''));
if (fs.existsSync(sshKeyPath)) {
scpCommand += ` -i "${sshKeyPath}"`;
}
}
// 添加 SSH 选项
scpCommand += ` -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null`;
return scpCommand;
}
// 上传配置文件
function uploadNginxConfig(config) {
const configFile = path.join(__dirname, 'aitu-releases.conf');
const remotePath = '/etc/nginx/sites-enabled/aitu-releases.conf';
const tempPath = `/tmp/aitu-releases-${Date.now()}.conf`;
if (!fs.existsSync(configFile)) {
console.error(`❌ 配置文件不存在: ${configFile}`);
process.exit(1);
}
console.log('📤 上传 Nginx 配置文件...');
console.log(` 本地文件: ${configFile}`);
console.log(` 远程路径: ${remotePath}`);
console.log(` 服务器: ${config.DEPLOY_USER}@${config.DEPLOY_HOST}:${config.DEPLOY_PORT}`);
// 判断是否使用密码
let usePassword = false;
if (config.DEPLOY_SSH_PASSWORD) {
if (!checkSshpassInstalled()) {
console.error('\n❌ 未安装 sshpass无法使用密码认证');
console.error(' 请安装: brew install hudochenkov/sshpass/sshpass (macOS)');
console.error(' 或者配置 SSH 密钥认证(更安全)');
process.exit(1);
}
usePassword = true;
}
// 显示认证方式
if (usePassword) {
console.log(` 使用密码认证`);
} else if (config.DEPLOY_SSH_KEY) {
console.log(` 使用 SSH 密钥认证`);
} else {
console.log(` 使用默认 SSH 认证`);
}
// 步骤 1: 上传到临时目录
console.log(`\n 步骤 1/2: 上传到临时目录 ${tempPath}`);
const scpCommand = buildSCPCommand(config, usePassword);
const tempTarget = `${config.DEPLOY_USER}@${config.DEPLOY_HOST}:${tempPath}`;
const uploadCommand = `${scpCommand} "${configFile}" "${tempTarget}"`;
try {
execSync(uploadCommand, { stdio: 'inherit' });
console.log(' ✅ 文件上传到临时目录成功');
} catch (error) {
console.error('\n❌ 上传到临时目录失败');
if (usePassword) {
console.error(' 可能的原因:');
console.error(' 1. 密码错误');
console.error(' 2. 服务器不允许密码认证');
console.error(' 3. 网络连接问题');
console.error(' 建议: 配置 SSH 密钥认证(更安全)');
} else {
console.error(' 可能的原因:');
console.error(' 1. SSH 密钥未配置或权限不足');
console.error(' 2. 服务器连接失败');
}
process.exit(1);
}
// 步骤 2: 使用 sudo 移动到目标位置
console.log(`\n 步骤 2/2: 移动到目标目录(需要 sudo 权限)`);
const sshCommand = buildSSHCommand(config, usePassword);
// 构建 sudo 命令,如果配置了 sudo 密码则使用 -S 选项
let sudoPrefix = 'sudo';
if (config.DEPLOY_SUDO_PASSWORD) {
// 使用 echo 通过管道传递密码给 sudo -S
// 注意:需要转义特殊字符,使用单引号包裹密码
const escapedPassword = config.DEPLOY_SUDO_PASSWORD.replace(/'/g, "'\\''");
sudoPrefix = `echo '${escapedPassword}' | sudo -S`;
}
// 备份旧文件(如果存在),然后移动新文件
const moveCommand = `${sshCommand} ${config.DEPLOY_USER}@${config.DEPLOY_HOST} "${sudoPrefix} cp ${tempPath} ${remotePath} && ${sudoPrefix} rm -f ${tempPath}"`;
try {
execSync(moveCommand, { stdio: 'inherit' });
console.log(' ✅ 文件已移动到目标目录');
console.log('✅ 配置文件上传成功');
return true;
} catch (error) {
console.error('\n❌ 移动到目标目录失败');
console.error(' 可能的原因:');
console.error(' 1. 用户没有 sudo 权限');
console.error(' 2. sudo 密码错误(如果配置了 DEPLOY_SUDO_PASSWORD');
console.error(' 3. 目标目录不存在或权限不足');
if (!config.DEPLOY_SUDO_PASSWORD) {
console.error(' 提示: 可以在 .env 中配置 DEPLOY_SUDO_PASSWORD 来自动输入 sudo 密码');
}
console.error(`\n 临时文件位置: ${tempPath}`);
console.error(' 可以手动执行:');
console.error(` ssh ${config.DEPLOY_USER}@${config.DEPLOY_HOST}`);
console.error(` sudo cp ${tempPath} ${remotePath}`);
process.exit(1);
}
}
// 测试并重载 Nginx 配置
function testAndReloadNginx(config) {
console.log('\n🔧 测试 Nginx 配置...');
const usePassword = !!config.DEPLOY_SSH_PASSWORD;
const sshCommand = buildSSHCommand(config, usePassword);
// 构建 sudo 命令,如果配置了 sudo 密码则使用 -S 选项
let sudoPrefix = 'sudo';
if (config.DEPLOY_SUDO_PASSWORD) {
const escapedPassword = config.DEPLOY_SUDO_PASSWORD.replace(/'/g, "'\\''");
sudoPrefix = `echo '${escapedPassword}' | sudo -S`;
}
// 测试配置
const testCommand = `${sshCommand} ${config.DEPLOY_USER}@${config.DEPLOY_HOST} "${sudoPrefix} nginx -t"`;
try {
console.log(' 执行: sudo nginx -t');
execSync(testCommand, { stdio: 'inherit' });
console.log('✅ Nginx 配置测试通过');
} catch (error) {
console.error('\n❌ Nginx 配置测试失败');
console.error(' 请检查配置文件语法是否正确');
if (!config.DEPLOY_SUDO_PASSWORD) {
console.error(' 提示: 如果 sudo 需要密码,可以在 .env 中配置 DEPLOY_SUDO_PASSWORD');
}
process.exit(1);
}
// 重载 Nginx
console.log('\n🔄 重载 Nginx 配置...');
const reloadCommand = `${sshCommand} ${config.DEPLOY_USER}@${config.DEPLOY_HOST} "${sudoPrefix} systemctl reload nginx || ${sudoPrefix} service nginx reload || ${sudoPrefix} nginx -s reload"`;
try {
execSync(reloadCommand, { stdio: 'inherit' });
console.log('✅ Nginx 配置已重载');
} catch (error) {
console.error('\n❌ Nginx 重载失败');
console.error(' 请手动执行: sudo systemctl reload nginx');
process.exit(1);
}
}
// 主函数
function main() {
console.log('🚀 上传 Nginx 配置文件\n');
const config = loadEnvConfig();
if (!config.DEPLOY_HOST || !config.DEPLOY_USER) {
console.error('❌ 缺少必要的配置');
console.error(' 请在 .env 文件中配置:');
console.error(' DEPLOY_HOST=your-server.com');
console.error(' DEPLOY_USER=username');
console.error(' DEPLOY_PORT=22 (可选)');
console.error(' DEPLOY_SSH_KEY=~/.ssh/id_rsa (可选,推荐)');
console.error(' DEPLOY_SSH_PASSWORD=password (可选,不推荐)');
process.exit(1);
}
// 上传配置文件
uploadNginxConfig(config);
// 询问是否测试并重载
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('\n是否测试并重载 Nginx 配置? (y/n): ', (answer) => {
rl.close();
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
testAndReloadNginx(config);
console.log('\n✅ 完成!');
} else {
console.log('\n⚠ 请手动测试并重载 Nginx:');
console.log(' sudo nginx -t');
console.log(' sudo systemctl reload nginx');
}
});
}
// 运行主函数
if (require.main === module) {
main();
}
module.exports = { uploadNginxConfig, testAndReloadNginx };