221 lines
8.1 KiB
JavaScript
221 lines
8.1 KiB
JavaScript
|
|
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); });
|