150 lines
5.6 KiB
JavaScript
150 lines
5.6 KiB
JavaScript
/**
|
||
* ═══════════════════════════════════════════════════
|
||
* 铸渊远程部署 · 第二阶段:安装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)
|
||
})
|