From 573d83fd2556a67b987885258aa4738802707c82 Mon Sep 17 00:00:00 2001 From: JianMiau Date: Mon, 24 Aug 2026 11:46:11 +0800 Subject: [PATCH] =?UTF-8?q?=E9=81=B7=E7=A7=BB=E4=BF=AE=E6=AD=A3=EF=BC=9A?= =?UTF-8?q?=E5=85=88=E7=A7=BB=E9=99=A4=E8=88=8A=E6=8E=A7=E5=88=B6=E5=8F=B0?= =?UTF-8?q?=E5=86=8D=E6=90=AC=E7=9B=AE=E9=8C=84=EF=BC=88Windows=20EBUSY?= =?UTF-8?q?=EF=BC=89=EF=BC=8Crename=20=E5=A4=B1=E6=95=97=E6=94=B9=E8=A4=87?= =?UTF-8?q?=E8=A3=BD=EF=BC=8C=E5=A4=B1=E6=95=97=E6=99=82=20bot=20=E7=94=A8?= =?UTF-8?q?=E5=8E=9F=E8=B7=AF=E5=BE=91=E5=BE=A9=E5=8E=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/migrate.js | 82 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 17 deletions(-) diff --git a/src/migrate.js b/src/migrate.js index 259bc5c..52a3d65 100644 --- a/src/migrate.js +++ b/src/migrate.js @@ -3,7 +3,7 @@ // 1) ~/.tgcodex → ~/.tgbot(設定、registry、會話全部搬過去,registry 裡的路徑一併改寫) // 2) 跑在舊套件路徑(telegram-codex-bot/src/index.js)上的 bot 程序重新掛到新套件 // 3) 舊控制台程序 tgcodex-console 移除(ensureConsoleOnPm2 會掛新的 tgbot-console) -// 每一步都是冪等的:沒有舊東西就什麼都不做。 +// 每一步都是冪等的:沒有舊東西就什麼都不做。搬家途中任何失敗都會把 bot 用原路徑拉回來,不留下停機狀態。 const fs = require('fs'); const path = require('path'); const pm2 = require('./pm2util'); @@ -16,6 +16,47 @@ function rewritePrefix(p, from, to) { return p; } +function underDir(p, dir) { + return path.resolve(p).toLowerCase().startsWith(path.resolve(dir).toLowerCase()); +} + +const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); + +// Windows 下剛被 pm2 刪掉的程序可能還握著檔案幾百毫秒;rename 失敗就等一下重試, +// 真的搬不動就整份複製過去(舊目錄留著,之後可手動刪) +async function moveDir(from, to, notes) { + let lastErr = null; + for (let i = 0; i < 6; i++) { + try { + fs.renameSync(from, to); + return; + } catch (err) { + lastErr = err; + if (!/EBUSY|EPERM|EACCES/.test(err.code || '')) throw err; + await sleep(500 * (i + 1)); + } + } + fs.cpSync(from, to, { recursive: true }); + try { + fs.rmSync(from, { recursive: true, force: true }); + } catch { + notes.push(`⚠️ 舊目錄 ${from} 被其他程序鎖住(${lastErr?.code}),已複製到新位置,舊目錄請之後手動刪除`); + } +} + +async function restartBots(bots, notes, label) { + const { loadConfig } = require('./config'); + for (const b of bots) { + if (!b.online) { notes.push(`⏸ ${b.name} 原本是停止狀態,已從 pm2 移除,可在控制台重新啟動`); continue; } + try { + await pm2.startBotOnPm2(loadConfig(b.cfgPath)); + notes.push(`🔄 ${b.name} ${label}`); + } catch (err) { + notes.push(`⚠️ ${b.name} 重新啟動失敗:${err.message}(請在控制台手動啟動)`); + } + } +} + async function migrateLegacy() { const notes = []; let list = await pm2.jlist().catch(() => []); @@ -23,16 +64,33 @@ async function migrateLegacy() { // 1) 家目錄搬家 if (fs.existsSync(pm2.LEGACY_HOME) && !fs.existsSync(pm2.NEW_HOME)) { - // 先把跑在舊家目錄上的 bot 從 pm2 拿掉(記住哪些在線上,搬完再啟) + // 舊控制台的 cwd 與 log 都在舊目錄裡,會把目錄鎖住(Windows EBUSY),先拿掉 + if (list.some((p) => p.name === pm2.LEGACY_CONSOLE_NAME)) { + await pm2.pm2Exec(`delete ${JSON.stringify(pm2.LEGACY_CONSOLE_NAME)}`).catch(() => {}); + notes.push(`🗑 已移除舊控制台程序 ${pm2.LEGACY_CONSOLE_NAME}`); + } + // 跑在舊家目錄上的 bot 也先從 pm2 拿掉(記住哪些在線上,搬完再啟) const affected = []; for (const p of list) { const cfgPath = pm2.botConfigPathOf(p); - if (!cfgPath) continue; - if (!path.resolve(cfgPath).toLowerCase().startsWith(path.resolve(pm2.LEGACY_HOME).toLowerCase())) continue; - affected.push({ name: p.name, online: isOnline(p), cfgPath: rewritePrefix(cfgPath, pm2.LEGACY_HOME, pm2.NEW_HOME) }); + if (!cfgPath || !underDir(cfgPath, pm2.LEGACY_HOME)) continue; + affected.push({ + name: p.name, + online: isOnline(p), + oldCfgPath: cfgPath, + cfgPath: rewritePrefix(cfgPath, pm2.LEGACY_HOME, pm2.NEW_HOME), + }); await pm2.pm2Exec(`delete ${JSON.stringify(p.name)}`).catch(() => {}); } - fs.renameSync(pm2.LEGACY_HOME, pm2.NEW_HOME); + + try { + await moveDir(pm2.LEGACY_HOME, pm2.NEW_HOME, notes); + } catch (err) { + // 搬不動:bot 照原路徑拉回來,控制台由後續 ensureConsoleOnPm2 掛(會沿用舊目錄) + notes.push(`❌ 設定目錄搬家失敗(${err.message}),維持使用 ${pm2.LEGACY_HOME}`); + await restartBots(affected.map((b) => ({ ...b, cfgPath: b.oldCfgPath })), notes, '已用原路徑重新啟動'); + return notes; + } notes.push(`📦 設定目錄已搬到 ${pm2.NEW_HOME}(原 ${pm2.LEGACY_HOME})`); // registry 裡的設定檔路徑改寫 @@ -45,17 +103,7 @@ async function migrateLegacy() { fs.writeFileSync(regFile, JSON.stringify(reg, null, 2) + '\n'); } catch { /* 沒有 registry 就算了 */ } - // 之前在線上的 bot 用新路徑重新掛回去 - const { loadConfig } = require('./config'); - for (const b of affected) { - if (!b.online) { notes.push(`⏸ ${b.name} 原本是停止狀態,已從 pm2 移除,可在控制台重新啟動`); continue; } - try { - await pm2.startBotOnPm2(loadConfig(b.cfgPath)); - notes.push(`🔄 ${b.name} 已用新路徑重新啟動`); - } catch (err) { - notes.push(`⚠️ ${b.name} 重新啟動失敗:${err.message}(請在控制台手動啟動)`); - } - } + await restartBots(affected, notes, '已用新路徑重新啟動'); list = await pm2.jlist().catch(() => []); }