chore: release v3.4.2

This commit is contained in:
kastov 2026-10-06 04:01:49 +03:00
commit ea7a835485
No known key found for this signature in database
GPG key ID: 1B27BE29057F4C90
18 changed files with 168 additions and 38 deletions

View file

@ -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,

38
package-lock.json generated
View file

@ -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",
@ -20,8 +20,8 @@
"@nestjs/platform-express": "11.2.1",
"@nestjs/schedule": "^6.1.3",
"@remnawave/hashed-set": "^0.0.4",
"@remnawave/node-plugins": "0.8.2",
"@remnawave/xtls-sdk": "0.16.0",
"@remnawave/node-plugins": "0.8.3",
"@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": {
@ -2537,24 +2537,24 @@
"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"
}
},
"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"

View file

@ -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",
@ -42,8 +42,8 @@
"@nestjs/platform-express": "11.2.1",
"@nestjs/schedule": "^6.1.3",
"@remnawave/hashed-set": "^0.0.4",
"@remnawave/node-plugins": "0.8.2",
"@remnawave/xtls-sdk": "0.16.0",
"@remnawave/node-plugins": "0.8.3",
"@remnawave/xtls-sdk": "0.17.0",
"@remnawave/xtls-sdk-nestjs": "0.6.1",
"compression": "^1.8.1",
"consola": "^3.4.2",

View file

@ -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));
}

View file

@ -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];

View file

@ -0,0 +1,2 @@
export * from './run-post-start.command';
export * from './run-post-start.handler';

View file

@ -0,0 +1,9 @@
import { Command } from '@nestjs/cqrs';
import { TNodeMetadata } from '@libs/contracts/models';
export class RunPostStartCommand extends Command<void> {
constructor(public readonly metadata: TNodeMetadata | undefined) {
super();
}
}

View file

@ -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<RunPostStartCommand> {
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;
}
}
}

View file

@ -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<XrayWebhookEvent> {
@ -80,7 +80,7 @@ export class XrayWebhookHandler implements IEventHandler<XrayWebhookEvent> {
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<XrayWebhookEvent> {
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);
}
}

View file

@ -4,4 +4,5 @@ export interface IPlugins {
torrentBlocker: boolean;
connectionDrop: boolean;
preStart: boolean;
postStart: boolean;
}

View file

@ -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,

View file

@ -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<string, string[]>,

View file

@ -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);

View file

@ -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 {

View file

@ -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}`),
);
}
}

View file

@ -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';

View file

@ -0,0 +1,30 @@
import { TNodePlugin } from '@remnawave/node-plugins';
type TPostStartConfig = NonNullable<TNodePlugin['postStart']>;
type TPostStartWebhook = NonNullable<TPostStartConfig['webhook']>;
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: '' };
}
}

View file

@ -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,