summary refs log tree commit diff
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2025-12-16 11:12:31 +0100
committerRory& <root@rory.gay>2025-12-16 11:12:31 +0100
commit9b786925b75be631ed45b39dcdb8fd5826a7bfee (patch)
treef4b4142c217f835df84e2744de1ce9a36761eb29
parentForgot to make rabbitmq static (diff)
downloadserver-ts-9b786925b75be631ed45b39dcdb8fd5826a7bfee.tar.xz
Experimental unix socket mode
-rw-r--r--.idea/runConfigurations/Start_CDN.xml2
-rw-r--r--.idea/workspace.xml96
-rw-r--r--src/gateway/listener/listener.ts4
-rw-r--r--src/util/util/Event.ts150
4 files changed, 205 insertions, 47 deletions
diff --git a/.idea/runConfigurations/Start_CDN.xml b/.idea/runConfigurations/Start_CDN.xml

index 8228c967..90fafd6c 100644 --- a/.idea/runConfigurations/Start_CDN.xml +++ b/.idea/runConfigurations/Start_CDN.xml
@@ -5,7 +5,7 @@ <scripts> <script value="start:cdn" /> </scripts> - <node-interpreter value="/nix/store/dcdc33kdjdhjnzg6rkmd0cx4kpwl8cac-nodejs-20.17.0/bin/node" /> + <node-interpreter value="project" /> <package-manager value="npm" /> <envs /> <EXTENSION ID="com.fapiko.jetbrains.plugins.better_direnv.runconfigs.NodeRunConfiguration"> diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index dfbf2c4a..4582bd98 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml
@@ -24,12 +24,19 @@ <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" /> </component> <component name="HighlightingSettingsPerFile"> - <setting file="file://$USER_HOME$/.cache/JetBrains/WebStorm2024.3/javascript/typings/node/20.17.10/node_modules/@types/node/crypto.d.ts" root0="SKIP_INSPECTION" /> <setting file="file://$PROJECT_DIR$/node_modules/@types/amqplib/index.d.ts" root0="SKIP_INSPECTION" /> + <setting file="file://$PROJECT_DIR$/node_modules/@types/amqplib/properties.d.ts" root0="SKIP_INSPECTION" /> <setting file="file://$PROJECT_DIR$/node_modules/@types/jsonwebtoken/index.d.ts" root0="SKIP_INSPECTION" /> <setting file="file://$PROJECT_DIR$/node_modules/@types/node/events.d.ts" root0="SKIP_INSPECTION" /> - <setting file="file://$PROJECT_DIR$/result/node_modules/lambert-server/src/Server.ts" root0="SKIP_INSPECTION" /> - <setting file="file://$PROJECT_DIR$/result/node_modules/lambert-server/src/Utils.ts" root0="SKIP_INSPECTION" /> + <setting file="file://$PROJECT_DIR$/node_modules/@types/ws/index.d.mts" root0="SKIP_INSPECTION" /> + <setting file="file://$PROJECT_DIR$/node_modules/@types/ws/index.d.ts" root0="SKIP_INSPECTION" /> + <setting file="file://$PROJECT_DIR$/node_modules/@types/ws/package.json" root0="SKIP_INSPECTION" /> + <setting file="file://$PROJECT_DIR$/node_modules/typeorm/data-source/DataSource.d.ts" root0="SKIP_INSPECTION" /> + <setting file="file://$PROJECT_DIR$/node_modules/typeorm/driver/types/ReplicationMode.d.ts" root0="SKIP_INSPECTION" /> + <setting file="file://$PROJECT_DIR$/node_modules/typeorm/query-builder/Brackets.d.ts" root0="SKIP_INSPECTION" /> + <setting file="file://$PROJECT_DIR$/node_modules/typeorm/query-builder/SelectQueryBuilder.d.ts" root0="SKIP_INSPECTION" /> + <setting file="file://$PROJECT_DIR$/node_modules/typeorm/query-builder/WhereExpressionBuilder.d.ts" root0="SKIP_INSPECTION" /> + <setting file="file://$PROJECT_DIR$/node_modules/typeorm/repository/BaseEntity.d.ts" root0="SKIP_INSPECTION" /> </component> <component name="ProblemsViewState"> <option name="selectedTabId" value="ProjectErrors" /> @@ -43,56 +50,56 @@ <option name="hideEmptyMiddlePackages" value="true" /> <option name="showLibraryContents" value="true" /> </component> - <component name="PropertiesComponent"><![CDATA[{ - "keyToString": { - "NIXITCH_NIXPKGS_CONFIG": "/etc/nix/nixpkgs-config.nix", - "NIXITCH_NIX_CONF_DIR": "", - "NIXITCH_NIX_OTHER_STORES": "", - "NIXITCH_NIX_PATH": "/home/Rory/.nix-defexpr/channels:nixpkgs=/nix/store/wb6agba4kfsxpbnb5hzlq58vkjzvbsk6-source", - "NIXITCH_NIX_PROFILES": "/run/current-system/sw /nix/var/nix/profiles/default /etc/profiles/per-user/Rory /home/Rory/.local/state/nix/profile /nix/profile /home/Rory/.nix-profile", - "NIXITCH_NIX_REMOTE": "", - "NIXITCH_NIX_USER_PROFILE_DIR": "/nix/var/nix/profiles/per-user/Rory", - "Node.js.Server.ts.executor": "Debug", - "RunOnceActivity.ShowReadmeOnStart": "true", - "RunOnceActivity.git.unshallow": "true", - "git-widget-placeholder": "master", - "javascript.nodejs.core.library.configured.version": "24.8.0", - "javascript.nodejs.core.library.typings.version": "24.7.0", - "last_opened_file_path": "/home/Rory/git/spacebar/server-master/src/schemas/api/users", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.standard": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.standard": "", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_interpreter_path": "node", - "nodejs_package_manager_path": "npm", - "npm.Start API.executor": "Run", - "npm.Start CDN.executor": "Run", - "npm.Start Gateway.executor": "Run", - "npm.build.executor": "Run", - "npm.start.executor": "Debug", - "prettierjs.PrettierConfiguration.Package": "/home/Rory/git/spacebar/server-master/node_modules/prettier", - "settings.editor.selected.configurable": "com.github.copilot.settings.customInstructions.CopilotInstructionsConfigurable", - "ts.external.directory.path": "/home/Rory/git/spacebar/server-master/node_modules/typescript/lib" + <component name="PropertiesComponent">{ + &quot;keyToString&quot;: { + &quot;NIXITCH_NIXPKGS_CONFIG&quot;: &quot;/etc/nix/nixpkgs-config.nix&quot;, + &quot;NIXITCH_NIX_CONF_DIR&quot;: &quot;&quot;, + &quot;NIXITCH_NIX_OTHER_STORES&quot;: &quot;&quot;, + &quot;NIXITCH_NIX_PATH&quot;: &quot;/home/Rory/.nix-defexpr/channels:nixpkgs=/nix/store/wb6agba4kfsxpbnb5hzlq58vkjzvbsk6-source&quot;, + &quot;NIXITCH_NIX_PROFILES&quot;: &quot;/run/current-system/sw /nix/var/nix/profiles/default /etc/profiles/per-user/Rory /home/Rory/.local/state/nix/profile /nix/profile /home/Rory/.nix-profile&quot;, + &quot;NIXITCH_NIX_REMOTE&quot;: &quot;&quot;, + &quot;NIXITCH_NIX_USER_PROFILE_DIR&quot;: &quot;/nix/var/nix/profiles/per-user/Rory&quot;, + &quot;Node.js.Server.ts.executor&quot;: &quot;Debug&quot;, + &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, + &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;, + &quot;git-widget-placeholder&quot;: &quot;master&quot;, + &quot;javascript.nodejs.core.library.configured.version&quot;: &quot;24.8.0&quot;, + &quot;javascript.nodejs.core.library.typings.version&quot;: &quot;24.7.0&quot;, + &quot;last_opened_file_path&quot;: &quot;/home/Rory/git/spacebar/server-master/src/util/migration/postgres&quot;, + &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, + &quot;node.js.detected.package.standard&quot;: &quot;true&quot;, + &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, + &quot;node.js.selected.package.standard&quot;: &quot;&quot;, + &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, + &quot;nodejs_interpreter_path&quot;: &quot;node&quot;, + &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;, + &quot;npm.Start API.executor&quot;: &quot;Run&quot;, + &quot;npm.Start CDN.executor&quot;: &quot;Run&quot;, + &quot;npm.Start Gateway.executor&quot;: &quot;Run&quot;, + &quot;npm.build.executor&quot;: &quot;Run&quot;, + &quot;npm.start.executor&quot;: &quot;Debug&quot;, + &quot;prettierjs.PrettierConfiguration.Package&quot;: &quot;/home/Rory/git/spacebar/server-master/node_modules/prettier&quot;, + &quot;settings.editor.selected.configurable&quot;: &quot;com.github.copilot.settings.customInstructions.CopilotInstructionsConfigurable&quot;, + &quot;ts.external.directory.path&quot;: &quot;/home/Rory/git/spacebar/server-master/node_modules/typescript/lib&quot; }, - "keyToStringList": { - "DatabaseDriversLRU": [ - "postgresql" + &quot;keyToStringList&quot;: { + &quot;DatabaseDriversLRU&quot;: [ + &quot;postgresql&quot; ], - "GitStage.ChangesTree.GroupingKeys": [ - "directory", - "module", - "repository" + &quot;GitStage.ChangesTree.GroupingKeys&quot;: [ + &quot;directory&quot;, + &quot;module&quot;, + &quot;repository&quot; ] } -}]]></component> +}</component> <component name="RecentsManager"> <key name="CopyFile.RECENT_KEYS"> + <recent name="$PROJECT_DIR$/src/util/migration/postgres" /> <recent name="$PROJECT_DIR$/src/schemas/api/users" /> <recent name="$PROJECT_DIR$/src/util/entities" /> <recent name="$PROJECT_DIR$/src/gateway/opcodes" /> <recent name="$PROJECT_DIR$/src/api/routes/users/@me/billing" /> - <recent name="$PROJECT_DIR$/src/util/util/networking/abuseipdb" /> </key> <key name="MoveFile.RECENT_KEYS"> <recent name="$PROJECT_DIR$/src/schemas/api/guilds" /> @@ -117,6 +124,7 @@ <entry key="Start CDN" value="STOPPED" /> <entry key="Start Gateway" value="STOPPED" /> <entry key="Start bundle" value="STOPPED" /> + <entry key="build" value="STOPPED" /> </map> </value> </entry> @@ -165,6 +173,8 @@ <workItem from="1764432507485" duration="30779000" /> <workItem from="1764521899659" duration="22029000" /> <workItem from="1765142585081" duration="16912000" /> + <workItem from="1765849511651" duration="7921000" /> + <workItem from="1765861713706" duration="18045000" /> </task> <servers /> </component> diff --git a/src/gateway/listener/listener.ts b/src/gateway/listener/listener.ts
index f76adfb2..f7e41f5e 100644 --- a/src/gateway/listener/listener.ts +++ b/src/gateway/listener/listener.ts
@@ -187,9 +187,7 @@ async function consume(this: WebSocket, opts: EventOpts) { } break; case "CHANNEL_CREATE": - if (!permission.overwriteChannel(data.permission_overwrites).has("VIEW_CHANNEL")) { - return; - } + if (!permission.overwriteChannel(data.permission_overwrites).has("VIEW_CHANNEL")) return; this.events[id] = await listenEvent(id, consumer, listenOpts); break; case "RELATIONSHIP_ADD": diff --git a/src/util/util/Event.ts b/src/util/util/Event.ts
index 6f2b0d9a..a1c84143 100644 --- a/src/util/util/Event.ts +++ b/src/util/util/Event.ts
@@ -21,7 +21,14 @@ import { RabbitMQ } from "./RabbitMQ"; import EventEmitter from "events"; import { EVENT, Event } from "../interfaces"; import { randomUUID } from "crypto"; +import path from "path"; +import { Socket } from "node:net"; +import { FSWatcher } from "node:fs"; +import { Stopwatch } from "./Stopwatch"; +import { Config } from "./Config"; export const events = new EventEmitter(); +let unixSocketListener: UnixSocketListener | null = null; +let unixSocketWriter: UnixSocketWriter | null = null; export async function emitEvent(payload: Omit<Event, "created_at">) { const id = (payload.guild_id || payload.channel_id || payload.user_id) as string; @@ -42,6 +49,12 @@ export async function emitEvent(payload: Omit<Event, "created_at">) { // todo: should we retry publishng the event? console.log("[RabbitMQ] ", e); } + } else if (process.env.EVENT_TRANSMISSION === "unix" && process.env.EVENT_SOCKET_PATH) { + if (!unixSocketWriter) { + unixSocketWriter = new UnixSocketWriter(process.env.EVENT_SOCKET_PATH); + await unixSocketWriter.init(); + } + await unixSocketWriter.emit(payload); } else if (process.env.EVENT_TRANSMISSION === "process") { process.send?.({ type: "event", event: payload, id } as ProcessEvent); } else { @@ -57,6 +70,14 @@ export async function initEvent() { // use event emitter // use process messages } + + await listenEvent("spacebar", async (event) => { + console.log("[Event] Received spacebar event:", event); + if ((event.event as string) === "SB_RELOAD_CONFIG") { + console.log("[Event] Reloading config due to RELOAD_CONFIG event"); + await Config.init(true); + } + }); } export interface EventOpts extends Event { @@ -84,6 +105,12 @@ export async function listenEvent(event: string, callback: (event: EventOpts) => return await rabbitListen(channel, event, callback, { acknowledge: opts?.acknowledge, }); + } else if (process.env.EVENT_TRANSMISSION === "unix" && process.env.EVENT_SOCKET_PATH) { + if (!unixSocketListener) { + unixSocketListener = new UnixSocketListener(path.join(process.env.EVENT_SOCKET_PATH, `${process.pid}.sock`)); + await unixSocketListener.init(); + } + return await unixSocketListener.listen(event, callback); } else if (process.env.EVENT_TRANSMISSION === "process") { const cancel = async () => { process.removeListener("message", listener); @@ -155,3 +182,126 @@ async function rabbitListen(channel: Channel, id: string, callback: (event: Even return cancel; } + +class UnixSocketListener { + eventEmitter: EventEmitter; + socketPath: string; + + constructor(socketPath: string) { + this.eventEmitter = new EventEmitter(); + this.socketPath = socketPath; + } + + async init() { + const net = await import("net"); + const server = net.createServer((socket) => { + socket.on("connect", () => { + console.log("[Events] Unix socket client connected"); + }); + socket.on("data", (data) => { + try { + const payload = JSON.parse(data.toString()); + this.eventEmitter.emit(payload.id, payload.event); + } catch (e) { + console.error("[Events] Failed to parse unix socket data:", e); + } + }); + }); + + server.listen(this.socketPath, () => { + console.log(`Unix socket server listening on ${this.socketPath}`); + }); + + const shutdown = () => { + console.log("[Events] Closing unix socket server"); + server.close(); + process.exit(0); + }; + for (const sig of ["SIGINT", "SIGTERM", "SIGQUIT"] as const) { + process.on(sig, shutdown); + } + } + + async listen(event: string, callback: (event: EventOpts) => unknown): Promise<() => Promise<void>> { + const listener = (data: any) => { + callback({ + ...data, + cancel, + }); + }; + + this.eventEmitter.addListener(event, listener); + + const cancel = async () => { + this.eventEmitter.removeListener(event, listener); + this.eventEmitter.setMaxListeners(this.eventEmitter.getMaxListeners() - 1); + }; + + this.eventEmitter.setMaxListeners(this.eventEmitter.getMaxListeners() + 1); + + return cancel; + } +} + +class UnixSocketWriter { + socketPath: string; + clients: { [key: string]: Socket } = {}; + watcher: FSWatcher; + + constructor(socketPath: string) { + this.socketPath = socketPath; + } + + async init() { + const net = await import("net"); + const fs = await import("fs"); + + if (!fs.opendirSync(this.socketPath)) throw new Error("Unix socket path does not exist or is not a directory: " + this.socketPath); + + console.log("[Events] Unix socket writer initializing for", this.socketPath); + + const connect = (file: string) => { + const fullPath = path.join(this.socketPath, file); + this.clients[fullPath] = net.createConnection(fullPath, () => { + console.log("[Events] Unix socket client connected to", fullPath); + }); + }; + + // connect to all sockets, now and in the future + this.watcher = fs.watch(this.socketPath, {}, (eventType, filename) => { + console.log("[Events] Unix socket writer received watch sig", eventType, filename); + connect(filename!); + }); + + // connect to existing sockets if any + fs.readdir(this.socketPath, (err, files) => { + if (err) return console.error("[Events] Unix socket writer failed to read directory:", err); + + console.log("[Events] Unix socket writer found existing sockets:", files); + files.forEach((file) => { + connect(file); + }); + }); + } + + async emit(event: Event) { + if (!this.clients) throw new Error("UnixSocketWriter not initialized"); + + const tsw = Stopwatch.startNew(); + const payload = JSON.stringify({ id: (event.guild_id || event.channel_id || event.user_id) as string, event }); + for (const socket of Object.entries(this.clients)) { + if(socket[1].destroyed) { + console.log("[Events] Unix socket writer found destroyed socket, removing:", socket[0]); + delete this.clients[socket[0]]; + continue; + } + try { + socket[1].write(payload); + } catch (e) { + console.error("[Events] Unix socket writer failed to write to socket", socket[0], ":", e); + } + } + + console.log(`[Events] Unix socket writer emitted to ${Object.entries(this.clients).length} sockets in ${tsw.elapsed().totalMilliseconds}ms`); + } +}