铸渊: 图片工作室部署完成 · 远程部署脚本 + Nginx配置
This commit is contained in:
parent
549c51dab0
commit
11c0d78127
50
image-studio/deploy/DEPLOY_REPORT.md
Normal file
50
image-studio/deploy/DEPLOY_REPORT.md
Normal file
@ -0,0 +1,50 @@
|
||||
# 铸渊图片工作室 · 部署回执
|
||||
|
||||
## 部署信息
|
||||
|
||||
| 项目 | 详情 |
|
||||
|------|------|
|
||||
| **部署时间** | 2026-05-23 08:04 UTC+8 |
|
||||
| **部署位置** | 新加坡 BS-SG-001(铸渊大脑)43.156.237.110 |
|
||||
| **服务端口** | 3912(PM2守护) |
|
||||
| **访问入口** | `https://guanghulab.com/images/` |
|
||||
| **架构** | 广州 Nginx → 新加坡 Nginx → image-studio:3912 |
|
||||
| **守护进程** | PM2,image-studio (online, 0 restarts) |
|
||||
|
||||
## 全链路验证
|
||||
|
||||
| 链路节点 | 状态 | HTTP |
|
||||
|----------|:----:|:----:|
|
||||
| SG localhost:3912 | 🟢 | 200 |
|
||||
| SG nginx /images/ | 🟢 | 200 |
|
||||
| SG public /images/ | 🟢 | 200 |
|
||||
| GZ→SG /images/ | 🟢 | 200 |
|
||||
| GZ nginx /images/ | 🟢 | 200 |
|
||||
| GZ nginx→图片下载 | 🟢 | 200 |
|
||||
|
||||
## 图片生成测试
|
||||
|
||||
| 风格 | 文件 | 尺寸 | 自动检测 |
|
||||
|------|------|:----:|:--------:|
|
||||
| 小红书 | `deploy_greeting.png` | 9.3KB | paragraph + guanghu |
|
||||
| 即刻 | `final_jike.png` | 13KB | quote + rose |
|
||||
| 海报 | `final_poster.png` | 66KB | notice + cream |
|
||||
|
||||
## 部署的源代码文件
|
||||
|
||||
- `/data/image-studio/config.js` — 风格配置 + 内容分析
|
||||
- `/data/image-studio/renderer.js` — Puppeteer渲染引擎
|
||||
- `/data/image-studio/generate.js` — 神笔马良 `fromText()` 入口
|
||||
- `/data/image-studio/server.js` — Web服务
|
||||
- `/data/image-studio/templates/xiaohongshu.js` — 小红书卡片
|
||||
- `/data/image-studio/templates/jike.js` — 即刻卡片
|
||||
- `/data/image-studio/templates/poster.js` — 海报模板
|
||||
- `/data/image-studio/templates/dynamic.js` — 万能动态模板
|
||||
|
||||
## Nginx配置变更
|
||||
|
||||
**新加坡** (`/etc/nginx/sites-enabled/default`):
|
||||
添加 `location /images/` → proxy_pass 到 `127.0.0.1:3912`
|
||||
|
||||
**广州** (`/etc/nginx/sites-enabled/guanghulab`):
|
||||
添加 `location /images/` → proxy_pass 到 `http://43.156.237.110/images/`
|
||||
94
image-studio/deploy/final-verify.mjs
Normal file
94
image-studio/deploy/final-verify.mjs
Normal file
@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 最终全链路验证
|
||||
*/
|
||||
|
||||
const SG = { ip: '43.156.237.110', port: 3911, key: 'zy_gtw_74d30f54fa8b5581514569ee7874def57861a31ccb2be8c9' }
|
||||
const GZ = { ip: '43.139.217.141', port: 3910, key: 'zy_gtw_4a155446b7acc09fe46a2a29972c0ca5d9954da19c35dc23' }
|
||||
|
||||
async function callGK(srv, method, path, body = null) {
|
||||
const url = `http://${srv.ip}:${srv.port}${path}`
|
||||
const opts = { method, headers: { 'Authorization': `Bearer ${srv.key}`, 'Content-Type': 'application/json' } }
|
||||
if (body) opts.body = JSON.stringify(body)
|
||||
const res = await fetch(url, opts)
|
||||
const text = await res.text()
|
||||
try { return { status: res.status, data: JSON.parse(text) } }
|
||||
catch { return { status: res.status, data: { raw: text } } }
|
||||
}
|
||||
|
||||
async function exec(srv, cmd) {
|
||||
const r = await callGK(srv, 'POST', '/exec', { cmd })
|
||||
return r.data
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('═'.repeat(50))
|
||||
console.log('✅ 铸渊图片工作室 · 最终验收')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
// 1. 新加坡本地服务检查
|
||||
console.log('\n1️⃣ 新加坡image-studio:')
|
||||
const r1 = await exec(SG, 'curl -s -w "\nHTTP:%{http_code}" http://localhost:3912/ 2>&1 | tail -2')
|
||||
console.log(' localhost:3912:', (r1.stdout || '').trim())
|
||||
|
||||
// 2. 新加坡Nginx代理
|
||||
const r2 = await exec(SG, 'curl -s -w "\nHTTP:%{http_code}" http://localhost/images/ 2>&1 | tail -2')
|
||||
console.log(' SG nginx /images/:', (r2.stdout || '').trim())
|
||||
|
||||
// 3. 新加坡外网
|
||||
const r3 = await exec(SG, `curl -s -w "\nHTTP:%{http_code}" http://${SG.ip}/images/ 2>&1 | tail -2`)
|
||||
console.log(' SG public /images/:', (r3.stdout || '').trim())
|
||||
|
||||
// 4. 广州→新加坡
|
||||
const r4 = await exec(GZ, `curl -s --connect-timeout 5 -w "\nHTTP:%{http_code}" http://${SG.ip}/images/ 2>&1 | tail -2`)
|
||||
console.log(' GZ→SG /images/:', (r4.stdout || '').trim())
|
||||
|
||||
// 5. 广州Nginx代理
|
||||
const r5 = await exec(GZ, `curl -s -k --connect-timeout 5 -H "Host: guanghulab.com" -w "\nHTTP:%{http_code}" https://localhost/images/ 2>&1 | tail -2`)
|
||||
console.log(' GZ nginx /images/:', (r5.stdout || '').trim())
|
||||
|
||||
// 6. 检查生成的测试图片
|
||||
console.log('\n2️⃣ 验证生成的图片:')
|
||||
const r6 = await exec(SG, 'ls -la /data/image-studio/output/deploy_greeting.png 2>&1')
|
||||
console.log(' 图片文件:', (r6.stdout || '').trim())
|
||||
|
||||
// 7. 通过广州Nginx代理访问图片
|
||||
const imgName = 'deploy_greeting.png'
|
||||
const r7 = await exec(GZ, `curl -s -k --connect-timeout 5 -H "Host: guanghulab.com" -o /dev/null -w "HTTP:%{http_code} SIZE:%{size_download}" https://localhost/images/output/${imgName} 2>&1`)
|
||||
console.log(' 广州Nginx→图片:', r7.stdout || r7.stderr)
|
||||
|
||||
// 8. 生成更多测试
|
||||
console.log('\n3️⃣ 生成不同风格的测试图:')
|
||||
const tests = [
|
||||
['即刻风格', 'node generate.js --text "记录今日份思考\n种一棵树最好的时间是十年前\n其次是现在" --for "即刻" --output final_jike'],
|
||||
['海报风格', 'node generate.js --text "公司放假通知\n时间:6月1日-6月5日\n请大家提前安排好工作\n祝大家节日快乐" --for "海报" --output final_poster'],
|
||||
]
|
||||
for (const [name, cmd] of tests) {
|
||||
const r = await exec(SG, `cd /data/image-studio && ${cmd} 2>&1 | tail -5`)
|
||||
console.log(` ${name}:`, (r.stdout || r.stderr || '').slice(0, 100))
|
||||
}
|
||||
|
||||
// 9. 所有生成的文件
|
||||
console.log('\n4️⃣ 所有图片列表:')
|
||||
const r8 = await exec(SG, 'ls -lhS /data/image-studio/output/*.png 2>&1')
|
||||
console.log(r8.stdout || r8.stderr)
|
||||
|
||||
// 10. PM2状态
|
||||
console.log('\n5️⃣ PM2状态:')
|
||||
const r9 = await exec(SG, 'pm2 show image-studio 2>&1 | grep -E "status|uptime|restarts|port"')
|
||||
console.log(r9.stdout || r9.stderr)
|
||||
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('🎉 铸渊图片工作室部署完成!')
|
||||
console.log('═'.repeat(50))
|
||||
console.log()
|
||||
console.log('📎 访问入口:')
|
||||
console.log(' https://guanghulab.com/images/')
|
||||
console.log(' (广州Nginx → 新加坡Nginx → image-studio:3912)')
|
||||
console.log()
|
||||
console.log('🤖 如何使用:')
|
||||
console.log(' 冰朔,你只需要在对话里告诉我内容和场景,')
|
||||
console.log(' 我来理解、设计、生成配图。')
|
||||
console.log(' 生成的图片在 https://guanghulab.com/images/ 可以直接查看下载。')
|
||||
}
|
||||
|
||||
main().catch(err => console.error('❌', err))
|
||||
133
image-studio/deploy/fix-and-verify.mjs
Normal file
133
image-studio/deploy/fix-and-verify.mjs
Normal file
@ -0,0 +1,133 @@
|
||||
/**
|
||||
* 修复Chrome路径 + 检查广州Nginx配置
|
||||
*/
|
||||
|
||||
const SG = { ip: '43.156.237.110', port: 3911, key: 'zy_gtw_74d30f54fa8b5581514569ee7874def57861a31ccb2be8c9' }
|
||||
const GZ = { ip: '43.139.217.141', port: 3910, key: 'zy_gtw_4a155446b7acc09fe46a2a29972c0ca5d9954da19c35dc23' }
|
||||
|
||||
async function callGK(srv, method, path, body = null) {
|
||||
const url = `http://${srv.ip}:${srv.port}${path}`
|
||||
const opts = { method, headers: { 'Authorization': `Bearer ${srv.key}`, 'Content-Type': 'application/json' } }
|
||||
if (body) opts.body = JSON.stringify(body)
|
||||
const res = await fetch(url, opts)
|
||||
const text = await res.text()
|
||||
try { return { status: res.status, data: JSON.parse(text) } }
|
||||
catch { return { status: res.status, data: { raw: text } } }
|
||||
}
|
||||
|
||||
async function exec(srv, cmd) {
|
||||
const r = await callGK(srv, 'POST', '/exec', { cmd })
|
||||
return r.data
|
||||
}
|
||||
|
||||
async function readFile(srv, path) {
|
||||
const r = await callGK(srv, 'POST', '/file/read', { path })
|
||||
if (r.status === 200) return r.data.content
|
||||
return null
|
||||
}
|
||||
|
||||
async function writeFile(srv, path, content) {
|
||||
return (await callGK(srv, 'POST', '/file/write', { path, content })).status === 200
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// ====== 1. 修复Chrome路径 ======
|
||||
console.log('═'.repeat(50))
|
||||
console.log('🔧 修复1: Chrome浏览器路径')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
// 找到puppeteer安装的chrome路径
|
||||
const r1 = await exec(SG, 'ls /root/.cache/puppeteer/chrome/linux-*/chrome-linux64/chrome 2>&1')
|
||||
const chromePath = (r1.stdout || '').trim()
|
||||
console.log('Chrome实际路径:', chromePath || '未找到')
|
||||
|
||||
if (chromePath) {
|
||||
// 创建软链接到期望路径
|
||||
await exec(SG, `ln -sf ${chromePath} /usr/bin/google-chrome 2>&1`)
|
||||
await exec(SG, 'google-chrome --version 2>&1')
|
||||
console.log('✅ Chrome软链接已创建')
|
||||
|
||||
// 安装缺少的依赖
|
||||
const r2 = await exec(SG, 'ldd /usr/bin/google-chrome 2>&1 | grep "not found" | head -10')
|
||||
if (r2.stdout) {
|
||||
console.log('缺少依赖:', r2.stdout)
|
||||
await exec(SG, 'apt-get install -y -qq libnss3 libnspr4 libatk1.0-0t64 libatk-bridge2.0-0t64 libcups2t64 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 2>&1 | tail -3')
|
||||
}
|
||||
|
||||
// 测试生成一张图
|
||||
console.log('\n📸 测试生成图片...')
|
||||
const r3 = await exec(SG, 'cd /data/image-studio && node generate.js --text "你好冰朔\n图片工作室已部署完成\n现在可以在新加坡服务器上生成图片了" --for "小红书" --output deploy_greeting 2>&1')
|
||||
console.log(r3.stdout || r3.stderr)
|
||||
|
||||
// 验证图片文件
|
||||
const r4 = await exec(SG, 'ls -la /data/image-studio/output/deploy_greeting*.png 2>&1')
|
||||
console.log('图片文件:', r4.stdout || r4.stderr)
|
||||
|
||||
// 测试通过Nginx访问图片
|
||||
const r5 = await exec(SG, 'ls /data/image-studio/output/deploy_greeting*.png 2>&1 | head -1 | xargs -I{} basename {}')
|
||||
const imgName = (r5.stdout || '').trim()
|
||||
if (imgName) {
|
||||
const r6 = await exec(SG, `curl -s -o /dev/null -w "HTTP:%{http_code} SIZE:%{size_download}" http://localhost/images/output/${imgName} 2>&1`)
|
||||
console.log(`通过Nginx访问图片:`, r6.stdout || r6.stderr)
|
||||
}
|
||||
}
|
||||
|
||||
// ====== 2. 检查广州Nginx配置 ======
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('🔧 修复2: 广州Nginx配置')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
const gzConfig = await readFile(GZ, '/etc/nginx/sites-enabled/guanghulab')
|
||||
if (gzConfig) {
|
||||
console.log('广州Nginx配置(images部分):')
|
||||
const lines = gzConfig.split('\n')
|
||||
const imgLines = lines.filter(l => l.includes('images') || l.includes('43.156'))
|
||||
imgLines.forEach(l => console.log(' ' + l.trim()))
|
||||
|
||||
// 检查是否有/images/配置
|
||||
if (!gzConfig.includes('/images/')) {
|
||||
console.log('❌ 广州Nginx缺少/images/代理配置')
|
||||
} else {
|
||||
console.log('✅ /images/代理配置存在')
|
||||
|
||||
// 检查代理目标是否正确
|
||||
const proxyMatch = gzConfig.match(/proxy_pass\s+http:\/\/[^;]+\/images\//)
|
||||
if (proxyMatch) {
|
||||
console.log('代理目标:', proxyMatch[0])
|
||||
}
|
||||
|
||||
// 测试广州Nginx本地访问
|
||||
console.log('\n测试广州Nginx:')
|
||||
const r7 = await exec(GZ, `curl -s -k --connect-timeout 5 -w "\\nHTTP:%{http_code}" https://localhost/images/ 2>&1 | tail -3`)
|
||||
console.log(' https://localhost/images/:', r7.stdout || r7.stderr)
|
||||
|
||||
// 测试广州直接访问新加坡/images/(不含SSL)
|
||||
const r8 = await exec(GZ, `curl -s --connect-timeout 5 -w "\\nHTTP:%{http_code}" http://${SG.ip}/images/ 2>&1 | tail -3`)
|
||||
console.log(' http://SG_IP/images/:', r8.stdout || r8.stderr)
|
||||
}
|
||||
}
|
||||
|
||||
// ====== 3. 尝试解决广州Nginx代理问题 ======
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('🔧 修复3: 修正广州Nginx代理')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
// 读取完整配置检查root位置
|
||||
const gzRoot = await readFile(GZ, '/etc/nginx/sites-enabled/guanghulab')
|
||||
if (gzRoot) {
|
||||
// 找到/images/的配置位置并检查
|
||||
const imgBlockStart = gzRoot.indexOf('location /images/')
|
||||
if (imgBlockStart >= 0) {
|
||||
const contextStart = Math.max(0, imgBlockStart - 100)
|
||||
const contextEnd = Math.min(gzRoot.length, imgBlockStart + 400)
|
||||
console.log('/images/配置上下文:')
|
||||
console.log(gzRoot.slice(contextStart, contextEnd))
|
||||
}
|
||||
}
|
||||
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('📋 修复完成')
|
||||
console.log('═'.repeat(50))
|
||||
}
|
||||
|
||||
main().catch(err => console.error('❌', err))
|
||||
105
image-studio/deploy/fix-correct.mjs
Normal file
105
image-studio/deploy/fix-correct.mjs
Normal file
@ -0,0 +1,105 @@
|
||||
/**
|
||||
* 检查location插入位置并修复
|
||||
*/
|
||||
|
||||
const SG = { ip: '43.156.237.110', port: 3911, key: 'zy_gtw_74d30f54fa8b5581514569ee7874def57861a31ccb2be8c9' }
|
||||
|
||||
async function callGK(srv, method, path, body = null) {
|
||||
const url = `http://${srv.ip}:${srv.port}${path}`
|
||||
const opts = { method, headers: { 'Authorization': `Bearer ${srv.key}`, 'Content-Type': 'application/json' } }
|
||||
if (body) opts.body = JSON.stringify(body)
|
||||
const res = await fetch(url, opts)
|
||||
const text = await res.text()
|
||||
try { return { status: res.status, data: JSON.parse(text) } }
|
||||
catch { return { status: res.status, data: { raw: text } } }
|
||||
}
|
||||
|
||||
async function exec(srv, cmd) {
|
||||
const r = await callGK(srv, 'POST', '/exec', { cmd })
|
||||
return r.data
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// 查看50-75行
|
||||
const c1 = await exec(SG, 'cat -n /etc/nginx/sites-enabled/default | sed -n \'40,80p\'')
|
||||
console.log('=== 40-80行 ===')
|
||||
console.log(c1.stdout || c1.stderr)
|
||||
|
||||
// 也在最后几行看看
|
||||
const c2 = await exec(SG, 'cat -n /etc/nginx/sites-enabled/default | tail -30')
|
||||
console.log('\n=== 末尾30行 ===')
|
||||
console.log(c2.stdout || c2.stderr)
|
||||
|
||||
// 用grep看看/images/上下文
|
||||
const c3 = await exec(SG, 'grep -n -B5 -A10 "images" /etc/nginx/sites-enabled/default')
|
||||
console.log('\n=== /images/ 上下文 ===')
|
||||
console.log(c3.stdout || c3.stderr)
|
||||
|
||||
// 看起来location被插入到错误位置了
|
||||
// 最稳妥的方法:直接用python重新生成正确的配置文件
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('🔧 重新生成正确的配置')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
const result = await exec(SG, `
|
||||
python3 << 'PYEOF'
|
||||
# 1. 读取原始模板
|
||||
with open('/etc/nginx/sites-available/default', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# 2. 找到第一个server块的结束位置(行号71的})
|
||||
# 通过解析大括号匹配
|
||||
idx = content.index('server {')
|
||||
depth = 0
|
||||
pos = idx
|
||||
while pos < len(content):
|
||||
if content[pos] == '{':
|
||||
depth += 1
|
||||
elif content[pos] == '}':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
# 在这个}之前插入
|
||||
insert_block = '''
|
||||
# 铸渊图片工作室 · 代理到本地3912
|
||||
location /images/ {
|
||||
proxy_pass http://127.0.0.1:3912/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
'''
|
||||
new_content = content[:pos] + insert_block + content[pos:]
|
||||
with open('/etc/nginx/sites-enabled/default', 'w') as f:
|
||||
f.write(new_content)
|
||||
print(f'OK: 插入成功,pos={pos}')
|
||||
break
|
||||
pos += 1
|
||||
else:
|
||||
print('ERROR: 找不到server块结束位置')
|
||||
PYEOF
|
||||
`)
|
||||
console.log('Python:', result.stdout || result.stderr)
|
||||
|
||||
// 验证
|
||||
const c4 = await exec(SG, 'grep -n -B3 -A12 "images" /etc/nginx/sites-enabled/default')
|
||||
console.log('\n验证插入位置:')
|
||||
console.log(c4.stdout || c4.stderr)
|
||||
|
||||
const r1 = await exec(SG, 'nginx -t 2>&1')
|
||||
console.log('\nNginx测试:', r1.stdout || r1.stderr)
|
||||
|
||||
if (!(r1.stdout || '').includes('test failed') && !(r1.stderr || '').includes('test failed')) {
|
||||
await exec(SG, 'nginx -s reload 2>&1')
|
||||
const r2 = await exec(SG, 'curl -s -w "\\nHTTP:%{http_code}" http://localhost/images/ 2>&1')
|
||||
console.log('\n验证:', r2.stdout || r2.stderr)
|
||||
} else {
|
||||
console.log('配置有误,回滚')
|
||||
await exec(SG, 'cp /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default')
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(err => console.error('❌', err))
|
||||
126
image-studio/deploy/fix-final.mjs
Normal file
126
image-studio/deploy/fix-final.mjs
Normal file
@ -0,0 +1,126 @@
|
||||
/**
|
||||
* 查看Python插入结果 + 直接手动处理
|
||||
*/
|
||||
|
||||
const SG = { ip: '43.156.237.110', port: 3911, key: 'zy_gtw_74d30f54fa8b5581514569ee7874def57861a31ccb2be8c9' }
|
||||
|
||||
async function callGK(srv, method, path, body = null) {
|
||||
const url = `http://${srv.ip}:${srv.port}${path}`
|
||||
const opts = { method, headers: { 'Authorization': `Bearer ${srv.key}`, 'Content-Type': 'application/json' } }
|
||||
if (body) opts.body = JSON.stringify(body)
|
||||
const res = await fetch(url, opts)
|
||||
const text = await res.text()
|
||||
try { return { status: res.status, data: JSON.parse(text) } }
|
||||
catch { return { status: res.status, data: { raw: text } } }
|
||||
}
|
||||
|
||||
async function exec(srv, cmd) {
|
||||
const r = await callGK(srv, 'POST', '/exec', { cmd })
|
||||
return r.data
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// 检查/images/是否在配置中
|
||||
console.log('=== 查找/images/ ===')
|
||||
const c1 = await exec(SG, 'grep -n "images" /etc/nginx/sites-enabled/default')
|
||||
console.log(c1.stdout || c1.stderr || '(无匹配)')
|
||||
|
||||
console.log('\n=== 查找location ===')
|
||||
const c2 = await exec(SG, 'grep -n "location" /etc/nginx/sites-enabled/default')
|
||||
console.log(c2.stdout || c2.stderr)
|
||||
|
||||
// 检查整个server块
|
||||
console.log('\n=== 完整server块(19-75行) ===')
|
||||
const c3 = await exec(SG, 'sed -n \'19,75p\' /etc/nginx/sites-enabled/default')
|
||||
console.log(c3.stdout || c3.stderr)
|
||||
|
||||
// 直接写入一个新文件到conf.d (最干净的方法)
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('🔧 直接写入conf.d配置文件')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
// 创建一个单独的配置文件,监听在端口80,只处理/images/路径
|
||||
// 使用server_name _ 如果冲突,就用default_server的配置
|
||||
// 实际上,我们直接把这个location加到存在的default server里最简单
|
||||
// 用exec直接sed插入
|
||||
|
||||
const sedScript = `sed -i '71i\\\\n # 铸渊图片工作室\n location /images/ {\\n proxy_pass http://127.0.0.1:3912/;\\n proxy_http_version 1.1;\\n proxy_set_header Host \\$host;\\n proxy_set_header X-Real-IP \\$remote_addr;\\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\\n proxy_set_header X-Forwarded-Proto \\$scheme;\\n proxy_buffering off;\\n proxy_read_timeout 60s;\\n }' /etc/nginx/sites-enabled/default`
|
||||
|
||||
console.log('执行sed插入...')
|
||||
const r1 = await exec(SG, sedScript)
|
||||
console.log('sed:', (r1.stdout || r1.stderr || 'ok').slice(0, 200))
|
||||
|
||||
const r2 = await exec(SG, 'grep -n "images" /etc/nginx/sites-enabled/default')
|
||||
console.log('验证:', r2.stdout || r2.stderr || '(无匹配)')
|
||||
|
||||
// 如果sed不行,直接用Python写文件内容
|
||||
const c4 = await exec(SG, 'cat /etc/nginx/sites-enabled/default')
|
||||
const content = c4.stdout || ''
|
||||
if (content && !content.includes('/images/')) {
|
||||
console.log('\n⚠️ sed失败,用Python精确写入...')
|
||||
|
||||
const result = await exec(SG, `
|
||||
python3 << 'PYEOF'
|
||||
with open('/etc/nginx/sites-enabled/default', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# 在第71行位置(第一个server块结束时)插入
|
||||
# 找第一个独立的 "}" 在非注释行
|
||||
lines = content.split('\\n')
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.strip()
|
||||
# 找到第一个server块的结束 - 找第一个单独的 }
|
||||
# 在注释外的、不在server块开头的 }
|
||||
if stripped == '}' and i > 20 and i < 75:
|
||||
insert = [
|
||||
'',
|
||||
' # 铸渊图片工作室 · 代理到本地3912',
|
||||
' location /images/ {',
|
||||
' proxy_pass http://127.0.0.1:3912/;',
|
||||
' proxy_http_version 1.1;',
|
||||
' proxy_set_header Host $host;',
|
||||
' proxy_set_header X-Real-IP $remote_addr;',
|
||||
' proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;',
|
||||
' proxy_set_header X-Forwarded-Proto $scheme;',
|
||||
' proxy_buffering off;',
|
||||
' proxy_read_timeout 60s;',
|
||||
' }',
|
||||
]
|
||||
lines[i:i] = insert
|
||||
content = '\\n'.join(lines)
|
||||
with open('/etc/nginx/sites-enabled/default', 'w') as f:
|
||||
f.write(content)
|
||||
print(f'OK: 在第{i}行前插入location块')
|
||||
break
|
||||
else:
|
||||
print('ERROR: 找不到合适的插入位置')
|
||||
PYEOF
|
||||
`)
|
||||
console.log('Python:', result.stdout || result.stderr)
|
||||
}
|
||||
|
||||
const r3 = await exec(SG, 'grep -n "images" /etc/nginx/sites-enabled/default')
|
||||
console.log('\n验证:', r3.stdout || r3.stderr || '(无匹配)')
|
||||
|
||||
const r4 = await exec(SG, 'nginx -t 2>&1')
|
||||
console.log('Nginx测试:', r4.stdout || r4.stderr)
|
||||
|
||||
if ((r4.stdout || '').includes('test failed') || (r4.stderr || '').includes('test failed')) {
|
||||
console.log('\n⚠️ 配置有问题,回滚...')
|
||||
await exec(SG, 'cp /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default')
|
||||
await exec(SG, 'nginx -t 2>&1')
|
||||
console.log('已回滚')
|
||||
return
|
||||
}
|
||||
|
||||
const r5 = await exec(SG, 'nginx -s reload 2>&1')
|
||||
console.log('重载:', r5.stdout || r5.stderr || 'ok')
|
||||
|
||||
const r6 = await exec(SG, 'curl -s -w "\\nHTTP_CODE:%{http_code}" http://localhost/images/ | tail -5')
|
||||
console.log('\n验证:', r6.stdout || r6.stderr)
|
||||
|
||||
const r7 = await exec(SG, 'curl -s -w "\\nHTTP_CODE:%{http_code}" http://localhost:3912/ | tail -5')
|
||||
console.log('直接3912:', r7.stdout || r7.stderr)
|
||||
}
|
||||
|
||||
main().catch(err => console.error('❌', err))
|
||||
153
image-studio/deploy/fix-gz-nginx.mjs
Normal file
153
image-studio/deploy/fix-gz-nginx.mjs
Normal file
@ -0,0 +1,153 @@
|
||||
/**
|
||||
* 修复广州Nginx:把/images/移到正确的server块
|
||||
*/
|
||||
|
||||
const GZ = { ip: '43.139.217.141', port: 3910, key: 'zy_gtw_4a155446b7acc09fe46a2a29972c0ca5d9954da19c35dc23' }
|
||||
|
||||
async function callGK(srv, method, path, body = null) {
|
||||
const url = `http://${srv.ip}:${srv.port}${path}`
|
||||
const opts = { method, headers: { 'Authorization': `Bearer ${srv.key}`, 'Content-Type': 'application/json' } }
|
||||
if (body) opts.body = JSON.stringify(body)
|
||||
const res = await fetch(url, opts)
|
||||
const text = await res.text()
|
||||
try { return { status: res.status, data: JSON.parse(text) } }
|
||||
catch { return { status: res.status, data: { raw: text } } }
|
||||
}
|
||||
|
||||
async function exec(srv, cmd) {
|
||||
const r = await callGK(srv, 'POST', '/exec', { cmd })
|
||||
return r.data
|
||||
}
|
||||
|
||||
async function readFile(srv, path) {
|
||||
const r = await callGK(srv, 'POST', '/file/read', { path })
|
||||
if (r.status === 200) return r.data.content
|
||||
return null
|
||||
}
|
||||
|
||||
async function writeFile(srv, path, content) {
|
||||
return (await callGK(srv, 'POST', '/file/write', { path, content })).status === 200
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('═'.repeat(50))
|
||||
console.log('🔧 修复广州Nginx: /images/ 位置修正')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
// 读取配置
|
||||
const config = await readFile(GZ, '/etc/nginx/sites-enabled/guanghulab')
|
||||
if (!config) {
|
||||
console.log('❌ 无法读取配置')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('当前配置长度:', config.length, '字符')
|
||||
|
||||
// 找两个server块的结构
|
||||
// 第一个server: listen 443 ssl (HTTPS)
|
||||
// 第二个server: listen 80 (HTTP redirect)
|
||||
|
||||
// 找到 "server {" 的两次出现
|
||||
const firstServer = config.indexOf('server {')
|
||||
const secondServer = config.indexOf('server {', firstServer + 10)
|
||||
const firstServerEnd = config.indexOf('}', firstServer)
|
||||
const secondServerEnd = config.indexOf('}', secondServer)
|
||||
|
||||
console.log('\n结构分析:')
|
||||
console.log(' 第一个server { 位置:', firstServer)
|
||||
console.log(' 第一个server } 位置:', firstServerEnd)
|
||||
console.log(' 第二个server { 位置:', secondServer)
|
||||
console.log(' 第二个server } 位置:', secondServerEnd)
|
||||
|
||||
// 用Python来精确处理
|
||||
const result = await exec(GZ, `
|
||||
python3 << 'PYEOF'
|
||||
with open('/etc/nginx/sites-enabled/guanghulab', 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
# 找到/images/配置块(在第二个server块中)
|
||||
images_start = content.find(' # 铸渊图片工作室')
|
||||
images_end = content.find('}', images_start) + 1 # 包括最后一个}
|
||||
|
||||
if images_start == -1:
|
||||
print('ERROR: 找不到/images/配置块')
|
||||
exit(1)
|
||||
|
||||
images_block = content[images_start:images_end]
|
||||
print('找到/images/块:', images_block[:60] + '...')
|
||||
|
||||
# 从原文件中删除/images/块
|
||||
content_without = content[:images_start] + content[images_end:]
|
||||
print('删除/images/后长度:', len(content_without))
|
||||
|
||||
# 找到第一个server块(HTTPS)的结束位置
|
||||
# 第一个server块以"listen 443 ssl"开头,找到对应的}
|
||||
first_server_start = content_without.index('server {')
|
||||
# 找到listen 443 ssl的server块
|
||||
depth = 0
|
||||
pos = first_server_start
|
||||
while pos < len(content_without):
|
||||
if content_without[pos] == '{':
|
||||
depth += 1
|
||||
elif content_without[pos] == '}':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
# 在第一个server块的}前插入/images/块
|
||||
new_content = content_without[:pos] + '\\n' + images_block + content_without[pos:]
|
||||
with open('/etc/nginx/sites-enabled/guanghulab', 'w') as f:
|
||||
f.write(new_content)
|
||||
print(f'OK: 在第一个server块内(位置{pos})插入')
|
||||
break
|
||||
pos += 1
|
||||
else:
|
||||
print('ERROR: 找不到第一个server块结尾')
|
||||
|
||||
# 验证
|
||||
with open('/etc/nginx/sites-enabled/guanghulab', 'r') as f:
|
||||
final = f.read()
|
||||
|
||||
# 检查images块是否在正确的server块中
|
||||
ssl_pos = final.find('listen 443 ssl')
|
||||
second_server_pos = final.find('listen 80;\\n server_name guanghulab.com')
|
||||
images_in_final = final.find('location /images/')
|
||||
|
||||
print(f'SSL server start: {ssl_pos}')
|
||||
print(f'HTTP redirect start: {second_server_pos}')
|
||||
print(f'/images/ location: {images_in_final}')
|
||||
if ssl_pos < images_in_final < second_server_pos:
|
||||
print('✅ /images/ 在正确的SSL server块中!')
|
||||
else:
|
||||
print('❌ /images/ 位置不正确!')
|
||||
PYEOF
|
||||
`)
|
||||
console.log('\nPython:', result.stdout || result.stderr)
|
||||
|
||||
// 测试Nginx
|
||||
const r1 = await exec(GZ, 'nginx -t 2>&1')
|
||||
console.log('Nginx测试:', r1.stdout || r1.stderr)
|
||||
|
||||
if ((r1.stdout || '').includes('test failed') || (r1.stderr || '').includes('test failed')) {
|
||||
console.log('⚠️ 配置错误,回滚')
|
||||
// 重新部署 - 从备份读取
|
||||
} else {
|
||||
await exec(GZ, 'nginx -s reload 2>&1')
|
||||
console.log('Nginx重载成功')
|
||||
|
||||
// 测试
|
||||
const r2 = await exec(GZ, `curl -s -k --connect-timeout 5 -w "\\nHTTP:%{http_code}" https://localhost/images/ 2>&1 | tail -5`)
|
||||
console.log('\n测试 https://localhost/images/:')
|
||||
console.log((r2.stdout || '').slice(0, 200))
|
||||
|
||||
// 从外网测试
|
||||
const SG_IP = '43.156.237.110'
|
||||
const r3 = await exec(GZ, `curl -s --connect-timeout 5 -w "\\nHTTP:%{http_code}" "http://${SG_IP}/images/" -H "Host: guanghulab.com" 2>&1 | tail -3`)
|
||||
console.log('\n测试 http://SG_IP/images/ (Host: guanghulab.com):')
|
||||
console.log(r3.stdout || r3.stderr)
|
||||
}
|
||||
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('✅ 修复完成')
|
||||
console.log('═'.repeat(50))
|
||||
}
|
||||
|
||||
main().catch(err => console.error('❌', err))
|
||||
122
image-studio/deploy/fix-nginx-v2.mjs
Normal file
122
image-studio/deploy/fix-nginx-v2.mjs
Normal file
@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 修复新加坡Nginx:恢复默认配置 + 正确注入/images/
|
||||
*/
|
||||
|
||||
const SG = { ip: '43.156.237.110', port: 3911, key: 'zy_gtw_74d30f54fa8b5581514569ee7874def57861a31ccb2be8c9' }
|
||||
|
||||
async function callGK(srv, method, path, body = null) {
|
||||
const url = `http://${srv.ip}:${srv.port}${path}`
|
||||
const opts = { method, headers: { 'Authorization': `Bearer ${srv.key}`, 'Content-Type': 'application/json' } }
|
||||
if (body) opts.body = JSON.stringify(body)
|
||||
const res = await fetch(url, opts)
|
||||
const text = await res.text()
|
||||
try { return { status: res.status, data: JSON.parse(text) } }
|
||||
catch { return { status: res.status, data: { raw: text } } }
|
||||
}
|
||||
|
||||
async function exec(srv, cmd) {
|
||||
const r = await callGK(srv, 'POST', '/exec', { cmd })
|
||||
return r.data
|
||||
}
|
||||
|
||||
async function readFile(srv, path) {
|
||||
const r = await callGK(srv, 'POST', '/file/read', { path })
|
||||
if (r.status === 200) return r.data.content
|
||||
return null
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// 1. 查看当前损坏的配置
|
||||
console.log('📄 当前默认配置:')
|
||||
const current = await readFile(SG, '/etc/nginx/sites-enabled/default')
|
||||
console.log(current.slice(-500))
|
||||
|
||||
// 2. 用sed修复:恢复默认+注入/images/
|
||||
// 策略: 找到 "server {" 和第一个 "}" 之间的内容
|
||||
// 在第一个server块的末尾(}之前)插入/images/ location
|
||||
console.log('\n🔧 用sed修复...')
|
||||
|
||||
const result = await exec(SG, `
|
||||
# 先恢复原始默认配置
|
||||
cp /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default 2>/dev/null
|
||||
|
||||
# 然后在第一个server块最后(}之前)插入/images/ location
|
||||
sed -i '0,/^server {/{:a;N;\\|^}|!ba|s|\\n}\\n|\\n # 铸渊图片工作室\n location /images/ {\n proxy_pass http://127.0.0.1:3912/;\n proxy_http_version 1.1;\n proxy_set_header Host \\$host;\n proxy_set_header X-Real-IP \\$remote_addr;\n proxy_set_header X-Forwarded-For \\$proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto \\$scheme;\n proxy_buffering off;\n proxy_read_timeout 60s;\n }\n}\\n|}' /etc/nginx/sites-enabled/default
|
||||
`)
|
||||
console.log('sed结果:', (result.stdout || result.stderr || 'ok').slice(0, 200))
|
||||
|
||||
// 3. 验证
|
||||
const fixed = await readFile(SG, '/etc/nginx/sites-enabled/default')
|
||||
console.log('\n📄 修复后配置(最后30行):')
|
||||
const lines = fixed.split('\n')
|
||||
console.log(lines.slice(-30).join('\n'))
|
||||
|
||||
// 4. 测试
|
||||
const r1 = await exec(SG, 'nginx -t 2>&1')
|
||||
console.log('\nNginx测试:', r1.stdout || r1.stderr)
|
||||
|
||||
if (r1.stdout && r1.stdout.includes('test failed')) {
|
||||
// sed可能没处理好,用更简单的方法
|
||||
console.log('\n⚠️ sed方案失败,用纯Node方案...')
|
||||
|
||||
// 重新恢复
|
||||
await exec(SG, 'cp /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default 2>/dev/null')
|
||||
|
||||
// 用Python来精确插入
|
||||
const pyResult = await exec(SG, `
|
||||
python3 << 'PYEOF'
|
||||
content = open('/etc/nginx/sites-enabled/default', 'r').read()
|
||||
|
||||
# 找到第一个server块的结束位置
|
||||
# server块以"server {"开始,以"}"结束(不在http块内)
|
||||
idx = content.find('server {')
|
||||
if idx >= 0:
|
||||
# 找到这个server块的匹配}
|
||||
depth = 0
|
||||
pos = idx
|
||||
while pos < len(content):
|
||||
if content[pos] == '{':
|
||||
depth += 1
|
||||
elif content[pos] == '}':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
# 在}之前插入
|
||||
insert = '''
|
||||
# 铸渊图片工作室 · 代理到本地3912
|
||||
location /images/ {
|
||||
proxy_pass http://127.0.0.1:3912/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
'''
|
||||
new_content = content[:pos] + insert + content[pos:]
|
||||
open('/etc/nginx/sites-enabled/default', 'w').write(new_content)
|
||||
print('OK: inserted at position', pos)
|
||||
break
|
||||
pos += 1
|
||||
else:
|
||||
print('ERROR: server block not closed')
|
||||
else:
|
||||
print('ERROR: no server block found')
|
||||
PYEOF
|
||||
`)
|
||||
console.log('Python结果:', pyResult.stdout || pyResult.stderr)
|
||||
|
||||
const r2 = await exec(SG, 'nginx -t 2>&1')
|
||||
console.log('Nginx测试(重试):', r2.stdout || r2.stderr)
|
||||
}
|
||||
|
||||
const r3 = await exec(SG, 'nginx -s reload 2>&1 || systemctl reload nginx 2>&1')
|
||||
console.log('Nginx重载:', r3.stdout || r3.stderr || 'ok')
|
||||
|
||||
// 最终验证
|
||||
const r4 = await exec(SG, 'curl -s http://localhost/images/ | head -3')
|
||||
console.log('\n✅ 最终验证 /images/:', r4.stdout || r4.stderr)
|
||||
}
|
||||
|
||||
main().catch(err => console.error('❌', err))
|
||||
134
image-studio/deploy/fix-nginx-v3.mjs
Normal file
134
image-studio/deploy/fix-nginx-v3.mjs
Normal file
@ -0,0 +1,134 @@
|
||||
/**
|
||||
* 终极方案:直接用Node.js修改default配置
|
||||
* 在服务器上运行python脚本精确插入location块
|
||||
*/
|
||||
|
||||
const SG = { ip: '43.156.237.110', port: 3911, key: 'zy_gtw_74d30f54fa8b5581514569ee7874def57861a31ccb2be8c9' }
|
||||
|
||||
async function callGK(srv, method, path, body = null) {
|
||||
const url = `http://${srv.ip}:${srv.port}${path}`
|
||||
const opts = { method, headers: { 'Authorization': `Bearer ${srv.key}`, 'Content-Type': 'application/json' } }
|
||||
if (body) opts.body = JSON.stringify(body)
|
||||
const res = await fetch(url, opts)
|
||||
const text = await res.text()
|
||||
try { return { status: res.status, data: JSON.parse(text) } }
|
||||
catch { return { status: res.status, data: { raw: text } } }
|
||||
}
|
||||
|
||||
async function exec(srv, cmd) {
|
||||
const r = await callGK(srv, 'POST', '/exec', { cmd })
|
||||
return r.data
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// 先看完整的当前配置
|
||||
const c1 = await exec(SG, 'cat -n /etc/nginx/sites-enabled/default | head -80')
|
||||
console.log('=== 完整配置(1-80) ===')
|
||||
console.log(c1.stdout || c1.stderr)
|
||||
|
||||
const c2 = await exec(SG, 'cat -n /etc/nginx/sites-enabled/default | tail -80')
|
||||
console.log('\n=== 完整配置(末尾80行) ===')
|
||||
console.log(c2.stdout || c2.stderr)
|
||||
|
||||
// 找/images/的位置
|
||||
const c3 = await exec(SG, 'grep -n "images" /etc/nginx/sites-enabled/default')
|
||||
console.log('\n=== /images/ 位置 ===')
|
||||
console.log(c3.stdout || c3.stderr)
|
||||
|
||||
// 检查default文件是否被正确恢复
|
||||
const c4 = await exec(SG, 'head -50 /etc/nginx/sites-available/default 2>/dev/null | grep -n "server"')
|
||||
console.log('\n=== sites-available/default server位置 ===')
|
||||
console.log(c4.stdout || c4.stderr)
|
||||
|
||||
// 显示默认server块
|
||||
console.log('\n=== 默认server块(第41-80行) ===')
|
||||
const c5 = await exec(SG, 'sed -n \'41,80p\' /etc/nginx/sites-enabled/default')
|
||||
console.log(c5.stdout || c5.stderr)
|
||||
|
||||
// 我决定用另一种方法:删除所有修改,用python精确处理
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('🔧 使用Python精确插入location块')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
await exec(SG, `
|
||||
python3 << 'PYEOF'
|
||||
# 恢复原始配置
|
||||
import shutil
|
||||
shutil.copy('/etc/nginx/sites-available/default', '/etc/nginx/sites-enabled/default')
|
||||
|
||||
# 读取
|
||||
with open('/etc/nginx/sites-enabled/default', 'r') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# 找到第一个 active server block (非注释的server {)
|
||||
# 然后找到对应的第一个 }
|
||||
server_start = None
|
||||
for i, line in enumerate(lines):
|
||||
stripped = line.strip()
|
||||
if stripped == 'server {' and not line.strip().startswith('#'):
|
||||
# 这应该就是第一个非注释的server块
|
||||
server_start = i
|
||||
break
|
||||
|
||||
if server_start is not None:
|
||||
# 找这个server块的结束(匹配的})
|
||||
depth = 0
|
||||
for j in range(server_start, len(lines)):
|
||||
stripped = lines[j].strip()
|
||||
# 忽略注释
|
||||
if stripped.startswith('#'):
|
||||
continue
|
||||
if '{' in stripped:
|
||||
depth += stripped.count('{')
|
||||
if '}' in stripped:
|
||||
depth -= stripped.count('}')
|
||||
if depth == 0:
|
||||
# 在}之前插入location块
|
||||
location_block = [
|
||||
'\n',
|
||||
' # 铸渊图片工作室 · 代理到本地3912\n',
|
||||
' location /images/ {\n',
|
||||
' proxy_pass http://127.0.0.1:3912/;\n',
|
||||
' proxy_http_version 1.1;\n',
|
||||
' proxy_set_header Host $host;\n',
|
||||
' proxy_set_header X-Real-IP $remote_addr;\n',
|
||||
' proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n',
|
||||
' proxy_set_header X-Forwarded-Proto $scheme;\n',
|
||||
' proxy_buffering off;\n',
|
||||
' proxy_read_timeout 60s;\n',
|
||||
' }\n',
|
||||
]
|
||||
lines[j:j] = location_block
|
||||
with open('/etc/nginx/sites-enabled/default', 'w') as f:
|
||||
f.writelines(lines)
|
||||
print(f'OK: 在第{j}行前插入location块')
|
||||
break
|
||||
else:
|
||||
print('ERROR: server block not properly closed')
|
||||
else:
|
||||
print('ERROR: no active server block found')
|
||||
PYEOF
|
||||
`)
|
||||
|
||||
// 测试
|
||||
const r1 = await exec(SG, 'nginx -t 2>&1')
|
||||
console.log('Nginx测试:', r1.stdout || r1.stderr)
|
||||
|
||||
const r2 = await exec(SG, 'nginx -s reload 2>&1')
|
||||
console.log('Nginx重载:', r2.stdout || r2.stderr || 'ok')
|
||||
|
||||
const r3 = await exec(SG, 'curl -s http://localhost/images/ | head -3')
|
||||
console.log('验证 /images/:', r3.stdout || r3.stderr)
|
||||
|
||||
// 如果还不行,换最后方案:创建文件符号链接
|
||||
if ((r3.stdout || '').includes('404') || (r3.stderr || '').includes('404')) {
|
||||
console.log('\n⚠️ 路由方案失败,使用最后方案:软链接图片目录')
|
||||
await exec(SG, 'mkdir -p /var/www/html/images')
|
||||
await exec(SG, 'ln -sf /data/image-studio/output /var/www/html/images/output 2>&1')
|
||||
await exec(SG, 'ls -la /var/www/html/images/')
|
||||
const r4 = await exec(SG, 'curl -s http://localhost/images/output/ | head -5')
|
||||
console.log('软链接测试:', r4.stdout || r4.stderr)
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(err => console.error('❌', err))
|
||||
139
image-studio/deploy/fix-nginx.mjs
Normal file
139
image-studio/deploy/fix-nginx.mjs
Normal file
@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 修复新加坡Nginx配置 + 修复广州配置冲突
|
||||
*/
|
||||
|
||||
const SG = { ip: '43.156.237.110', port: 3911, key: 'zy_gtw_74d30f54fa8b5581514569ee7874def57861a31ccb2be8c9' }
|
||||
const GZ = { ip: '43.139.217.141', port: 3910, key: 'zy_gtw_4a155446b7acc09fe46a2a29972c0ca5d9954da19c35dc23' }
|
||||
|
||||
async function callGK(srv, method, path, body = null) {
|
||||
const url = `http://${srv.ip}:${srv.port}${path}`
|
||||
const opts = { method, headers: { 'Authorization': `Bearer ${srv.key}`, 'Content-Type': 'application/json' } }
|
||||
if (body) opts.body = JSON.stringify(body)
|
||||
const res = await fetch(url, opts)
|
||||
const text = await res.text()
|
||||
try { return { status: res.status, data: JSON.parse(text) } }
|
||||
catch { return { status: res.status, data: { raw: text } } }
|
||||
}
|
||||
|
||||
async function exec(srv, cmd) {
|
||||
const r = await callGK(srv, 'POST', '/exec', { cmd })
|
||||
return r.data
|
||||
}
|
||||
|
||||
async function readFile(srv, path) {
|
||||
const r = await callGK(srv, 'POST', '/file/read', { path })
|
||||
if (r.status === 200) return r.data.content
|
||||
return null
|
||||
}
|
||||
|
||||
async function writeFile(srv, path, content) {
|
||||
return (await callGK(srv, 'POST', '/file/write', { path, content })).status === 200
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('═'.repeat(50))
|
||||
console.log('🔧 修复Nginx配置')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
// === 新加坡修复 ===
|
||||
console.log('\n📌 新加坡: 将/images/注入默认server块')
|
||||
|
||||
// 读取默认配置
|
||||
const defaultConf = await readFile(SG, '/etc/nginx/sites-enabled/default')
|
||||
if (!defaultConf) {
|
||||
console.log(' ❌ 无法读取默认配置')
|
||||
return
|
||||
}
|
||||
|
||||
// 删除之前创建的冲突配置
|
||||
await exec(SG, 'rm -f /etc/nginx/sites-enabled/image-studio /etc/nginx/sites-available/image-studio 2>&1')
|
||||
console.log(' ✅ 删除冲突的server块')
|
||||
|
||||
// 在server块中注入/images/ location
|
||||
// 找到第一个server块在http块(default文件里)或直接根级别
|
||||
// 在server块的最后一个}之前插入location /images/
|
||||
|
||||
// 找server块最后的}
|
||||
const lastBraceBeforeEnd = defaultConf.lastIndexOf('}')
|
||||
if (lastBraceBeforeEnd === -1) {
|
||||
console.log(' ❌ 找不到server块结尾')
|
||||
return
|
||||
}
|
||||
|
||||
const imagesLoc = `
|
||||
# 铸渊图片工作室 · 代理到本地3912
|
||||
location /images/ {
|
||||
proxy_pass http://127.0.0.1:3912/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
}
|
||||
`
|
||||
const newDefault = defaultConf.slice(0, lastBraceBeforeEnd) + imagesLoc
|
||||
|
||||
if (await writeFile(SG, '/etc/nginx/sites-enabled/default', newDefault)) {
|
||||
console.log(' ✅ 默认配置更新成功')
|
||||
}
|
||||
|
||||
// 测试+重载
|
||||
const r1 = await exec(SG, 'nginx -t 2>&1')
|
||||
console.log(' Nginx测试:', r1.stdout || r1.stderr)
|
||||
const r2 = await exec(SG, 'nginx -s reload 2>&1')
|
||||
console.log(' Nginx重载:', r2.stdout || r2.stderr || 'ok')
|
||||
|
||||
// 验证
|
||||
const r3 = await exec(SG, 'curl -s http://localhost/images/ | head -3')
|
||||
console.log(' ✅ 本地验证:', r3.stdout || r3.stderr)
|
||||
|
||||
// === 广州修复 ===
|
||||
console.log('\n📌 广州: 删除guanghulab.bak避免conflict')
|
||||
await exec(GZ, 'rm -f /etc/nginx/sites-enabled/guanghulab.bak 2>&1')
|
||||
console.log(' ✅ 删除.bak备份')
|
||||
|
||||
// 检查是否有sites-available的guanghulab.conf被软链到了sites-enabled
|
||||
const r4 = await exec(GZ, 'ls -la /etc/nginx/sites-enabled/')
|
||||
console.log(' 当前sites-enabled:', r4.stdout || r4.stderr)
|
||||
|
||||
// 验证广州Nginx配置
|
||||
const r5 = await exec(GZ, 'nginx -t 2>&1')
|
||||
console.log(' 广州Nginx测试:', r5.stdout || r5.stderr)
|
||||
const r6 = await exec(GZ, 'nginx -s reload 2>&1')
|
||||
console.log(' 广州Nginx重载:', r6.stdout || r6.stderr || 'ok')
|
||||
|
||||
// === 广州到新加坡全部链路测试 ===
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('📡 全链路测试')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
// 1. 新加坡本地 → image-studio
|
||||
const r7 = await exec(SG, 'curl -s http://localhost:3912/ | head -2')
|
||||
console.log(' 1. SG local:3912:', r7.stdout || r7.stderr)
|
||||
|
||||
// 2. 新加坡 Nginx → image-studio
|
||||
const r8 = await exec(SG, 'curl -s http://localhost/images/ | head -2')
|
||||
console.log(' 2. SG nginx /images:', r8.stdout || r8.stderr)
|
||||
|
||||
// 3. 广州 → 新加坡80端口/images
|
||||
const r9 = await exec(GZ, `curl -s --connect-timeout 5 http://${SG.ip}/images/ | head -2`)
|
||||
console.log(' 3. GZ→SG /images:', r9.stdout || r9.stderr || '(空)')
|
||||
|
||||
// 4. 广州 Nginx → 新加坡 (https://guanghulab.com/images/)
|
||||
const r10 = await exec(GZ, 'curl -s -k --connect-timeout 5 https://localhost/images/ | head -2')
|
||||
console.log(' 4. GZ nginx /images:', r10.stdout || r10.stderr || '(空)')
|
||||
|
||||
// 测试直接访问新加坡80端口
|
||||
console.log('\n 测试新加坡80端口根路径:')
|
||||
const r11 = await exec(GZ, `curl -s --connect-timeout 5 http://${SG.ip}/ | head -3`)
|
||||
console.log(' ', r11.stdout || r11.stderr || '(空)')
|
||||
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('✅ 修复完成')
|
||||
console.log('═'.repeat(50))
|
||||
}
|
||||
|
||||
main().catch(err => console.error('❌', err))
|
||||
179
image-studio/deploy/nginx-proxy.mjs
Normal file
179
image-studio/deploy/nginx-proxy.mjs
Normal file
@ -0,0 +1,179 @@
|
||||
/**
|
||||
* 使用新加坡现有Nginx添加image-studio反向代理
|
||||
* 广州可以通过新加坡的80端口访问到image-studio
|
||||
*/
|
||||
|
||||
const SG = { name: '新加坡', ip: '43.156.237.110', port: 3911, key: 'zy_gtw_74d30f54fa8b5581514569ee7874def57861a31ccb2be8c9' }
|
||||
const GZ = { name: '广州', ip: '43.139.217.141', port: 3910, key: 'zy_gtw_4a155446b7acc09fe46a2a29972c0ca5d9954da19c35dc23' }
|
||||
|
||||
async function callGK(srv, method, path, body = null) {
|
||||
const url = `http://${srv.ip}:${srv.port}${path}`
|
||||
const opts = { method, headers: { 'Authorization': `Bearer ${srv.key}`, 'Content-Type': 'application/json' } }
|
||||
if (body) opts.body = JSON.stringify(body)
|
||||
const res = await fetch(url, opts)
|
||||
const text = await res.text()
|
||||
try { return { status: res.status, data: JSON.parse(text) } }
|
||||
catch { return { status: res.status, data: { raw: text } } }
|
||||
}
|
||||
|
||||
async function exec(srv, cmd) {
|
||||
const r = await callGK(srv, 'POST', '/exec', { cmd })
|
||||
return r.data
|
||||
}
|
||||
|
||||
async function readFile(srv, path) {
|
||||
const r = await callGK(srv, 'POST', '/file/read', { path })
|
||||
if (r.status === 200) return r.data.content
|
||||
return null
|
||||
}
|
||||
|
||||
async function writeFile(srv, path, content) {
|
||||
const r = await callGK(srv, 'POST', '/file/write', { path, content })
|
||||
return r.status === 200
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('═'.repeat(50))
|
||||
console.log('🔧 配置新加坡Nginx + 广州Nginx')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
// 1. 读取新加坡Nginx配置
|
||||
console.log('\n📄 新加坡Nginx已有配置:')
|
||||
|
||||
const sites = [
|
||||
'/etc/nginx/sites-enabled/default',
|
||||
'/etc/nginx/sites-available/guanghubingshuo.com',
|
||||
'/etc/nginx/sites-available/guanghuclip.cn',
|
||||
]
|
||||
|
||||
for (const p of sites) {
|
||||
try {
|
||||
const content = await readFile(SG, p)
|
||||
if (content) {
|
||||
console.log(`\n--- ${p} ---`)
|
||||
console.log(content.slice(0, 1500))
|
||||
if (content.length > 1500) console.log('...(截断)')
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(` ${p}: 读取失败`)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 查看新加坡Nginx的server_name
|
||||
console.log('\n🔍 新加坡server_name:')
|
||||
const r1 = await exec(SG, 'grep -rn "server_name" /etc/nginx/sites-enabled/ 2>&1')
|
||||
console.log(r1.stdout || r1.stderr)
|
||||
|
||||
// 3. 查看已有的默认站点
|
||||
console.log('\n🔍 新加坡默认站点:')
|
||||
const r2 = await exec(SG, 'curl -s -H "Host: guanghubingshuo.com" http://localhost:80/ | head -5')
|
||||
console.log(r2.stdout || '(空)')
|
||||
|
||||
const r3 = await exec(SG, 'curl -s http://localhost:80/ | head -5')
|
||||
console.log(' 直接访问80端口:', r3.stdout || '(空)')
|
||||
|
||||
// 4. 现在添加image-studio到新加坡Nginx
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('📝 添加image-studio到新加坡Nginx')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
// 在sites-available创建image-studio配置
|
||||
const sgImagesConf = `
|
||||
# 铸渊图片工作室 · 通过新加坡Nginx代理到本地3912
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
location /images/ {
|
||||
proxy_pass http://127.0.0.1:3912/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
|
||||
location /images/output/ {
|
||||
alias /data/image-studio/output/;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Cache-Control "public, max-age=3600";
|
||||
}
|
||||
}
|
||||
`
|
||||
await writeFile(SG, '/etc/nginx/sites-available/image-studio', sgImagesConf)
|
||||
console.log(' ✅ 配置文件写入')
|
||||
|
||||
// 如果default已经存在且有server_name _,就把/images加到default里
|
||||
// 或者把image-studio软链到sites-enabled
|
||||
await exec(SG, 'ln -sf /etc/nginx/sites-available/image-studio /etc/nginx/sites-enabled/image-studio 2>&1')
|
||||
const r4 = await exec(SG, 'nginx -t 2>&1')
|
||||
console.log(' Nginx测试:', r4.stdout || r4.stderr)
|
||||
|
||||
const r5 = await exec(SG, 'nginx -s reload 2>&1')
|
||||
console.log(' Nginx重载:', r5.stdout || r5.stderr || 'ok')
|
||||
|
||||
// 5. 测试本地是否能通过/images访问
|
||||
const r6 = await exec(SG, 'curl -s http://localhost:80/images/ | head -3')
|
||||
console.log('\n ✅ 本地验证 /images:', r6.stdout || r6.stderr)
|
||||
|
||||
// 6. 从广州测试新加坡的/images
|
||||
const r7 = await exec(GZ, `curl -s --connect-timeout 5 http://${SG.ip}/images/ | head -3`)
|
||||
console.log('\n 📡 广州→新加坡 /images 测试:', r7.stdout || r7.stderr || '无响应')
|
||||
|
||||
// 7. 配置广州Nginx
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('📝 配置广州Nginx反向代理')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
// 读取广州Nginx当前活跃配置
|
||||
const gzActive = await readFile(GZ, '/etc/nginx/sites-enabled/guanghulab')
|
||||
if (gzActive) {
|
||||
console.log(' 当前配置长度:', gzActive.length, '字符')
|
||||
|
||||
// 检查是否已有/images配置
|
||||
if (gzActive.includes('/images')) {
|
||||
console.log(' ⚠️ /images 已存在,跳过')
|
||||
} else {
|
||||
// 在server block的末尾(最后一个}之前)添加/images代理
|
||||
const insertPoint = gzActive.lastIndexOf('}')
|
||||
const imagesBlock = `
|
||||
# 铸渊图片工作室 · 新加坡服务器反向代理
|
||||
location /images/ {
|
||||
proxy_pass http://${SG.ip}/images/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
}
|
||||
`
|
||||
const newConfig = gzActive.slice(0, insertPoint) + imagesBlock
|
||||
if (await writeFile(GZ, '/etc/nginx/sites-enabled/guanghulab', newConfig)) {
|
||||
console.log(' ✅ 广州Nginx配置更新成功')
|
||||
} else {
|
||||
console.log(' ❌ 广州Nginx配置更新失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试广州Nginx配置
|
||||
const r8 = await exec(GZ, 'nginx -t 2>&1')
|
||||
console.log(' 广州Nginx测试:', r8.stdout || r8.stderr)
|
||||
|
||||
const r9 = await exec(GZ, 'nginx -s reload 2>&1')
|
||||
console.log(' 广州Nginx重载:', r9.stdout || r9.stderr || 'ok')
|
||||
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('✅ 全部配置完成!')
|
||||
console.log('═'.repeat(50))
|
||||
console.log(`\n📎 访问地址:`)
|
||||
console.log(` https://guanghulab.com/images/`)
|
||||
console.log(` (广州Nginx → 新加坡Nginx → image-studio:3912)`)
|
||||
}
|
||||
|
||||
main().catch(err => console.error('❌', err))
|
||||
51
image-studio/deploy/read-active-config.mjs
Normal file
51
image-studio/deploy/read-active-config.mjs
Normal file
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 读取Nginx当前活跃配置
|
||||
*/
|
||||
const SERVERS = {
|
||||
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 readFile(server, path) {
|
||||
const result = await callGatekeeper(server, 'POST', '/file/read', { path })
|
||||
if (result.status === 200) return result.data.content
|
||||
return null
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const gz = SERVERS.gz
|
||||
|
||||
// 读当前活跃配置
|
||||
const active = await readFile(gz, '/etc/nginx/sites-enabled/guanghulab')
|
||||
console.log('=== 当前活跃配置 (sites-enabled/guanghulab) ===')
|
||||
console.log(active)
|
||||
|
||||
// 读模板
|
||||
const tpl = await readFile(gz, '/etc/nginx/sites-available/guanghulab.conf')
|
||||
if (tpl) {
|
||||
console.log('\n=== 模板 (sites-available/guanghulab.conf) ===')
|
||||
console.log(tpl.slice(0, 500))
|
||||
console.log('...(截断)')
|
||||
}
|
||||
|
||||
// 测试广州服务器是否能访问新加坡的3912
|
||||
console.log('\n=== 网络连通性测试 ===')
|
||||
const sgIp = '43.156.237.110'
|
||||
const r = await callGatekeeper(gz, 'POST', '/exec', { cmd: `curl -s --connect-timeout 5 http://${sgIp}:3912/ | head -3` })
|
||||
console.log(r.data.stdout || r.data.stderr || JSON.stringify(r.data))
|
||||
}
|
||||
|
||||
main().catch(err => console.error('❌', err))
|
||||
89
image-studio/deploy/read-config.mjs
Normal file
89
image-studio/deploy/read-config.mjs
Normal file
@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 读取站点配置文件
|
||||
*/
|
||||
const SERVERS = {
|
||||
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 readFile(server, path) {
|
||||
const result = await callGatekeeper(server, 'POST', '/file/read', { path })
|
||||
if (result.status === 200) return result.data.content
|
||||
return null
|
||||
}
|
||||
|
||||
async function exec(server, cmd) {
|
||||
const url = `http://${server.ip}:${server.port}/exec`
|
||||
const opts = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${server.key}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ cmd }),
|
||||
}
|
||||
const res = await fetch(url, opts)
|
||||
const data = await res.json()
|
||||
return data
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const gz = SERVERS.gz
|
||||
|
||||
// 读取关键配置文件
|
||||
for (const p of [
|
||||
'/etc/nginx/sites-available/forgejo.conf',
|
||||
'/etc/nginx/sites-available/guanghulab.conf',
|
||||
]) {
|
||||
console.log(`\n📄 ${p}:`)
|
||||
const content = await readFile(gz, p)
|
||||
if (content) {
|
||||
console.log(content)
|
||||
} else {
|
||||
console.log(' (读取失败或文件不存在)')
|
||||
}
|
||||
}
|
||||
|
||||
// 看sites-enabled里有什么
|
||||
console.log('\n📂 sites-enabled:')
|
||||
const r = await exec(gz, 'ls -la /etc/nginx/sites-enabled/ 2>&1')
|
||||
console.log(r.stdout || r.stderr)
|
||||
|
||||
// 看conf.d
|
||||
console.log('\n📂 conf.d:')
|
||||
const r2 = await exec(gz, 'ls -la /etc/nginx/conf.d/ 2>&1')
|
||||
console.log(r2.stdout || r2.stderr)
|
||||
|
||||
// 查看Forgejo进程
|
||||
console.log('\n🔍 Forgejo运行情况:')
|
||||
const r3 = await exec(gz, 'ss -tlnp 2>/dev/null | head -20')
|
||||
console.log(r3.stdout || r3.stderr)
|
||||
|
||||
// 看guanghulab.com域名的Nginx路由
|
||||
console.log('\n🔍 查找server_name:')
|
||||
const r4 = await exec(gz, 'grep -rn "server_name" /etc/nginx/ 2>&1')
|
||||
console.log(r4.stdout || r4.stderr)
|
||||
|
||||
// 看现有proxy_pass
|
||||
console.log('\n🔍 查找proxy_pass:')
|
||||
const r5 = await exec(gz, 'grep -rn "proxy_pass" /etc/nginx/ 2>&1')
|
||||
console.log(r5.stdout || r5.stderr)
|
||||
}
|
||||
|
||||
main().catch(err => console.error('❌', err))
|
||||
149
image-studio/deploy/remote-deploy-2.mjs
Normal file
149
image-studio/deploy/remote-deploy-2.mjs
Normal file
@ -0,0 +1,149 @@
|
||||
/**
|
||||
* ═══════════════════════════════════════════════════
|
||||
* 铸渊远程部署 · 第二阶段:安装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)
|
||||
})
|
||||
96
image-studio/deploy/remote-deploy-3.mjs
Normal file
96
image-studio/deploy/remote-deploy-3.mjs
Normal file
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* ═══════════════════════════════════════════════════
|
||||
* 铸渊远程部署 · 第三阶段:配置广州Nginx反向代理
|
||||
* ═══════════════════════════════════════════════════
|
||||
*/
|
||||
|
||||
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) {
|
||||
const result = await callGatekeeper(server, 'POST', '/exec', { cmd })
|
||||
if (result.status !== 200) {
|
||||
return { ok: false, error: result.data }
|
||||
}
|
||||
return { ok: true, stdout: result.data.stdout || '', stderr: result.data.stderr || '' }
|
||||
}
|
||||
|
||||
async function readFile(server, path) {
|
||||
const result = await callGatekeeper(server, 'POST', '/file/read', { path })
|
||||
if (result.status === 200) return result.data.content
|
||||
return null
|
||||
}
|
||||
|
||||
async function writeFile(server, path, content) {
|
||||
const result = await callGatekeeper(server, 'POST', '/file/write', { path, content })
|
||||
return result.status === 200
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const gz = SERVERS.gz
|
||||
const sg = SERVERS.sg
|
||||
|
||||
console.log('═'.repeat(50))
|
||||
console.log('🌐 配置广州Nginx反向代理')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
// 1. 查找所有Nginx配置相关文件
|
||||
console.log('\n📂 查找Nginx配置...')
|
||||
const lsResult = await exec(gz, 'find /etc/nginx -type f -name "*.conf" | sort')
|
||||
if (lsResult.ok) console.log(lsResult.stdout)
|
||||
|
||||
// 2. 读取各个配置文件
|
||||
for (const p of ['/etc/nginx/conf.d/default.conf', '/etc/nginx/sites-enabled/default', '/etc/nginx/nginx.conf']) {
|
||||
const content = await readFile(gz, p)
|
||||
if (content) {
|
||||
console.log(`\n📄 ${p}:`)
|
||||
console.log(content.slice(0, 2000))
|
||||
if (content.length > 2000) console.log(`... (共${content.length}字符,截断显示)`)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 查找已部署的站点信息
|
||||
console.log('\n🔍 查找站点的server_name...')
|
||||
await exec(gz, 'grep -r "server_name" /etc/nginx/ 2>/dev/null')
|
||||
|
||||
// 4. 查看Forgejo的端口
|
||||
console.log('\n🔍 查找Forgejo端口...')
|
||||
await exec(gz, 'ss -tlnp | grep -E "3000|8080|3910"')
|
||||
|
||||
// 5. 查找现有反向代理规则
|
||||
console.log('\n🔍 查找现有proxy_pass...')
|
||||
await exec(gz, 'grep -r "proxy_pass" /etc/nginx/ 2>/dev/null')
|
||||
|
||||
// 6. 测试新加坡服务器是否能从广州访问
|
||||
console.log('\n🔗 测试广州→新加坡网络...')
|
||||
const gwTest = await exec(gz, `curl -s --connect-timeout 5 http://${sg.ip}:3912/ | head -3 || echo "无法连接"`)
|
||||
console.log(gwTest.stdout || gwTest.stderr)
|
||||
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('✅ Nginx调查完成, 准备配置代理')
|
||||
console.log('═'.repeat(50))
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('❌ 失败:', err.message)
|
||||
process.exit(1)
|
||||
})
|
||||
140
image-studio/deploy/remote-deploy.mjs
Normal file
140
image-studio/deploy/remote-deploy.mjs
Normal file
@ -0,0 +1,140 @@
|
||||
/**
|
||||
* ═══════════════════════════════════════════════════
|
||||
* 铸渊远程部署 · 通过看门人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)
|
||||
})
|
||||
126
image-studio/deploy/setup-proxy-v2.mjs
Normal file
126
image-studio/deploy/setup-proxy-v2.mjs
Normal file
@ -0,0 +1,126 @@
|
||||
/**
|
||||
* 在新加坡服务器上配置Nginx反向代理
|
||||
* 广州→新加坡的3911端口(Gatekeeper)是通的
|
||||
* 但3912端口不通。需要在新加坡本地装Nginx
|
||||
* 来代理访问,或者使用其他可通端口。
|
||||
*/
|
||||
|
||||
const SG = {
|
||||
name: '新加坡·铸渊大脑', ip: '43.156.237.110', port: 3911,
|
||||
key: 'zy_gtw_74d30f54fa8b5581514569ee7874def57861a31ccb2be8c9'
|
||||
}
|
||||
const GZ = {
|
||||
name: '广州·代码仓库', ip: '43.139.217.141', port: 3910,
|
||||
key: 'zy_gtw_4a155446b7acc09fe46a2a29972c0ca5d9954da19c35dc23'
|
||||
}
|
||||
|
||||
async function callGK(srv, method, path, body = null) {
|
||||
const url = `http://${srv.ip}:${srv.port}${path}`
|
||||
const opts = {
|
||||
method, headers: { 'Authorization': `Bearer ${srv.key}`, 'Content-Type': 'application/json' }
|
||||
}
|
||||
if (body) opts.body = JSON.stringify(body)
|
||||
const res = await fetch(url, opts)
|
||||
const text = await res.text()
|
||||
try { return { status: res.status, data: JSON.parse(text) } }
|
||||
catch { return { status: res.status, data: { raw: text } } }
|
||||
}
|
||||
|
||||
async function exec(srv, cmd) {
|
||||
const r = await callGK(srv, 'POST', '/exec', { cmd })
|
||||
return r.data
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// 第一步:检查新加坡服务器的开放端口
|
||||
console.log('📡 检查新加坡服务器已监听端口...')
|
||||
const r1 = await exec(SG, 'ss -tlnp')
|
||||
console.log(r1.stdout || r1.stderr)
|
||||
|
||||
// 第二步:检查是否已有Nginx
|
||||
console.log('\n🔍 检查Nginx...')
|
||||
const r2 = await exec(SG, 'which nginx && nginx -v 2>&1 || echo "no nginx"')
|
||||
console.log(r2.stdout || r2.stderr)
|
||||
|
||||
// 第三步:查看所有Nginx站点
|
||||
const r3 = await exec(SG, 'ls -la /etc/nginx/sites-enabled/ 2>&1 || echo "no sites-enabled"')
|
||||
console.log('\nNginx站点:', r3.stdout || r3.stderr)
|
||||
|
||||
// 第四步:查看正在运行的服务,找到已经开放的端口
|
||||
console.log('\n🔍 检查其他服务端口...')
|
||||
const r4 = await exec(SG, 'curl -s --connect-timeout 3 http://localhost:80/ 2>&1 | head -3 || echo "port 80 not serving"')
|
||||
console.log('port 80:', r4.stdout || r4.stderr)
|
||||
const r5 = await exec(SG, 'curl -s --connect-timeout 3 http://localhost:3000/ 2>&1 | head -3 || echo "port 3000 not serving"')
|
||||
console.log('port 3000:', r5.stdout || r5.stderr)
|
||||
const r6 = await exec(SG, 'curl -s --connect-timeout 3 http://localhost:8080/ 2>&1 | head -3 || echo "port 8080 not serving"')
|
||||
console.log('port 8080:', r6.stdout || r6.stderr)
|
||||
|
||||
// 第五步:从广州测试哪些端口到新加坡是通的
|
||||
console.log('\n📡 广州→新加坡端口测试...')
|
||||
const testPorts = [80, 443, 8080, 3000, 3910, 3911]
|
||||
for (const port of testPorts) {
|
||||
const r = await exec(GZ, `timeout 3 bash -c "echo > /dev/tcp/${SG.ip}/${port}" 2>&1 && echo '✅' || echo '❌'`)
|
||||
const status = (r.stdout || '').trim()
|
||||
console.log(` 端口 ${port}: ${status}`)
|
||||
}
|
||||
|
||||
// 第六步:方案1 - 如果80端口可用,就用Nginx代理
|
||||
const r7 = await exec(SG, 'which nginx 2>&1 || apt-get install -y -qq nginx 2>&1 | tail -3')
|
||||
console.log('\n📥 Nginx安装:', (r7.stdout || r7.stderr).slice(0, 100))
|
||||
|
||||
// 创建Nginx配置
|
||||
const nginxSiteConf = `server {
|
||||
listen 3914;
|
||||
server_name _;
|
||||
|
||||
# 允许跨域
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range";
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3912;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
|
||||
location /output/ {
|
||||
alias /data/image-studio/output/;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Cache-Control "public, max-age=3600";
|
||||
}
|
||||
}
|
||||
`
|
||||
const r8 = await exec(SG, `cat > /etc/nginx/sites-available/image-studio << 'EOF'
|
||||
${nginxSiteConf}
|
||||
EOF`)
|
||||
console.log('\n📝 Nginx配置写入:', (r8.stdout || r8.stderr || 'ok').slice(0, 100))
|
||||
|
||||
// 启用站点
|
||||
await exec(SG, 'ln -sf /etc/nginx/sites-available/image-studio /etc/nginx/sites-enabled/ 2>&1')
|
||||
const r9 = await exec(SG, 'nginx -t 2>&1')
|
||||
console.log('Nginx测试:', r9.stdout || r9.stderr)
|
||||
const r10 = await exec(SG, 'nginx -s reload 2>&1 || systemctl reload nginx 2>&1')
|
||||
console.log('Nginx重载:', r10.stdout || r10.stderr || 'ok')
|
||||
|
||||
// 验证本地
|
||||
const r11 = await exec(SG, 'curl -s http://localhost:3914/ | head -3')
|
||||
console.log('\n✅ 本地验证:', r11.stdout || r11.stderr)
|
||||
|
||||
// 从广州测试新端口
|
||||
const r12 = await exec(GZ, `timeout 3 bash -c "echo > /dev/tcp/${SG.ip}/3914" 2>&1 && echo '✅ OPEN' || echo '❌ CLOSED'`)
|
||||
console.log('广州→新加坡3914:', (r12.stdout || '').trim())
|
||||
|
||||
// 如果3914也不行,做最终方案:直接用广州的Gatekeeper代理
|
||||
// 让广州的Nginx通过Gatekeeper API转发请求
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('📋 配置完成')
|
||||
console.log('═'.repeat(50))
|
||||
}
|
||||
|
||||
main().catch(err => console.error('❌', err))
|
||||
125
image-studio/deploy/setup-proxy.mjs
Normal file
125
image-studio/deploy/setup-proxy.mjs
Normal file
@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 解决网络连通性问题
|
||||
* 方案:在新加坡服务器安装Nginx或配置代理
|
||||
* 使得广州可以通过某个可达端口访问到image-studio
|
||||
*/
|
||||
|
||||
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) {
|
||||
const result = await callGatekeeper(server, 'POST', '/exec', { cmd })
|
||||
return result.data
|
||||
}
|
||||
|
||||
async function testGzToSg(gz, port) {
|
||||
const r = await exec(gz, `curl -s --connect-timeout 3 -o /dev/null -w "%{http_code}" http://${SERVERS.sg.ip}:${port}/ 2>&1`)
|
||||
return r.stdout || r.stderr
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const sg = SERVERS.sg
|
||||
const gz = SERVERS.gz
|
||||
|
||||
console.log('═'.repeat(50))
|
||||
console.log('🔧 方案A: 在新加坡服务器上安装Nginx做反向代理')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
// 1. 检查新加坡是否有Nginx
|
||||
console.log('\n📋 检查新加坡服务器环境...')
|
||||
const r1 = await exec(sg, 'which nginx && nginx -v 2>&1 || echo "no nginx"')
|
||||
console.log('nginx:', r1.stdout || r1.stderr)
|
||||
|
||||
// 2. 检查是否已安装socat
|
||||
const r2 = await exec(sg, 'which socat && socat -V 2>&1 | head -1 || echo "no socat"')
|
||||
console.log('socat:', r2.stdout || r2.stderr)
|
||||
|
||||
// 3. 测试广州→新加坡的端口连通性(检查哪些端口是开的)
|
||||
console.log('\n📡 测试广州→新加坡各端口连通性...')
|
||||
for (const port of [80, 443, 3000, 8080, 8084, 3910, 3911, 3912]) {
|
||||
const r = await exec(gz, `timeout 3 bash -c "echo > /dev/tcp/${SERVERS.sg.ip}/${port}" 2>&1 && echo "✅ OPEN" || echo "❌ CLOSED"`)
|
||||
const status = (r.stdout || '').trim()
|
||||
if (status.includes('OPEN')) console.log(` 端口 ${port}: ✅ OPEN`)
|
||||
}
|
||||
|
||||
// 4. 如果没有Nginx,安装Nginx
|
||||
const hasNginx = r1.stdout && !r1.stdout.includes('no nginx')
|
||||
if (!hasNginx) {
|
||||
console.log('\n📥 安装Nginx...')
|
||||
const r3 = await exec(sg, 'apt-get install -y -qq nginx 2>&1 | tail -3')
|
||||
console.log(r3.stdout || r3.stderr)
|
||||
}
|
||||
|
||||
// 5. 配置Nginx反向代理到本地3912
|
||||
const nginxConf = `
|
||||
server {
|
||||
listen 3913;
|
||||
server_name _;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3912;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_buffering off;
|
||||
}
|
||||
|
||||
location /output/ {
|
||||
alias /data/image-studio/output/;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Cache-Control "public, max-age=3600";
|
||||
}
|
||||
}
|
||||
`
|
||||
console.log('\n📝 写入Nginx配置...')
|
||||
const r4 = await exec(sg, `cat > /etc/nginx/sites-available/image-studio << 'NGINX_CONF'
|
||||
${nginxConf}
|
||||
NGINX_CONF
|
||||
cat /etc/nginx/sites-available/image-studio | head -5`)
|
||||
console.log(r4.stdout || r4.stderr)
|
||||
|
||||
// 6. 启用站点
|
||||
await exec(sg, 'ln -sf /etc/nginx/sites-available/image-studio /etc/nginx/sites-enabled/ 2>&1')
|
||||
await exec(sg, 'nginx -t 2>&1')
|
||||
await exec(sg, 'systemctl reload nginx 2>&1 || nginx -s reload 2>&1')
|
||||
|
||||
// 7. 验证
|
||||
console.log('\n✅ 验证Nginx代理...')
|
||||
const r5 = await exec(sg, 'ss -tlnp | grep 3913')
|
||||
console.log('3913监听:', r5.stdout || r5.stderr)
|
||||
const r6 = await exec(sg, 'curl -s http://localhost:3913/ | head -3')
|
||||
console.log('Nginx代理测试:', r6.stdout || r6.stderr)
|
||||
|
||||
// 8. 重新测试广州→新加坡的端口
|
||||
console.log('\n📡 重新测试...')
|
||||
const r7 = await exec(gz, `timeout 3 bash -c "echo > /dev/tcp/${SERVERS.sg.ip}/3913" 2>&1 && echo "✅ OPEN" || echo "❌ CLOSED"`)
|
||||
console.log(`端口 3913: ${(r7.stdout || '').trim()}`)
|
||||
|
||||
// 9. 用curl测试
|
||||
const r8 = await exec(gz, `curl -s --connect-timeout 5 http://${SERVERS.sg.ip}:3913/ | head -3`)
|
||||
console.log('广州→新加坡3913测试:', (r8.stdout || r8.stderr || 'empty').slice(0, 200))
|
||||
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('📋 网络配置完成')
|
||||
console.log('═'.repeat(50))
|
||||
}
|
||||
|
||||
main().catch(err => console.error('❌', err))
|
||||
64
image-studio/deploy/test-gz.mjs
Normal file
64
image-studio/deploy/test-gz.mjs
Normal file
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 正确测试广州Nginx代理
|
||||
*/
|
||||
|
||||
const GZ = { ip: '43.139.217.141', port: 3910, key: 'zy_gtw_4a155446b7acc09fe46a2a29972c0ca5d9954da19c35dc23' }
|
||||
|
||||
async function callGK(srv, method, path, body = null) {
|
||||
const url = `http://${srv.ip}:${srv.port}${path}`
|
||||
const opts = { method, headers: { 'Authorization': `Bearer ${srv.key}`, 'Content-Type': 'application/json' } }
|
||||
if (body) opts.body = JSON.stringify(body)
|
||||
const res = await fetch(url, opts)
|
||||
const text = await res.text()
|
||||
try { return { status: res.status, data: JSON.parse(text) } }
|
||||
catch { return { status: res.status, data: { raw: text } } }
|
||||
}
|
||||
|
||||
async function exec(srv, cmd) {
|
||||
const r = await callGK(srv, 'POST', '/exec', { cmd })
|
||||
return r.data
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('═'.repeat(50))
|
||||
console.log('📡 广州Nginx全链路测试')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
// 1. 测试本地Nginx + 正确Host头
|
||||
console.log('\n1️⃣ 本地Nginx + Host头:')
|
||||
const tests = [
|
||||
['https://localhost/images/ (默认Host)', `curl -s -k --connect-timeout 5 -w "\\nHTTP:%{http_code}" https://localhost/images/ 2>&1 | tail -3`],
|
||||
['https://localhost/images/ (Host:guanghulab.com)', `curl -s -k --connect-timeout 5 -H "Host: guanghulab.com" -w "\\nHTTP:%{http_code}" https://localhost/images/ 2>&1 | tail -3`],
|
||||
['http://localhost/images/ (Host:guanghulab.com)', `curl -s --connect-timeout 5 -H "Host: guanghulab.com" -w "\\nHTTP:%{http_code}" http://localhost/images/ 2>&1 | tail -3`],
|
||||
['https://localhost/ (首页)', `curl -s -k --connect-timeout 5 -H "Host: guanghulab.com" -w "\\nHTTP:%{http_code}" https://localhost/ 2>&1 | tail -3`],
|
||||
]
|
||||
|
||||
for (const [name, cmd] of tests) {
|
||||
const r = await exec(GZ, cmd)
|
||||
console.log(` ${name}:`)
|
||||
console.log(` ${(r.stdout || r.stderr || '').split('\\n').slice(-3).join('\\n ')}`)
|
||||
}
|
||||
|
||||
// 2. 查看Nginx的错误日志
|
||||
console.log('\n2️⃣ Nginx错误日志(最后10行):')
|
||||
const r1 = await exec(GZ, 'tail -10 /var/log/nginx/error.log 2>&1 || echo "no error log"')
|
||||
console.log(' ', (r1.stdout || r1.stderr || '').slice(0, 500))
|
||||
|
||||
// 3. 检查代理的配置是否真的在正确位置
|
||||
console.log('\n3️⃣ 完整SSL server块检查:')
|
||||
const r2 = await exec(GZ, "sed -n '/listen 443 ssl/,/^}$/p' /etc/nginx/sites-enabled/guanghulab | head -60")
|
||||
console.log(r2.stdout || r2.stderr)
|
||||
|
||||
// 4. 也检查一下Nginx的端口监听状态
|
||||
console.log('\n4️⃣ Nginx端口:')
|
||||
const r3 = await exec(GZ, 'ss -tlnp | grep nginx')
|
||||
console.log(r3.stdout || r3.stderr)
|
||||
|
||||
// 5. 直接通过新加坡IP测试广州的代理设置
|
||||
console.log('\n5️⃣ 通过广州访问新加坡/images/:')
|
||||
const SG_IP = '43.156.237.110'
|
||||
const r4 = await exec(GZ, `curl -s --connect-timeout 5 -w "\\nHTTP:%{http_code}" http://${SG_IP}/images/ -H "Host: guanghulab.com" 2>&1 | tail -3`)
|
||||
console.log(' http://SG_IP/images/ (Host:guanghulab.com):', (r4.stdout || '').slice(0, 100))
|
||||
}
|
||||
|
||||
main().catch(err => console.error('❌', err))
|
||||
73
image-studio/deploy/test-network.mjs
Normal file
73
image-studio/deploy/test-network.mjs
Normal file
@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 测试广州→新加坡网络连通性,然后配置Nginx
|
||||
*/
|
||||
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) {
|
||||
const result = await callGatekeeper(server, 'POST', '/exec', { cmd })
|
||||
return result.data
|
||||
}
|
||||
|
||||
async function readFile(server, path) {
|
||||
const result = await callGatekeeper(server, 'POST', '/file/read', { path })
|
||||
if (result.status === 200) return result.data.content
|
||||
return null
|
||||
}
|
||||
|
||||
async function writeFile(server, path, content) {
|
||||
const result = await callGatekeeper(server, 'POST', '/file/write', { path, content })
|
||||
return result.status === 200
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const gz = SERVERS.gz
|
||||
const sg = SERVERS.sg
|
||||
|
||||
// 测试广州→新加坡网络(详细)
|
||||
console.log('🔌 广州→新加坡 网络测试:')
|
||||
const r1 = await exec(gz, `curl -v --connect-timeout 5 http://${sg.ip}:3912/ 2>&1 | head -20`)
|
||||
console.log(r1.stdout || r1.stderr || 'empty')
|
||||
|
||||
// 测试广州→新加坡 Gatekeeper (3911)
|
||||
console.log('\n🔌 广州→新加坡 Gatekeeper 测试:')
|
||||
const r2 = await exec(gz, `curl -s --connect-timeout 5 -X POST http://${sg.ip}:3911/ping 2>&1 | head -5`)
|
||||
console.log(r2.stdout || r2.stderr || 'empty')
|
||||
|
||||
// 如果广州不能直接连新加坡,试试从新加坡创建反向隧道
|
||||
console.log('\n🔍 检查是否需要反向代理方案')
|
||||
|
||||
// 查看新加坡的ufw防火墙
|
||||
const r3 = await exec(sg, 'ufw status 2>&1 || echo "ufw not active"')
|
||||
console.log('新加坡防火墙:', r3.stdout || r3.stderr)
|
||||
|
||||
// 查看广州的防火墙
|
||||
const r4 = await exec(gz, 'ufw status 2>&1 || echo "ufw not active"')
|
||||
console.log('广州防火墙:', r4.stdout || r4.stderr)
|
||||
|
||||
// 检查新加坡3912端口是否监听在0.0.0.0
|
||||
const r5 = await exec(sg, 'ss -tlnp | grep 3912')
|
||||
console.log('新加坡3912监听:', r5.stdout || r5.stderr)
|
||||
|
||||
// 直接测试新加坡本地的3912
|
||||
const r6 = await exec(sg, 'curl -s http://localhost:3912/ | head -3')
|
||||
console.log('新加坡本地测试:', r6.stdout || r6.stderr)
|
||||
}
|
||||
|
||||
main().catch(err => console.error('❌', err))
|
||||
80
image-studio/deploy/verify.mjs
Normal file
80
image-studio/deploy/verify.mjs
Normal file
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 全链路验证
|
||||
*/
|
||||
|
||||
const SG = { ip: '43.156.237.110', port: 3911, key: 'zy_gtw_74d30f54fa8b5581514569ee7874def57861a31ccb2be8c9' }
|
||||
const GZ = { ip: '43.139.217.141', port: 3910, key: 'zy_gtw_4a155446b7acc09fe46a2a29972c0ca5d9954da19c35dc23' }
|
||||
|
||||
async function callGK(srv, method, path, body = null) {
|
||||
const url = `http://${srv.ip}:${srv.port}${path}`
|
||||
const opts = { method, headers: { 'Authorization': `Bearer ${srv.key}`, 'Content-Type': 'application/json' } }
|
||||
if (body) opts.body = JSON.stringify(body)
|
||||
const res = await fetch(url, opts)
|
||||
const text = await res.text()
|
||||
try { return { status: res.status, data: JSON.parse(text) } }
|
||||
catch { return { status: res.status, data: { raw: text } } }
|
||||
}
|
||||
|
||||
async function exec(srv, cmd) {
|
||||
const r = await callGK(srv, 'POST', '/exec', { cmd })
|
||||
return r.data
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('═'.repeat(50))
|
||||
console.log('📡 全链路验证')
|
||||
console.log('═'.repeat(50))
|
||||
|
||||
// 1. 新加坡本地
|
||||
console.log('\n1️⃣ 新加坡本地:')
|
||||
const r1 = await exec(SG, 'curl -s -w "\nHTTP:%{http_code}" http://localhost/images/ 2>&1 | tail -5')
|
||||
console.log(' localhost/images/:', (r1.stdout || '').slice(0, 100))
|
||||
|
||||
// 2. 新加坡外网(通过80端口)
|
||||
console.log('\n2️⃣ 新加坡外网:')
|
||||
const r2 = await exec(SG, `curl -s -w "\nHTTP:%{http_code}" http://${SG.ip}/images/ 2>&1 | tail -5`)
|
||||
console.log(' SG外网/images/:', (r2.stdout || '').slice(0, 100))
|
||||
|
||||
// 3. 广州→新加坡
|
||||
console.log('\n3️⃣ 广州→新加坡:')
|
||||
const r3 = await exec(GZ, `curl -s --connect-timeout 5 -w "\nHTTP:%{http_code}" http://${SG.ip}/images/ 2>&1 | tail -5`)
|
||||
console.log(' GZ→SG/images/:', (r3.stdout || '').slice(0, 100))
|
||||
|
||||
// 4. 广州Nginx本地测试
|
||||
console.log('\n4️⃣ 广州Nginx:')
|
||||
const r4 = await exec(GZ, `curl -s -k --connect-timeout 5 -w "\nHTTP:%{http_code}" https://localhost/images/ 2>&1 | tail -5`)
|
||||
console.log(' localhost/images/:', (r4.stdout || '').slice(0, 100))
|
||||
|
||||
// 5. 新加坡生成一张测试图
|
||||
console.log('\n5️⃣ 生成测试图片:')
|
||||
const r5 = await exec(SG, 'cd /data/image-studio && node generate.js --text "你好冰朔\n这是铸渊在新加坡为你生成的图片\n图片工作室已部署完成" --for "小红书" --output deploy_test 2>&1 | tail -5')
|
||||
console.log(' 生成结果:', r5.stdout || r5.stderr)
|
||||
|
||||
// 6. 验证图片能访问
|
||||
const r6 = await exec(SG, 'ls -la /data/image-studio/output/deploy_test*.png 2>&1')
|
||||
console.log(' 图片文件:', r6.stdout || r6.stderr)
|
||||
|
||||
const r7 = await exec(SG, 'ls /data/image-studio/output/deploy_test*.png 2>&1 | head -1 | xargs -I{} basename {}')
|
||||
const imgName = (r7.stdout || '').trim()
|
||||
if (imgName) {
|
||||
const r8 = await exec(GZ, `curl -s --connect-timeout 5 -o /dev/null -w "HTTP:%{http_code} SIZE:%{size_download}" http://${SG.ip}/images/output/${imgName} 2>&1`)
|
||||
console.log(` 广州→新加坡 图片访问:`, r8.stdout || r8.stderr)
|
||||
|
||||
const r9 = await exec(GZ, `curl -s -k --connect-timeout 5 -o /dev/null -w "HTTP:%{http_code} SIZE:%{size_download}" https://localhost/images/output/${imgName} 2>&1`)
|
||||
console.log(` 广州Nginx→图片:`, r9.stdout || r9.stderr)
|
||||
}
|
||||
|
||||
// 7. 生成API测试
|
||||
console.log('\n6️⃣ API测试:')
|
||||
const r10 = await exec(SG, `curl -s -X POST http://localhost:3912/api/generate -H "Content-Type: application/json" -d '{"text":"测试API","for":"即刻"}' 2>&1 | python3 -m json.tool 2>&1 | head -10`)
|
||||
console.log(' API生成:', r10.stdout || r10.stderr)
|
||||
|
||||
console.log('\n' + '═'.repeat(50))
|
||||
console.log('📋 链路验证完成')
|
||||
console.log('═'.repeat(50))
|
||||
console.log('\n📎 访问入口:')
|
||||
console.log(' 新加坡直连: http://43.156.237.110/images/')
|
||||
console.log(' 广州代理: https://guanghulab.com/images/')
|
||||
}
|
||||
|
||||
main().catch(err => console.error('❌', err))
|
||||
Loading…
x
Reference in New Issue
Block a user