summary refs log tree commit diff
path: root/src/bundle
diff options
context:
space:
mode:
Diffstat (limited to 'src/bundle')
-rw-r--r--src/bundle/Server.ts126
-rw-r--r--src/bundle/index.ts4
-rw-r--r--src/bundle/start.ts96
-rw-r--r--src/bundle/stats.ts43
4 files changed, 269 insertions, 0 deletions
diff --git a/src/bundle/Server.ts b/src/bundle/Server.ts
new file mode 100644

index 00000000..da810d9b --- /dev/null +++ b/src/bundle/Server.ts
@@ -0,0 +1,126 @@ +process.on("unhandledRejection", console.error); +process.on("uncaughtException", console.error); + +import http from "http"; +import * as Api from "@fosscord/api"; +import * as Gateway from "@fosscord/gateway"; +import { CDNServer } from "@fosscord/cdn"; +import express from "express"; +import { green, bold, yellow } from "picocolors"; +import { Config, initDatabase, BannedWords } from "@fosscord/util"; +import * as Sentry from "@sentry/node"; +import * as Tracing from "@sentry/tracing"; + +const app = express(); +const server = http.createServer(); +const port = Number(process.env.PORT) || 3001; +const production = process.env.NODE_ENV == "development" ? false : true; +server.on("request", app); + +const api = new Api.FosscordServer({ server, port, production, app }); +const cdn = new CDNServer({ server, port, production, app }); +const gateway = new Gateway.Server({ server, port, production }); + +//this is what has been added for the /stop API route +process.on('SIGTERM', () => { + server.close(() => { + console.log("Stop API has been successfully POSTed, SIGTERM sent"); + }); +}); +//this is what has been added for the /stop API route + +async function main() { + await initDatabase(); + await Config.init(); + await BannedWords.init(); + // only set endpointPublic, if not already set + await Config.set({ + cdn: { + endpointClient: "${location.host}", + endpointPrivate: `http://localhost:${port}`, + }, + gateway: { + endpointClient: + '${location.protocol === "https:" ? "wss://" : "ws://"}${location.host}', + endpointPrivate: `ws://localhost:${port}`, + ...(!Config.get().gateway.endpointPublic && { + endpointPublic: `ws://localhost:${port}`, + }), + }, + // regions: { + // default: "fosscord", + // useDefaultAsOptimal: true, + // available: [ + // { + // id: "fosscord", + // name: "Fosscord", + // endpoint: "slowcord.maddy.k.vu:3004", + // vip: false, + // custom: false, + // deprecated: false, + // }, + // ], + // }, + } as any); + + //Sentry + if (Config.get().sentry.enabled) { + console.log( + `[Bundle] ${yellow("You are using Sentry! This may slightly impact performance on large loads!")}` + ); + Sentry.init({ + dsn: Config.get().sentry.endpoint, + integrations: [ + new Sentry.Integrations.Http({ tracing: true }), + new Tracing.Integrations.Express({ app }), + new Tracing.Integrations.Mysql(), + ], + tracesSampleRate: Config.get().sentry.traceSampleRate, + environment: Config.get().sentry.environment, + }); + + Sentry.addGlobalEventProcessor((event, hint) => { + if (event.transaction) { + event.transaction = event.transaction.split("/").map(x => !parseInt(x) ? x : ":id").join("/"); + } + + delete event.request?.cookies; + if (event.request?.headers) { + delete event.request.headers["X-Real-Ip"]; + delete event.request.headers["X-Forwarded-For"]; + delete event.request.headers["X-Forwarded-Host"]; + delete event.request.headers["X-Super-Properties"]; + } + + if (event.breadcrumbs) { + event.breadcrumbs = event.breadcrumbs.filter(x => { + if (x.message?.includes("identified as")) return false; + if (x.message?.includes("[WebSocket] closed")) return false; + if (x.message?.includes("Got Resume -> cancel not implemented")) return false; + if (x.message?.includes("[Gateway] New connection from")) return false; + + return true; + }) + } + + return event; + }); + + app.use(Sentry.Handlers.requestHandler()); + app.use(Sentry.Handlers.tracingHandler()); + } + + await Promise.all([api.start(), cdn.start(), gateway.start()]); + + if (Config.get().sentry.enabled) { + app.use(Sentry.Handlers.errorHandler()); + app.use(function onError(err: any, req: any, res: any, next: any) { + res.statusCode = 500; + res.end(res.sentry + "\n"); + }); + } + + console.log(`[Server] ${green(`listening on port ${bold(port)}`)}`); +} + +main().catch(console.error); diff --git a/src/bundle/index.ts b/src/bundle/index.ts new file mode 100644
index 00000000..960d4dc0 --- /dev/null +++ b/src/bundle/index.ts
@@ -0,0 +1,4 @@ +export * from "@fosscord/api"; +export * from "@fosscord/util"; +export * from "@fosscord/gateway"; +export * from "@fosscord/cdn"; \ No newline at end of file diff --git a/src/bundle/start.ts b/src/bundle/start.ts new file mode 100644
index 00000000..2a1e6520 --- /dev/null +++ b/src/bundle/start.ts
@@ -0,0 +1,96 @@ +// process.env.MONGOMS_DEBUG = "true"; +require('module-alias/register'); +import "reflect-metadata"; +import cluster, { Worker } from "cluster"; +import os from "os"; +import { red, bold, yellow, cyan } from "picocolors"; +import { initStats } from "./stats"; +import { config } from "dotenv"; +config(); +import { execSync } from "child_process"; + +// TODO: add socket event transmission +var cores = 1; +try { + cores = Number(process.env.THREADS) || os.cpus().length; +} catch { + console.log("[API] Failed to get thread count! Using 1..."); +} + +if (cluster.isMaster) { + function getCommitOrFail() { + try { + return execSync("git rev-parse HEAD").toString().trim(); + } catch (e) { + return null; + } + } + const commit = getCommitOrFail(); + + console.log( + bold(` +███████╗ ██████╗ ███████╗███████╗ ██████╗ ██████╗ ██████╗ ██████╗ +██╔════╝██╔═══██╗██╔════╝██╔════╝██╔════╝██╔═══██╗██╔══██╗██╔══██╗ +█████╗ ██║ ██║███████╗███████╗██║ ██║ ██║██████╔╝██║ ██║ +██╔══╝ ██║ ██║╚════██║╚════██║██║ ██║ ██║██╔══██╗██║ ██║ +██║ ╚██████╔╝███████║███████║╚██████╗╚██████╔╝██║ ██║██████╔╝ +╚═╝ ╚═════╝ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ + + fosscord-server | ${yellow( + `Pre-release (${commit !== null + ? commit.slice(0, 7) + : "Unknown (Git cannot be found)" + })` + )} + +Commit Hash: ${commit !== null + ? `${cyan(commit)} (${yellow(commit.slice(0, 7))})` + : "Unknown (Git cannot be found)" + } +Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).) +`) + ); + + if (commit == null) { + console.log(yellow(`Warning: Git is not installed or not in PATH.`)); + } + + initStats(); + + console.log(`[Process] starting with ${cores} threads`); + + if (cores === 1) { + require("./Server"); + } else { + process.env.EVENT_TRANSMISSION = "process"; + + // Fork workers. + for (let i = 0; i < cores; i++) { + // Delay each worker start if using sqlite database to prevent locking it + let delay = process.env.DATABASE?.includes("://") ? 0 : i * 1000; + setTimeout(() => { + cluster.fork(); + console.log(`[Process] worker ${cyan(i)} started.`); + }, delay); + } + + cluster.on("message", (sender: Worker, message: any) => { + for (const id in cluster.workers) { + const worker = cluster.workers[id]; + if (worker === sender || !worker) continue; + worker.send(message); + } + }); + + cluster.on("exit", (worker: any, code: any, signal: any) => { + console.log( + `[Worker] ${red( + `died with PID: ${worker.process.pid} , restarting ...` + )}` + ); + cluster.fork(); + }); + } +} else { + require("./Server"); +} diff --git a/src/bundle/stats.ts b/src/bundle/stats.ts new file mode 100644
index 00000000..0234e0b4 --- /dev/null +++ b/src/bundle/stats.ts
@@ -0,0 +1,43 @@ +import os from "os"; +import osu from "node-os-utils"; +import { red } from "picocolors"; + +export function initStats() { + console.log(`[Path] running in ${__dirname}`); + try { + console.log(`[CPU] ${osu.cpu.model()} Cores x${osu.cpu.count()}`); + } + catch { + console.log('[CPU] Failed to get cpu model!') + } + + console.log(`[System] ${os.platform()} ${os.arch()}`); + console.log(`[Process] running with PID: ${process.pid}`); + if (process.getuid && process.getuid() === 0) { + console.warn( + red( + `[Process] Warning fosscord is running as root, this highly discouraged and might expose your system vulnerable to attackers. Please run fosscord as a user without root privileges.` + ) + ); + } + + // TODO: node-os-utils might have a memory leak, more investigation needed + // TODO: doesn't work if spawned with multiple threads + // setInterval(async () => { + // const [cpuUsed, memory, network] = await Promise.all([ + // osu.cpu.usage(), + // osu.mem.info(), + // osu.netstat.inOut(), + // ]); + // var networkUsage = ""; + // if (typeof network === "object") { + // networkUsage = `| [Network]: in ${network.total.inputMb}mb | out ${network.total.outputMb}mb`; + // } + + // console.log( + // `[CPU] ${cpuUsed.toPrecision(3)}% | [Memory] ${Math.round( + // process.memoryUsage().rss / 1024 / 1024 + // )}mb/${memory.totalMemMb.toFixed(0)}mb ${networkUsage}` + // ); + // }, 1000 * 60 * 5); +}