190 lines
6.6 KiB
JavaScript
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();
|