Lumi/src/main.js
2026-07-22 11:01:49 +02:00

190 lines
6.6 KiB
JavaScript

const { migrate } = require("./services/db");
const { ensureDefaults, getSetting, setSetting } = require("./services/settings");
const { createWebServer } = require("./web/server");
const { startBot, stopBot } = require("./services/discord");
const { startTwitchBot, stopTwitchBot } = require("./services/twitch");
const { startYouTubeBot, stopYouTubeBot } = require("./services/youtube");
const pluginService = require("./services/plugins");
const { loadEnabled } = pluginService;
const stopPlugins =
typeof pluginService.stopPlugins === "function"
? pluginService.stopPlugins
: async () => {};
const { checkForUpdates, completePendingBranchDeployment, pullUpdates, registerRestartHandler, requestRestart } = require("./services/updater");
const { createCommandRouter } = require("./services/command-router");
const { registerTopCommand } = require("./services/top");
const logger = require("./services/logger");
const { isPlatformEnabled } = require("./services/platforms");
const { registerCorePlaceholders } = require("./services/placeholders");
const { overlayConnectorManager } = require("./services/overlay-connectors");
const { cleanupSnapshots } = require("./services/update-manager");
const { twitchEventSubManager } = require("./services/twitch-eventsub");
const {
isSafeModeRequested,
markStartupVerification
} = require("./services/recovery-mode");
async function main() {
migrate();
ensureDefaults();
const configuredRemote = getSetting("git_remote", "origin");
const configuredBranch = getSetting("git_branch", "main");
const completedDeployment = await completePendingBranchDeployment(configuredRemote, configuredBranch);
if (completedDeployment) {
console.log(`Completed deployment of ${completedDeployment.deployment.branch}; restarting with synchronized core and plugin code.`);
requestRestart({ delayMs: 0 });
return;
}
registerCorePlaceholders();
logger.hookConsole();
const runtimeLog = logger.createLogger("core:runtime", { category: "lifecycle" });
const logCleanup = logger.cleanupLogs({
maxAgeDays: getSetting("log_retention_days", logger.DEFAULT_MAX_AGE_DAYS),
maxEntries: getSetting("log_retention_max_entries", logger.DEFAULT_MAX_ENTRIES)
});
runtimeLog.info("Lumi startup initiated", {
version: require("../package.json").version,
node: process.version,
platform: process.platform,
log_entries_removed: logCleanup.removed
}, { event: "startup" });
try {
cleanupSnapshots();
} catch (error) {
console.warn(`Snapshot cleanup could not complete: ${error.message}`);
}
const safeModeRequested = isSafeModeRequested();
const startupMarker = markStartupVerification();
if (startupMarker?.status === "stale") {
console.warn("Recovery marker detected from incomplete update; start with LUMI_SAFE_MODE=1 for recovery tools.");
}
const settingsApi = { getSetting, setSetting };
const commandRouter = createCommandRouter({ settings: settingsApi });
registerTopCommand({ commandRouter, settings: settingsApi });
let discordClient = null;
let twitchClient = null;
let youtubeClient = null;
if (!safeModeRequested && isPlatformEnabled("discord")) {
try {
discordClient = await startBot({ commandRouter });
} catch (error) {
console.error("Discord bot failed to start", error);
}
}
if (!safeModeRequested && isPlatformEnabled("twitch")) {
try {
twitchClient = await startTwitchBot({ commandRouter });
} catch (error) {
console.error("Twitch bot failed to start", error);
}
try {
await twitchEventSubManager.start();
} catch (error) {
console.error("Twitch event alerts failed to start", error);
}
}
if (!safeModeRequested && isPlatformEnabled("youtube")) {
try {
youtubeClient = await startYouTubeBot({ commandRouter });
} catch (error) {
console.error("YouTube bot failed to start", error);
}
}
const app = createWebServer({
discordClient,
commandRouter,
loadPlugins: (appInstance, web, webhooks) => {
if (safeModeRequested) return;
loadEnabled({
app: appInstance,
discordClient,
twitchClient,
youtubeClient,
settings: settingsApi,
web,
webhooks,
commandRouter
});
}
});
if (!safeModeRequested) {
overlayConnectorManager.start().catch((error) => {
console.error("OBS overlay connectors failed to start", error);
});
}
const port = Number(process.env.PORT || 3000);
const webServer = app.listen(port, () => {
runtimeLog.run({ event: "web_ready" }, () => {
console.log(`WebUI listening on http://localhost:${port}`, { port });
});
});
app.locals.lumiUpgradeRegistry?.attach(webServer);
const autoUpdateEnabled = getSetting("auto_update_enabled", false);
const intervalMinutes = getSetting("auto_update_interval_minutes", 60);
if (!safeModeRequested && autoUpdateEnabled) {
const intervalMs = Math.max(5, Number(intervalMinutes)) * 60 * 1000;
let autoUpdateRunning = false;
setInterval(async () => {
if (autoUpdateRunning) return;
autoUpdateRunning = true;
try {
const remote = getSetting("git_remote", "origin");
const branch = getSetting("git_branch", "main");
if (checkForUpdates(remote, branch)) {
await pullUpdates(remote, branch);
requestRestart();
}
} catch (error) {
console.error("Auto-update failed", error);
} finally {
autoUpdateRunning = false;
}
}, intervalMs);
}
let shuttingDown = false;
const shutdown = async (exitCode = 0) => {
if (shuttingDown) {
return;
}
shuttingDown = true;
app.locals.lumiUpgradeRegistry?.close();
runtimeLog.info("Lumi shutdown started", { exit_code: exitCode }, { event: exitCode === 10 ? "restart" : "shutdown" });
const closeWebServer = new Promise((resolve) => webServer.close(resolve));
for (const stop of [
() => overlayConnectorManager.stop(),
() => twitchEventSubManager.stop(),
() => stopPlugins(),
() => stopBot(),
() => stopTwitchBot(),
() => stopYouTubeBot()
]) {
try {
await stop();
} catch (error) {
runtimeLog.warn("A service did not stop cleanly", { error: error.message }, { event: "shutdown_service_error" });
}
}
await Promise.race([
closeWebServer,
new Promise((resolve) => setTimeout(resolve, 2500))
]);
webServer.closeAllConnections?.();
process.exit(Number(exitCode) === 10 ? 10 : 0);
};
registerRestartHandler(() => shutdown(10));
process.on("SIGINT", () => shutdown(0));
process.on("SIGTERM", () => shutdown(0));
}
main();