guanghulab/image-studio/deploy/remote-deploy.mjs

141 lines
5.3 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.

/**
* ═══════════════════════════════════════════════════
* 铸渊远程部署 · 通过看门人API部署图片工作室
* ═══════════════════════════════════════════════════
*
* 冰朔我不需要SSH密钥。
* 铸渊看门人就是我的手。
*/
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' },
}
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(`${server.name}: ${cmd.slice(0, 80)}...`)
const result = await callGatekeeper(server, 'POST', '/exec', { cmd })
if (result.status !== 200) {
console.log(` ❌ [${result.status}] ${JSON.stringify(result.data)}`)
} else {
const out = (result.data.stdout || '').trim()
const err = (result.data.stderr || '').trim()
if (out) console.log(` stdout: ${out.slice(0, 300)}`)
if (err) console.log(` stderr: ${err.slice(0, 300)}`)
}
return result
}
async function writeFile(server, filepath, content) {
console.log(`${server.name}: 写入 ${filepath}`)
return callGatekeeper(server, 'POST', '/file/write', { path: filepath, content })
}
async function readFile(server, filepath) {
const result = await callGatekeeper(server, 'POST', '/file/read', { path: filepath })
if (result.status === 200) return result.data.content || ''
return null
}
async function checkStatus(server) {
console.log(`\n📡 测试连接 ${server.name} (${server.ip}:${server.port})...`)
const result = await callGatekeeper(server, 'POST', '/status')
if (result.status === 200) {
console.log(` ✅ 连接成功!`)
console.log(` 状态: ${JSON.stringify(result.data, null, 2).slice(0, 400)}`)
return true
} else {
console.log(` ❌ 连接失败: ${result.status} ${JSON.stringify(result.data)}`)
return false
}
}
async function deployImageStudio() {
const sg = SERVERS.sg
const gz = SERVERS.gz
/* ═══ 第一步:测试连接 ═══ */
console.log('═'.repeat(50))
console.log('🔌 铸渊远程部署 · 检查服务器连接')
console.log('═'.repeat(50))
const sgOk = await checkStatus(sg)
if (!sgOk) {
console.log('\n⚠ 新加坡服务器连接失败,尝试广州服务器直接反向代理方案...')
}
/* ═══ 第二步在新加坡部署image-studio ═══ */
if (sgOk) {
console.log('\n📦 第二步:在新加坡服务器部署图片工作室')
console.log('═'.repeat(50))
// 检查Node.js
await exec(sg, 'node --version')
await exec(sg, 'which google-chrome || which chromium-browser || echo "no chrome"')
// 创建目录
await exec(sg, 'mkdir -p /data/image-studio/output /data/image-studio/templates /data/image-studio/deploy')
// 安装系统依赖
console.log('\n 📥 安装系统依赖...')
await exec(sg, 'apt-get update -qq && apt-get install -y -qq google-chrome-stable libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libdbus-1-3 libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2 2>&1 | tail -5')
// 安装Node依赖
await exec(sg, 'cd /data/image-studio && npm init -y && npm install puppeteer express 2>&1 | tail -5')
// 检查PM2
await exec(sg, 'which pm2 || npm install -g pm2 2>&1 | tail -3')
console.log('\n ✅ 新加坡服务器基础环境就绪')
console.log(' 接下来需要通过 /file/write 上传源代码文件')
}
/* ═══ 第三步配置广州Nginx代理 ═══ */
console.log('\n🌐 第三步检查广州服务器Nginx代理')
console.log('═'.repeat(50))
const gzOk = await checkStatus(gz)
if (gzOk) {
// 读取现有Nginx配置
const nginxConf = await readFile(gz, '/etc/nginx/sites-enabled/default')
if (nginxConf) {
console.log(' 已读取Nginx配置长度:', nginxConf.length, '字符')
console.log(' 前200字符:', nginxConf.slice(0, 200))
} else {
console.log(' 无法直接读取Nginx配置权限问题需要调整方案')
// 尝试其他路径
for (const p of ['/etc/nginx/conf.d/default.conf', '/etc/nginx/nginx.conf', '/data/nginx/conf.d/default.conf']) {
const c = await readFile(gz, p)
if (c) { console.log(` ✅ 找到配置: ${p}`); break }
}
}
}
console.log('\n' + '═'.repeat(50))
console.log('📋 部署检查完成')
console.log('═'.repeat(50))
}
deployImageStudio().catch(err => {
console.error('❌ 部署失败:', err.message)
process.exit(1)
})