import { Client } from "ssh2"; import { readFileSync, readdirSync, statSync } from "fs"; import { join, relative } from "path"; const SSH_CONFIG = { host: "119.45.10.34", port: 22, username: "root", password: "#xyzh%CS#2512@28", readyTimeout: 10000, }; function sshExec(command, timeout = 60000) { return new Promise((resolve, reject) => { const conn = new Client(); let stdout = ""; let stderr = ""; let timer = setTimeout(() => { conn.end(); resolve({ stdout, stderr: stderr + "\n[TIMEOUT]", code: -1 }); }, timeout); conn.on("ready", () => { conn.exec(command, (err, stream) => { if (err) { clearTimeout(timer); conn.end(); return reject(err); } stream.on("close", (code) => { clearTimeout(timer); conn.end(); resolve({ stdout, stderr, code }); }); stream.on("data", (d) => { stdout += d.toString(); }); stream.stderr.on("data", (d) => { stderr += d.toString(); }); }); }).on("error", (err) => { clearTimeout(timer); reject(err); }).connect(SSH_CONFIG); }); } function sshUpload(localPath, remotePath) { return new Promise((resolve, reject) => { const conn = new Client(); conn.on("ready", () => { conn.sftp((err, sftp) => { if (err) { conn.end(); return reject(err); } const content = readFileSync(localPath); const ws = sftp.createWriteStream(remotePath); ws.on("close", () => { conn.end(); resolve(); }); ws.on("error", (e) => { conn.end(); reject(e); }); ws.write(content); ws.end(); }); }).on("error", (err) => reject(err)).connect(SSH_CONFIG); }); } function sshUploadDir(localDir, remoteDir) { return new Promise((resolve, reject) => { const conn = new Client(); conn.on("ready", () => { conn.sftp((err, sftp) => { if (err) { conn.end(); return reject(err); } const files = []; function walkDir(dir, baseDir) { const items = readdirSync(dir); for (const item of items) { const fullPath = join(dir, item); const stat = statSync(fullPath); if (stat.isFile()) { files.push({ local: fullPath, remote: join(remoteDir, relative(baseDir, fullPath)), }); } } } walkDir(localDir, localDir); let uploaded = 0; const total = files.length; if (total === 0) { conn.end(); resolve([]); return; } const results = []; for (const file of files) { const content = readFileSync(file.local); const ws = sftp.createWriteStream(file.remote); ws.on("close", () => { results.push({ local: file.local, remote: file.remote, success: true }); uploaded++; if (uploaded === total) { conn.end(); resolve(results); } }); ws.on("error", (e) => { results.push({ local: file.local, remote: file.remote, success: false, error: e.message }); uploaded++; if (uploaded === total) { conn.end(); resolve(results); } }); ws.write(content); ws.end(); } }); }).on("error", (err) => reject(err)).connect(SSH_CONFIG); }); } const PROJECT = "/www/wwwroot/demo.tensorgrove.com.cn"; const LOCAL_BASE = "C:\\Users\\UI\\Desktop\\bigwo\\test2"; async function main() { console.log("========================================"); console.log(" BigWo Test2 同步部署脚本"); console.log(" 目标服务器: " + SSH_CONFIG.host); console.log(" 项目路径: " + PROJECT); console.log("========================================\n"); const serverFiles = [ { local: "server\\app.js", remote: `${PROJECT}/server/app.js` }, { local: "server\\routes\\chat.js", remote: `${PROJECT}/server/routes/chat.js` }, { local: "server\\routes\\session.js", remote: `${PROJECT}/server/routes/session.js` }, { local: "server\\routes\\voice.js", remote: `${PROJECT}/server/routes/voice.js` }, { local: "server\\services\\arkChatService.js", remote: `${PROJECT}/server/services/arkChatService.js` }, { local: "server\\services\\cozeChatService.js", remote: `${PROJECT}/server/services/cozeChatService.js` }, { local: "server\\services\\toolExecutor.js", remote: `${PROJECT}/server/services/toolExecutor.js` }, { local: "server\\services\\volcengine.js", remote: `${PROJECT}/server/services/volcengine.js` }, { local: "server\\config\\tools.js", remote: `${PROJECT}/server/config/tools.js` }, { local: "server\\config\\voiceChatConfig.js", remote: `${PROJECT}/server/config/voiceChatConfig.js` }, { local: "server\\db\\index.js", remote: `${PROJECT}/server/db/index.js` }, { local: "server\\lib\\token.js", remote: `${PROJECT}/server/lib/token.js` }, ]; const clientFiles = [ { local: "client\\dist\\index.html", remote: `${PROJECT}/client/dist/index.html` }, { local: "client\\dist\\assets\\index-DR-ymgvy.css", remote: `${PROJECT}/client/dist/assets/index-DR-ymgvy.css` }, { local: "client\\dist\\assets\\index-DV4vMa2s.js", remote: `${PROJECT}/client/dist/assets/index-DV4vMa2s.js` }, { local: "client\\dist\\assets\\index.esm.min-C5F81t8Q.js", remote: `${PROJECT}/client/dist/assets/index.esm.min-C5F81t8Q.js` }, ]; console.log("=== 1. 检查服务器状态 ==="); const pm2Check = await sshExec("pm2 list 2>&1 | head -20"); console.log(pm2Check.stdout); console.log("\n=== 2. 创建备份 ==="); const backupDir = `${PROJECT}/_backup_${Date.now()}`; const backupResult = await sshExec(`mkdir -p ${backupDir}/server ${backupDir}/client/dist/assets`); console.log(`备份目录: ${backupDir}`); const backupFiles = [ `${PROJECT}/server/app.js`, `${PROJECT}/server/routes/chat.js`, `${PROJECT}/server/routes/session.js`, `${PROJECT}/server/routes/voice.js`, `${PROJECT}/server/services/arkChatService.js`, `${PROJECT}/server/services/cozeChatService.js`, `${PROJECT}/server/services/toolExecutor.js`, `${PROJECT}/server/services/volcengine.js`, `${PROJECT}/server/config/tools.js`, `${PROJECT}/server/config/voiceChatConfig.js`, `${PROJECT}/server/db/index.js`, `${PROJECT}/server/lib/token.js`, `${PROJECT}/client/dist/index.html`, ]; for (const file of backupFiles) { const backupCmd = `cp ${file} ${backupDir}/ 2>/dev/null || echo "skip"`; await sshExec(backupCmd); } console.log("备份完成"); console.log("\n=== 3. 同步服务端代码 ==="); for (const { local, remote } of serverFiles) { const localPath = join(LOCAL_BASE, local); try { await sshUpload(localPath, remote); console.log(` ✅ ${local} → ${remote}`); } catch (e) { console.error(` ❌ ${local}: ${e.message}`); } } console.log("\n=== 4. 同步前端构建产物 ==="); for (const { local, remote } of clientFiles) { const localPath = join(LOCAL_BASE, local); try { await sshUpload(localPath, remote); console.log(` ✅ ${local} → ${remote}`); } catch (e) { console.error(` ❌ ${local}: ${e.message}`); } } console.log("\n=== 5. 重启 PM2 服务 ==="); const restart = await sshExec("pm2 restart bigwo-server 2>&1"); console.log(restart.stdout); console.log("\n=== 6. 等待服务启动 ==="); await new Promise(r => setTimeout(r, 3000)); console.log("\n=== 7. 健康检查 ==="); const health = await sshExec(`curl -s http://127.0.0.1:3012/api/health 2>&1`); console.log("Health Check:", health.stdout); console.log("\n=== 8. PM2 状态 ==="); const pm2Status = await sshExec("pm2 list 2>&1"); console.log(pm2Status.stdout); console.log("\n=== 9. 最新日志 ==="); const logs = await sshExec("pm2 logs bigwo-server --nostream --lines 20 2>&1"); console.log(logs.stdout); console.log("\n========================================"); console.log(" 部署完成!"); console.log(" 访问: https://demo.tensorgrove.com.cn"); console.log("========================================"); } main().catch(e => { console.error("Fatal:", e.message); process.exit(1); });