import * as fs from "node:fs"; import { fileURLToPath } from "node:url"; import { dirname } from "node:path"; import * as os from "node:os"; import cluster from "node:cluster"; import net from "node:net"; import repl from "node:repl"; import chalk from "chalk"; import chalkTemplate from "chalk-template"; import semver from "semver"; import Logger from "../services/logger.js"; import loadConfig from "../config/load.js"; import { envOption } from "../env.js"; import { showMachineInfo } from "../misc/show-machine-info.js"; import { db, initDb } from "../db/postgre.js"; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../../built/meta.json`, "utf-8")); const logger = new Logger("core", "cyan"); const bootLogger = logger.createSubLogger("boot", "magenta", false); const themeColor = chalk.hex("#31748f"); function greet() { if (!envOption.quiet) { console.log(chalkTemplate`--- ${os.hostname()} {gray (PID: ${process.pid.toString()})} ---`); } const displayVersion = meta.version.startsWith("v") ? meta.version : `v${meta.version}`; bootLogger.info(`FrozenFriendsYume ${displayVersion}, initializing...`, null, true); } /** * Init master process */ export async function masterMain() { let config; // initialize app try { greet(); showEnvironment(); await showMachineInfo(bootLogger); showNodejsVersion(); config = loadConfigBoot(); await connectDb(); if (config.shellAddress !== undefined) spawnShell(config.shellAddress); } catch (e1) { bootLogger.error("Fatal error occurred during initialization", null, true); bootLogger.error(e1); process.exit(1); } bootLogger.succ("FrozenFriendsYume initialized"); if (!envOption.disableClustering) { await spawnWorkers(config.clusterLimit); } bootLogger.succ(`Now listening on port ${config.port} on ${config.url} - using ${config.domain}`, null, true); if (!envOption.noDaemons && !config.onlyQueueProcessor) { import("../daemons/server-stats.js").then((x)=>x.default()); import("../daemons/queue-stats.js").then((x)=>x.default()); import("../daemons/janitor.js").then((x)=>x.default()); } } function showEnvironment() { const env = process.env.NODE_ENV; const logger = bootLogger.createSubLogger("env"); logger.info(typeof env === "undefined" ? "NODE_ENV is not set" : `NODE_ENV: ${env}`); if (env !== "production") { logger.warn("The environment is not in production mode."); logger.warn("DO NOT USE FOR PRODUCTION PURPOSE!", null, true); } } function showNodejsVersion() { const nodejsLogger = bootLogger.createSubLogger("nodejs"); nodejsLogger.info(`Version ${process.version} detected.`); const minVersion = fs.readFileSync(`${_dirname}/../../../../.node-version`, "utf-8").trim(); if (semver.lt(process.version, minVersion)) { nodejsLogger.error(`At least Node.js ${minVersion} required!`); process.exit(1); } } function loadConfigBoot() { const configLogger = bootLogger.createSubLogger("config"); let config; try { config = loadConfig(); } catch (exception) { if (exception.code === "ENOENT") { configLogger.error("Configuration file not found", null, true); process.exit(1); } else if (e instanceof Error) { configLogger.error(e.message); process.exit(1); } throw exception; } configLogger.succ("Loaded"); return config; } async function connectDb() { const dbLogger = bootLogger.createSubLogger("db"); // Try to connect to DB try { dbLogger.info("Connecting..."); await initDb(); const v = await db.query("SHOW server_version").then((x)=>x[0].server_version); dbLogger.succ(`Connected: v${v}`); } catch (e1) { dbLogger.error("Cannot connect", null, true); dbLogger.error(e1); process.exit(1); } } async function spawnWorkers(limit = 1) { const workers = Math.min(limit, os.cpus().length); bootLogger.info(`Starting ${workers} worker${workers === 1 ? "" : "s"}...`); await Promise.all([ ...Array(workers) ].map(spawnWorker)); bootLogger.succ("All workers started"); } function spawnWorker() { return new Promise((res)=>{ const worker = cluster.fork(); worker.on("message", (message)=>{ if (message === "listenFailed") { bootLogger.error("The server Listen failed due to the previous error."); process.exit(1); } if (message !== "ready") return; res(); }); }); } function spawnShell(address) { logger.info(`Spawning debug shell on ${address}`); if (typeof address == "string") { try { fs.unlinkSync(address); } catch {} } net.createServer().unref().listen(address).on("connection", (socket)=>{ const r = repl.start({ input: socket, output: socket }); socket.on("close", ()=>r.close()); socket.on("error", ()=>r.close()); r.on("close", ()=>socket.destroy()); if (!envOption.disableClustering) { r.defineCommand("worker", (iid)=>{ r.close(); const id = parseInt(iid.trim()); cluster.workers[id].send("debug-shell", socket); }); } }).on("error", (error)=>logger.error(error)); }