From 3bc09f25672f0673ce9d874ff947b5a4d347e673 Mon Sep 17 00:00:00 2001 From: kastov Date: Wed, 2 Sep 2026 22:51:10 +0300 Subject: [PATCH 1/3] chore: update dependencies --- .../commands/handler/add-user.command.ts | 1 - package-lock.json | 26 +++++++++---------- package.json | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/libs/contract/commands/handler/add-user.command.ts b/libs/contract/commands/handler/add-user.command.ts index d36b094..aed52c2 100644 --- a/libs/contract/commands/handler/add-user.command.ts +++ b/libs/contract/commands/handler/add-user.command.ts @@ -6,7 +6,6 @@ export enum CipherType { AES_128_GCM = 5, AES_256_GCM = 6, CHACHA20_POLY1305 = 7, - NONE = 9, UNKNOWN = 0, UNRECOGNIZED = -1, XCHACHA20_POLY1305 = 8, diff --git a/package-lock.json b/package-lock.json index c5436f8..d808d1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "@nestjs/schedule": "^6.1.3", "@remnawave/hashed-set": "^0.0.4", "@remnawave/node-plugins": "0.8.2", - "@remnawave/xtls-sdk": "0.16.0", + "@remnawave/xtls-sdk": "0.17.0", "@remnawave/xtls-sdk-nestjs": "0.6.1", "compression": "^1.8.1", "consola": "^3.4.2", @@ -286,9 +286,9 @@ } }, "node_modules/@bufbuild/protobuf": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.12.1.tgz", - "integrity": "sha512-BvAMfS6LrgZiryOAZ4pBYucu4wG/Ei/9o9DZ9akbREnMLbPJiom2i8b9C8IsKErQoiKqVhrerzt3kOT/RrzLHg==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.14.1.tgz", + "integrity": "sha512-agRJn3+EJDUe8AvxTx/LnHA/GErvLE62pSaSk7+MwFOtOv8eWBu/qCq2qoZjBjVZ3C2aiFJCveuSs17KMkYGOw==", "license": "(Apache-2.0 AND BSD-3-Clause)" }, "node_modules/@colors/colors": { @@ -2546,15 +2546,15 @@ } }, "node_modules/@remnawave/xtls-sdk": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@remnawave/xtls-sdk/-/xtls-sdk-0.16.0.tgz", - "integrity": "sha512-RKu3Ss+2jBuzHp3uqUqRNOqk1mtq54GfcRgeI83SrrbsZsZBZ6aeX0p2B54jVd6fx2BYyunY+1ZlyzNRK6mvIA==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@remnawave/xtls-sdk/-/xtls-sdk-0.17.0.tgz", + "integrity": "sha512-D6pTJ2CqchuttUbF/b1RDU5+8HSv+FURYGqxyrJ2f7qYf5br8WokSiE7uveeYPn5OwG75HQh4ZFCRsWlA1Fs2A==", "license": "AGPL-3.0-only", "dependencies": { - "@bufbuild/protobuf": "^2.12.1", + "@bufbuild/protobuf": "^2.14.1", "long": "^5.3.2", - "nice-grpc": "^2.1.16", - "protobufjs": "^8.6.5" + "nice-grpc": "^2.1.17", + "protobufjs": "^8.8.0" } }, "node_modules/@remnawave/xtls-sdk-nestjs": { @@ -6355,9 +6355,9 @@ } }, "node_modules/protobufjs": { - "version": "8.6.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.6.5.tgz", - "integrity": "sha512-zeE5LPpencAGXvsxyOYmEgJhxzHY8IsmPAFzstZVhDSVT8QH03q6gMZwZRaQGApevZbAL6u28ugs4CC+YKB2jQ==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.8.0.tgz", + "integrity": "sha512-N3xhQ5yyBx3vQq4gubBfASzYhJGNzeDbjqBpu61g7UVylsN/qyffU96TKWD3GbbLOKF82VGNRNvv1+BFgE31Eg==", "license": "BSD-3-Clause", "dependencies": { "long": "^5.3.2" diff --git a/package.json b/package.json index dcb05fe..e8d3936 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "@nestjs/schedule": "^6.1.3", "@remnawave/hashed-set": "^0.0.4", "@remnawave/node-plugins": "0.8.2", - "@remnawave/xtls-sdk": "0.16.0", + "@remnawave/xtls-sdk": "0.17.0", "@remnawave/xtls-sdk-nestjs": "0.6.1", "compression": "^1.8.1", "consola": "^3.4.2", From 50d0898be4360ca535de527b677d233b70da8f35 Mon Sep 17 00:00:00 2001 From: kastov Date: Tue, 6 Oct 2026 03:43:40 +0300 Subject: [PATCH 2/3] feat: add post-start plugin --- package-lock.json | 8 ++-- package.json | 2 +- src/common/utils/send-webhook.ts | 16 ++++++++ src/modules/_plugin/commands/index.ts | 3 +- .../_plugin/commands/run-post-start/index.ts | 2 + .../run-post-start/run-post-start.command.ts | 9 +++++ .../run-post-start/run-post-start.handler.ts | 22 +++++++++++ .../xray-webhook/xray-webhook.handler.ts | 15 +------- .../_plugin/interfaces/plugins.interface.ts | 1 + src/modules/_plugin/plugin.module.ts | 2 + src/modules/_plugin/plugin.service.ts | 13 +++++++ src/modules/_plugin/services/nft.service.ts | 2 + .../_plugin/services/plugin-state.service.ts | 5 ++- .../_plugin/services/post-start.service.ts | 37 +++++++++++++++++++ src/modules/_plugin/services/states/index.ts | 1 + .../services/states/post-start.state.ts | 30 +++++++++++++++ src/modules/xray-core/xray.service.ts | 3 ++ 17 files changed, 151 insertions(+), 20 deletions(-) create mode 100644 src/common/utils/send-webhook.ts create mode 100644 src/modules/_plugin/commands/run-post-start/index.ts create mode 100644 src/modules/_plugin/commands/run-post-start/run-post-start.command.ts create mode 100644 src/modules/_plugin/commands/run-post-start/run-post-start.handler.ts create mode 100644 src/modules/_plugin/services/post-start.service.ts create mode 100644 src/modules/_plugin/services/states/post-start.state.ts diff --git a/package-lock.json b/package-lock.json index d808d1f..cfc8011 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@nestjs/platform-express": "11.2.1", "@nestjs/schedule": "^6.1.3", "@remnawave/hashed-set": "^0.0.4", - "@remnawave/node-plugins": "0.8.2", + "@remnawave/node-plugins": "0.8.3", "@remnawave/xtls-sdk": "0.17.0", "@remnawave/xtls-sdk-nestjs": "0.6.1", "compression": "^1.8.1", @@ -2537,9 +2537,9 @@ "license": "AGPL-3.0-only" }, "node_modules/@remnawave/node-plugins": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@remnawave/node-plugins/-/node-plugins-0.8.2.tgz", - "integrity": "sha512-/klo/XH4imZ2cupLavj4++S+hHgVA8uzhVgpQdC0y9kzUtVE168d7brcYEdxB1UGw49LsER+UDYplcyTSvV5QQ==", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@remnawave/node-plugins/-/node-plugins-0.8.3.tgz", + "integrity": "sha512-GtJv0vOoAarwj4dmVndkrOgA9QzqxiX4Qm4AgMKPk+MmLOQhcOfZSXjGrLstekNkuXgvTZY2KnLBzzR1qGcYKA==", "license": "AGPL-3.0-only", "dependencies": { "zod": "4.5.4" diff --git a/package.json b/package.json index e8d3936..5a8c454 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@nestjs/platform-express": "11.2.1", "@nestjs/schedule": "^6.1.3", "@remnawave/hashed-set": "^0.0.4", - "@remnawave/node-plugins": "0.8.2", + "@remnawave/node-plugins": "0.8.3", "@remnawave/xtls-sdk": "0.17.0", "@remnawave/xtls-sdk-nestjs": "0.6.1", "compression": "^1.8.1", diff --git a/src/common/utils/send-webhook.ts b/src/common/utils/send-webhook.ts new file mode 100644 index 0000000..33857d5 --- /dev/null +++ b/src/common/utils/send-webhook.ts @@ -0,0 +1,16 @@ +const WEBHOOK_TIMEOUT_MS = 5_000; + +export function sendWebhook(url: string, body: unknown, onError?: (error: unknown) => void): void { + fetch(url, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(body), + signal: AbortSignal.timeout(WEBHOOK_TIMEOUT_MS), + }) + .then((response) => { + if (!response.ok) onError?.(new Error(`HTTP ${response.status}`)); + + return response.body?.cancel(); + }) + .catch((error) => onError?.(error)); +} diff --git a/src/modules/_plugin/commands/index.ts b/src/modules/_plugin/commands/index.ts index 0bac247..f0da1e4 100644 --- a/src/modules/_plugin/commands/index.ts +++ b/src/modules/_plugin/commands/index.ts @@ -1,4 +1,5 @@ import { ResetPluginsHandler } from './reset-plugins/reset-plugins.handler'; +import { RunPostStartHandler } from './run-post-start/run-post-start.handler'; import { RunPreStartHandler } from './run-pre-start/run-pre-start.handler'; -export const COMMANDS = [ResetPluginsHandler, RunPreStartHandler]; +export const COMMANDS = [ResetPluginsHandler, RunPreStartHandler, RunPostStartHandler]; diff --git a/src/modules/_plugin/commands/run-post-start/index.ts b/src/modules/_plugin/commands/run-post-start/index.ts new file mode 100644 index 0000000..26c4782 --- /dev/null +++ b/src/modules/_plugin/commands/run-post-start/index.ts @@ -0,0 +1,2 @@ +export * from './run-post-start.command'; +export * from './run-post-start.handler'; diff --git a/src/modules/_plugin/commands/run-post-start/run-post-start.command.ts b/src/modules/_plugin/commands/run-post-start/run-post-start.command.ts new file mode 100644 index 0000000..a24327f --- /dev/null +++ b/src/modules/_plugin/commands/run-post-start/run-post-start.command.ts @@ -0,0 +1,9 @@ +import { Command } from '@nestjs/cqrs'; + +import { TNodeMetadata } from '@libs/contracts/models'; + +export class RunPostStartCommand extends Command { + constructor(public readonly metadata: TNodeMetadata | undefined) { + super(); + } +} diff --git a/src/modules/_plugin/commands/run-post-start/run-post-start.handler.ts b/src/modules/_plugin/commands/run-post-start/run-post-start.handler.ts new file mode 100644 index 0000000..34d51b7 --- /dev/null +++ b/src/modules/_plugin/commands/run-post-start/run-post-start.handler.ts @@ -0,0 +1,22 @@ +import { Logger } from '@nestjs/common'; +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; + +import { PostStartService } from '../../services/post-start.service'; +import { RunPostStartCommand } from './run-post-start.command'; + +@CommandHandler(RunPostStartCommand) +export class RunPostStartHandler implements ICommandHandler { + public readonly logger = new Logger(RunPostStartHandler.name); + + constructor(private readonly postStartService: PostStartService) {} + + async execute(command: RunPostStartCommand) { + try { + this.postStartService.run(command.metadata); + return; + } catch (error) { + this.logger.error(error); + return; + } + } +} diff --git a/src/modules/_plugin/events/xray-webhook/xray-webhook.handler.ts b/src/modules/_plugin/events/xray-webhook/xray-webhook.handler.ts index 44d1ade..e047124 100644 --- a/src/modules/_plugin/events/xray-webhook/xray-webhook.handler.ts +++ b/src/modules/_plugin/events/xray-webhook/xray-webhook.handler.ts @@ -4,6 +4,7 @@ import { Logger } from '@nestjs/common'; import { IEventHandler, EventsHandler } from '@nestjs/cqrs'; import { formatExecutionTime, getTime } from '@common/utils/get-elapsed-time'; +import { sendWebhook } from '@common/utils/send-webhook'; import { TorrentBlockerReportModel, XrayWebhookSchema } from '@libs/contracts/models'; import { NftService } from '../../services/nft.service'; @@ -11,7 +12,6 @@ import { PluginStateService } from '../../services/plugin-state.service'; import { XrayWebhookEvent } from './xray-webhook.event'; const SOURCE_REGEX = /^(?:(?:tcp|udp):)?(?:\[(.+?)\]|(.+?))(?::(\d+))?$/; -const WEBHOOK_TIMEOUT_MS = 5_000; @EventsHandler(XrayWebhookEvent) export class XrayWebhookHandler implements IEventHandler { @@ -80,7 +80,7 @@ export class XrayWebhookHandler implements IEventHandler { const webhookUrl = this.pluginState.torrentBlocker.getWebhookUrl(); if (webhookUrl) { - this.sendWebhook(webhookUrl, report); + sendWebhook(webhookUrl, report); } } catch (error) { this.logger.error(`Error in Event XrayWebhookHandler: ${error}`); @@ -99,15 +99,4 @@ export class XrayWebhookHandler implements IEventHandler { return candidate; } - - private sendWebhook(url: string, report: TorrentBlockerReportModel): void { - fetch(url, { - method: 'POST', - headers: { 'content-type': 'application/json' }, - body: JSON.stringify(report), - signal: AbortSignal.timeout(WEBHOOK_TIMEOUT_MS), - }) - .then((response) => response.body?.cancel()) - .catch(() => void 0); - } } diff --git a/src/modules/_plugin/interfaces/plugins.interface.ts b/src/modules/_plugin/interfaces/plugins.interface.ts index db3decd..5a8c745 100644 --- a/src/modules/_plugin/interfaces/plugins.interface.ts +++ b/src/modules/_plugin/interfaces/plugins.interface.ts @@ -4,4 +4,5 @@ export interface IPlugins { torrentBlocker: boolean; connectionDrop: boolean; preStart: boolean; + postStart: boolean; } diff --git a/src/modules/_plugin/plugin.module.ts b/src/modules/_plugin/plugin.module.ts index 9e70f9f..a0a1737 100644 --- a/src/modules/_plugin/plugin.module.ts +++ b/src/modules/_plugin/plugin.module.ts @@ -8,6 +8,7 @@ import { PluginService } from './plugin.service'; import { QUERIES } from './queries'; import { NftService } from './services/nft.service'; import { PluginStateService } from './services/plugin-state.service'; +import { PostStartService } from './services/post-start.service'; import { PreStartService } from './services/pre-start.service'; @Module({ @@ -18,6 +19,7 @@ import { PreStartService } from './services/pre-start.service'; PluginStateService, NftService, PreStartService, + PostStartService, ...QUERIES, ...EVENTS, ...COMMANDS, diff --git a/src/modules/_plugin/plugin.service.ts b/src/modules/_plugin/plugin.service.ts index 29fe4e0..e3b934f 100644 --- a/src/modules/_plugin/plugin.service.ts +++ b/src/modules/_plugin/plugin.service.ts @@ -90,6 +90,7 @@ export class PluginService { this.syncConnectionDrop(pluginData, sharedMap); this.syncTorrentBlocker(pluginData, sharedMap); this.syncPreStart(pluginData); + this.syncPostStart(pluginData); await this.syncIngressFilter(pluginData, sharedMap); await this.syncEgressFilter(pluginData, sharedMap); @@ -170,6 +171,18 @@ export class PluginService { ); } + private syncPostStart(pluginData: TNodePlugin): void { + if (!pluginData.postStart) return; + if (!pluginData.postStart.enabled) return; + if (!this.state.plugins.postStart) return; + + this.state.postStart.configure(pluginData.postStart); + + const { enabled } = this.state.postStart.webhookConfig; + + this.logger.log(`[PLUGIN] Post-Start: webhook ${enabled ? 'enabled' : 'disabled'}.`); + } + private async syncIngressFilter( pluginData: TNodePlugin, sharedMap: Map, diff --git a/src/modules/_plugin/services/nft.service.ts b/src/modules/_plugin/services/nft.service.ts index fa79018..840e2b3 100644 --- a/src/modules/_plugin/services/nft.service.ts +++ b/src/modules/_plugin/services/nft.service.ts @@ -42,6 +42,7 @@ export class NftService implements OnModuleDestroy, OnModuleInit { torrentBlocker: false, egressFilter: false, preStart: true, + postStart: true, }); try { @@ -66,6 +67,7 @@ export class NftService implements OnModuleDestroy, OnModuleInit { torrentBlocker: true, egressFilter: true, preStart: true, + postStart: true, }); } catch (error) { this.logger.error(error); diff --git a/src/modules/_plugin/services/plugin-state.service.ts b/src/modules/_plugin/services/plugin-state.service.ts index b5f90f7..ba1b4aa 100644 --- a/src/modules/_plugin/services/plugin-state.service.ts +++ b/src/modules/_plugin/services/plugin-state.service.ts @@ -1,13 +1,14 @@ import { Injectable } from '@nestjs/common'; import { IPlugins } from '../interfaces'; -import { TorrentBlockerState, ConnectionDropState, PreStartState } from './states'; +import { TorrentBlockerState, ConnectionDropState, PreStartState, PostStartState } from './states'; @Injectable() export class PluginStateService { public readonly torrentBlocker = new TorrentBlockerState(); public readonly connectionDrop = new ConnectionDropState(); public readonly preStart = new PreStartState(); + public readonly postStart = new PostStartState(); private initialized = false; private lastConfigHash: string | null = null; @@ -18,6 +19,7 @@ export class PluginStateService { torrentBlocker: false, egressFilter: false, preStart: true, + postStart: true, }; private pluginConfigDetails: { uuid: string; name: string } | null = null; @@ -47,6 +49,7 @@ export class PluginStateService { this.torrentBlocker.reset(); this.connectionDrop.reset(); this.preStart.reset(); + this.postStart.reset(); } cleanUpActivePlugin(): void { diff --git a/src/modules/_plugin/services/post-start.service.ts b/src/modules/_plugin/services/post-start.service.ts new file mode 100644 index 0000000..01b6b7b --- /dev/null +++ b/src/modules/_plugin/services/post-start.service.ts @@ -0,0 +1,37 @@ +import { Injectable, Logger } from '@nestjs/common'; + +import { sendWebhook } from '@common/utils/send-webhook'; +import { TNodeMetadata } from '@libs/contracts/models'; + +import { PluginStateService } from './plugin-state.service'; + +@Injectable() +export class PostStartService { + private readonly logger = new Logger(PostStartService.name); + + constructor(private readonly state: PluginStateService) {} + + public run(metadata: TNodeMetadata | undefined): void { + if (!this.state.plugins.postStart) return; + if (!this.state.postStart.isEnabled) return; + + this.sendCoreStartedWebhook(metadata); + } + + private sendCoreStartedWebhook(metadata: TNodeMetadata | undefined): void { + const { enabled, url } = this.state.postStart.webhookConfig; + + if (!enabled) return; + + sendWebhook( + url, + { + scope: 'service', + event: 'service.core_started', + timestamp: new Date().toISOString(), + metadata: metadata ?? {}, + }, + (error) => this.logger.warn(`[POST-START] Webhook failed: ${error}`), + ); + } +} diff --git a/src/modules/_plugin/services/states/index.ts b/src/modules/_plugin/services/states/index.ts index d121cc4..246649e 100644 --- a/src/modules/_plugin/services/states/index.ts +++ b/src/modules/_plugin/services/states/index.ts @@ -1,3 +1,4 @@ export { ConnectionDropState } from './connection-drop.state'; export { TorrentBlockerState } from './torrent-blocker.state'; export { PreStartState } from './pre-start.state'; +export { PostStartState } from './post-start.state'; diff --git a/src/modules/_plugin/services/states/post-start.state.ts b/src/modules/_plugin/services/states/post-start.state.ts new file mode 100644 index 0000000..b2ae183 --- /dev/null +++ b/src/modules/_plugin/services/states/post-start.state.ts @@ -0,0 +1,30 @@ +import { TNodePlugin } from '@remnawave/node-plugins'; + +type TPostStartConfig = NonNullable; +type TPostStartWebhook = NonNullable; + +export class PostStartState { + private enabled = false; + private webhook: TPostStartWebhook = { enabled: false, url: '' }; + + get isEnabled(): boolean { + return this.enabled; + } + + get webhookConfig(): TPostStartWebhook { + return this.webhook; + } + + configure(config: TPostStartConfig): void { + this.enabled = config.enabled; + + this.webhook = config.webhook + ? { enabled: config.webhook.enabled, url: config.webhook.url } + : { enabled: false, url: '' }; + } + + reset(): void { + this.enabled = false; + this.webhook = { enabled: false, url: '' }; + } +} diff --git a/src/modules/xray-core/xray.service.ts b/src/modules/xray-core/xray.service.ts index 3bc58dc..8934f16 100644 --- a/src/modules/xray-core/xray.service.ts +++ b/src/modules/xray-core/xray.service.ts @@ -19,6 +19,7 @@ import { KNOWN_ERRORS } from '@libs/contracts/constants'; import { IntegrationsService } from '@integration-modules/integrations.service'; import { ResetPluginsCommand } from '../_plugin/commands/reset-plugins/reset-plugins.command'; +import { RunPostStartCommand } from '../_plugin/commands/run-post-start/run-post-start.command'; import { RunPreStartCommand } from '../_plugin/commands/run-pre-start/run-pre-start.command'; import { GetTorrentBlockerStateQuery } from '../_plugin/queries/get-torrent-blocker-state'; import { InternalService } from '../internal/internal.service'; @@ -242,6 +243,8 @@ export class XrayService implements OnApplicationBootstrap { this.logger.log(`✔ XRay Core v${this.xrayVersion} is up and running.`); + await this.commandBus.execute(new RunPostStartCommand(body.internals.metadata)); + return ok( new StartXrayResponseModel( isStarted, From 1037b7cf2a6d4a64a79b03a2bbeaaa19e470e669 Mon Sep 17 00:00:00 2001 From: kastov Date: Tue, 6 Oct 2026 03:56:03 +0300 Subject: [PATCH 3/3] chore: bump version to 3.4.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cfc8011..069de3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@remnawave/node", - "version": "3.4.1", + "version": "3.4.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@remnawave/node", - "version": "3.4.1", + "version": "3.4.2", "license": "AGPL-3.0-only", "dependencies": { "@henrygd/semaphore": "0.1.0", diff --git a/package.json b/package.json index 5a8c454..fb21161 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@remnawave/node", - "version": "3.4.1", + "version": "3.4.2", "private": false, "description": "Remnawave Node", "homepage": "https://github.com/remnawave",