141 lines
5.3 KiB
JavaScript
141 lines
5.3 KiB
JavaScript
/**
|
||
* ═══════════════════════════════════════════════════
|
||
* 铸渊远程部署 · 通过看门人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)
|
||
})
|