Files
telegram-bot/src/migrate.js
T

136 lines
5.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
// 從 telegram-codex-bot0.1.x)升級:
// 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');
function rewritePrefix(p, from, to) {
if (typeof p !== 'string') return p;
const norm = path.resolve(p);
const fromNorm = path.resolve(from);
if (norm.toLowerCase().startsWith(fromNorm.toLowerCase())) return path.join(to, norm.slice(fromNorm.length));
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(() => []);
const isOnline = (p) => p.pm2_env?.status === 'online';
// 1) 家目錄搬家
if (fs.existsSync(pm2.LEGACY_HOME) && !fs.existsSync(pm2.NEW_HOME)) {
// 舊控制台的 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 || !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(() => {});
}
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 裡的設定檔路徑改寫
const regFile = path.join(pm2.NEW_HOME, 'registry.json');
try {
const reg = JSON.parse(fs.readFileSync(regFile, 'utf-8'));
for (const entry of Object.values(reg.bots || {})) {
entry.configPath = rewritePrefix(entry.configPath, pm2.LEGACY_HOME, pm2.NEW_HOME);
}
fs.writeFileSync(regFile, JSON.stringify(reg, null, 2) + '\n');
} catch { /* 沒有 registry 就算了 */ }
await restartBots(affected, notes, '已用新路徑重新啟動');
list = await pm2.jlist().catch(() => []);
}
// 2) 還跑在舊套件(或舊安裝路徑)上的 bot:重新掛到目前套件的 index.js
const { loadConfig } = require('./config');
for (const p of list) {
const cfgPath = pm2.botConfigPathOf(p);
if (!cfgPath) continue;
const script = p.pm2_env?.pm_exec_path || '';
if (path.resolve(script) === path.resolve(pm2.BOT_SCRIPT)) continue;
if (!fs.existsSync(cfgPath)) continue;
if (!isOnline(p)) {
await pm2.pm2Exec(`delete ${JSON.stringify(p.name)}`).catch(() => {});
notes.push(`⏸ ${p.name} 原本是停止狀態(舊套件),已從 pm2 移除,可在控制台重新啟動`);
continue;
}
try {
await pm2.startBotOnPm2(loadConfig(cfgPath));
notes.push(`🔄 ${p.name} 已從舊套件切換到 telegram-bot 並重新啟動`);
} catch (err) {
notes.push(`⚠️ ${p.name} 切換失敗:${err.message}`);
}
}
// 3) 舊控制台程序由 ensureConsoleOnPm2 處理(會刪掉 tgcodex-console 再掛 tgbot-console
return notes;
}
module.exports = { migrateLegacy };