/** * ═══════════════════════════════════════════════════ * 铸渊远程部署 · 第二阶段:安装Chrome + 上传源码 + 启动服务 * ═══════════════════════════════════════════════════ */ const SERVERS = { sg: { name: '新加坡·铸渊大脑', ip: '43.156.237.110', port: 3911, key: 'zy_gtw_74d30f54fa8b5581514569ee7874def57861a31ccb2be8c9' }, gz: { name: '广州·代码仓库', ip: '43.139.217.141', port: 3910, key: 'zy_gtw_4a155446b7acc09fe46a2a29972c0ca5d9954da19c35dc23' }, } const LOCAL_BASE = '/workspace/guanghulab/image-studio' import { readFileSync } from 'fs' async function callGatekeeper(server, method, path, body = null) { const url = `http://${server.ip}:${server.port}${path}` const opts = { method, headers: { 'Authorization': `Bearer ${server.key}`, 'Content-Type': 'application/json', }, } if (body) opts.body = JSON.stringify(body) const res = await fetch(url, opts) const text = await res.text() let data try { data = JSON.parse(text) } catch { data = { raw: text } } return { status: res.status, data } } async function exec(server, cmd) { console.log(` → ${cmd.slice(0, 100)}`) const result = await callGatekeeper(server, 'POST', '/exec', { cmd }) if (result.status !== 200) { console.log(` ❌ [${result.status}] ${JSON.stringify(result.data).slice(0, 200)}`) } else { const out = (result.data.stdout || '').trim() const err = (result.data.stderr || '').trim() if (out) console.log(` ${out.split('\n').slice(0, 8).join('\n ')}`) if (err) console.log(` ⚠️ ${err.split('\n').slice(0, 4).join('\n ')}`) } return result } async function writeFile(server, remotePath, localPath) { const content = readFileSync(localPath, 'utf-8') const result = await callGatekeeper(server, 'POST', '/file/write', { path: remotePath, content }) if (result.status === 200) { console.log(` ✅ ${remotePath} (${content.length} 字节)`) } else { console.log(` ❌ ${remotePath}: ${JSON.stringify(result.data)}`) } return result } async function main() { const sg = SERVERS.sg const gz = SERVERS.gz // ======= 1. 在新加坡服务器安装Chrome ======= console.log('═'.repeat(50)) console.log('📦 1. 在新加坡服务器安装Chrome') console.log('═'.repeat(50)) // 检查系统版本,安装Chromium await exec(sg, 'cat /etc/os-release | head -3') // 安装Chromium(Ubuntu仓库中有chromium-browser) console.log('\n 安装Chromium...') await exec(sg, 'apt-get install -y -qq chromium-browser 2>&1 | tail -5') // 验证安装 await exec(sg, 'which chromium-browser && chromium-browser --version || echo "not found"') // 如果没有chromium-browser,试试用npx puppeteer安装 await exec(sg, 'which chromium-browser || (cd /data/image-studio && npx puppeteer browsers install chrome 2>&1 | tail -5)') // ======= 2. 上传源代码文件 ======= console.log('\n' + '═'.repeat(50)) console.log('📤 2. 上传源代码到新加坡服务器') console.log('═'.repeat(50)) const files = [ ['config.js', '/data/image-studio/config.js'], ['renderer.js', '/data/image-studio/renderer.js'], ['generate.js', '/data/image-studio/generate.js'], ['server.js', '/data/image-studio/server.js'], ['templates/xiaohongshu.js', '/data/image-studio/templates/xiaohongshu.js'], ['templates/jike.js', '/data/image-studio/templates/jike.js'], ['templates/poster.js', '/data/image-studio/templates/poster.js'], ['templates/dynamic.js', '/data/image-studio/templates/dynamic.js'], ['deploy/setup.sh', '/data/image-studio/deploy/setup.sh'], ] for (const [local, remote] of files) { await writeFile(sg, remote, `${LOCAL_BASE}/${local}`) } // 更新package.json添加type:module await exec(sg, `cd /data/image-studio && npm pkg set type="module"`) // ======= 3. 启动服务 ======= console.log('\n' + '═'.repeat(50)) console.log('🚀 3. 启动图片工作室服务') console.log('═'.repeat(50)) // 先用node直接启动测试是否能正常运行 await exec(sg, 'cd /data/image-studio && timeout 5 node server.js 2>&1 || true') // 用PM2启动(后台运行) await exec(sg, 'pm2 delete image-studio 2>/dev/null; cd /data/image-studio && pm2 start server.js --name image-studio -o /data/image-studio/output.log -e /data/image-studio/error.log') await exec(sg, 'sleep 2 && pm2 list') // 验证服务是否运行 await exec(sg, 'curl -s http://localhost:3912/ | head -5 || echo "服务未启动"') await exec(sg, 'ss -tlnp | grep 3912') // ======= 4. 保存PM2配置 ======= await exec(sg, 'pm2 save') // ======= 5. 检查广州Nginx配置 ======= console.log('\n' + '═'.repeat(50)) console.log('🌐 4. 检查广州Nginx配置') console.log('═'.repeat(50)) // 读取Nginx配置 const nginxResult = await callGatekeeper(gz, 'POST', '/file/read', { path: '/etc/nginx/nginx.conf' }) if (nginxResult.status === 200) { console.log(nginxResult.data.content.slice(0, 1000)) } else { // 试试conf.d const confResult = await callGatekeeper(gz, 'POST', '/exec', { cmd: 'ls /etc/nginx/sites-enabled/ /etc/nginx/conf.d/ 2>&1' }) if (confResult.status === 200) { console.log('Nginx配置目录:', confResult.data.stdout || confResult.data.stderr) } } console.log('\n' + '═'.repeat(50)) console.log('✅ 部署流程完成!') console.log('═'.repeat(50)) } main().catch(err => { console.error('❌ 失败:', err.message) process.exit(1) })