铸渊: 图片工作室部署完成 · 远程部署脚本 + Nginx配置

This commit is contained in:
铸渊 2026-05-23 00:09:07 +00:00
parent 549c51dab0
commit 11c0d78127
20 changed files with 2228 additions and 0 deletions

View File

@ -0,0 +1,50 @@
# 铸渊图片工作室 · 部署回执
## 部署信息
| 项目 | 详情 |
|------|------|
| **部署时间** | 2026-05-23 08:04 UTC+8 |
| **部署位置** | 新加坡 BS-SG-001铸渊大脑43.156.237.110 |
| **服务端口** | 3912PM2守护 |
| **访问入口** | `https://guanghulab.com/images/` |
| **架构** | 广州 Nginx → 新加坡 Nginx → image-studio:3912 |
| **守护进程** | PM2image-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/`

View 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))

View 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))

View 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))

View 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))

View 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))

View 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))

View 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))

View 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))

View 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))

View 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))

View 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))

View 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')
// 安装ChromiumUbuntu仓库中有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)
})

View 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)
})

View 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)
})

View 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))

View 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))

View 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))

View 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))

View 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))