chore: update @nestjs packages and other dependencies; refactor torrent blocker state management and API config generation

This commit is contained in:
kastov 2026-03-08 18:48:42 +03:00
parent 739fe84c6e
commit 9d3319216c
No known key found for this signature in database
GPG key ID: 1B27BE29057F4C90
8 changed files with 120 additions and 52 deletions

40
package-lock.json generated
View file

@ -12,17 +12,17 @@
"@henrygd/semaphore": "0.1.0",
"@kastov/body-parser-with-zstd": "2.2.2",
"@kastov/node-supervisord": ">=2.0.3",
"@nestjs/common": "11.1.15",
"@nestjs/common": "11.1.16",
"@nestjs/config": "4.0.3",
"@nestjs/core": "11.1.15",
"@nestjs/core": "11.1.16",
"@nestjs/cqrs": "11.0.3",
"@nestjs/jwt": "11.0.2",
"@nestjs/passport": "11.0.5",
"@nestjs/platform-express": "11.1.15",
"@nestjs/platform-express": "11.1.16",
"@peculiar/webcrypto": "^1.5.0",
"@peculiar/x509": "^1.14.3",
"@remnawave/hashed-set": "^0.0.4",
"@remnawave/node-plugins": "0.1.1",
"@remnawave/node-plugins": "0.1.3",
"@remnawave/supervisord-nestjs": "0.3.1",
"@remnawave/xtls-sdk": "0.11.1",
"@remnawave/xtls-sdk-nestjs": "0.6.1",
@ -1207,9 +1207,9 @@
}
},
"node_modules/@nestjs/common": {
"version": "11.1.15",
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.15.tgz",
"integrity": "sha512-51sqIj83xEhmxi3D+QA+bo+hs+YTnZ5eJvOxa34VFiSlilT8DEF83kZc4t1t3arO8PjfDFI+LaTQQkx0gm1QGA==",
"version": "11.1.16",
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.16.tgz",
"integrity": "sha512-JSIeW+USuMJkkcNbiOdcPkVCeI3TSnXstIVEPpp3HiaKnPRuSbUUKm9TY9o/XpIcPHWUOQItAtC5BiAwFdVITQ==",
"license": "MIT",
"dependencies": {
"file-type": "21.3.0",
@ -1253,9 +1253,9 @@
}
},
"node_modules/@nestjs/core": {
"version": "11.1.15",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.15.tgz",
"integrity": "sha512-s+SQxA8yAGu8RapbI5Y3GFuNyvF1ocva2Va+d4K5T7G0eC5kAvYfiHr8iKvZg+rue5NjV7fYQQmEvA5jCTG+CA==",
"version": "11.1.16",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.16.tgz",
"integrity": "sha512-tXWXyCiqWthelJjrE0KLFjf0O98VEt+WPVx5CrqCf+059kIxJ8y1Vw7Cy7N4fwQafWNrmFL2AfN87DDMbVAY0w==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
@ -1329,14 +1329,14 @@
}
},
"node_modules/@nestjs/platform-express": {
"version": "11.1.15",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.15.tgz",
"integrity": "sha512-sR42dQ5fPy4NGbnhT4mRIJLL0h2eNY4KpLkcfd27vg5XdTjQQ07wkQORrw6rAkWkBvMRr0dmhAcUBnXpe7ro5Q==",
"version": "11.1.16",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.16.tgz",
"integrity": "sha512-IOegr5+ZfUiMKgk+garsSU4MOkPRhm46e6w8Bp1GcO4vCdl9Piz6FlWAzKVfa/U3Hn/DdzSVJOW3TWcQQFdBDw==",
"license": "MIT",
"dependencies": {
"cors": "2.8.6",
"express": "5.2.1",
"multer": "2.1.0",
"multer": "2.1.1",
"path-to-regexp": "8.3.0",
"tslib": "2.8.1"
},
@ -2068,9 +2068,9 @@
"license": "AGPL-3.0-only"
},
"node_modules/@remnawave/node-plugins": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@remnawave/node-plugins/-/node-plugins-0.1.1.tgz",
"integrity": "sha512-UxiEG6+hjjCDRjXlAieq4vx9KIJNwI36hdup3vl6fTXYUy4csnPR6JsPC5AhGj1n4B539ifkEr2MuWtwXLfx5w==",
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@remnawave/node-plugins/-/node-plugins-0.1.3.tgz",
"integrity": "sha512-n5gZGHpC3pJVB99+BeVX9ZadKIyAajHDlMBviyJOepb2R9gB7eICPxIt9YroLS/5/GdkcLNd6ojveE2fvDF4Jw==",
"license": "AGPL-3.0-only",
"dependencies": {
"zod": "3.25.76"
@ -6075,9 +6075,9 @@
"license": "MIT"
},
"node_modules/multer": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/multer/-/multer-2.1.0.tgz",
"integrity": "sha512-TBm6j41rxNohqawsxlsWsNNh/VdV4QFXcBvRcPhXaA05EZ79z0qJ2bQFpync6JBoHTeNY5Q1JpG7AlTjdlfAEA==",
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz",
"integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==",
"license": "MIT",
"dependencies": {
"append-field": "^1.0.0",

View file

@ -35,17 +35,17 @@
"@henrygd/semaphore": "0.1.0",
"@kastov/body-parser-with-zstd": "2.2.2",
"@kastov/node-supervisord": ">=2.0.3",
"@nestjs/common": "11.1.15",
"@nestjs/common": "11.1.16",
"@nestjs/config": "4.0.3",
"@nestjs/core": "11.1.15",
"@nestjs/core": "11.1.16",
"@nestjs/jwt": "11.0.2",
"@nestjs/passport": "11.0.5",
"@nestjs/platform-express": "11.1.15",
"@nestjs/platform-express": "11.1.16",
"@peculiar/webcrypto": "^1.5.0",
"@peculiar/x509": "^1.14.3",
"@remnawave/hashed-set": "^0.0.4",
"@nestjs/cqrs": "11.0.3",
"@remnawave/node-plugins": "0.1.1",
"@remnawave/node-plugins": "0.1.3",
"@remnawave/supervisord-nestjs": "0.3.1",
"@remnawave/xtls-sdk": "0.11.1",
"@remnawave/xtls-sdk-nestjs": "0.6.1",

View file

@ -15,14 +15,32 @@ import { getServerCerts } from './generate-mtls-certs';
import { getXtlsApiPort } from './get-initial-ports';
import { IPolicyConfig } from './interfaces';
export const generateApiConfig = (
config: Record<string, unknown>,
isTorrentBlockerEnabled: boolean,
interface IRoutingXrayConfig {
rules: {
ruleTag?: string;
webhook?: {
url: string;
deduplication: number;
};
[key: string]: unknown;
}[];
}
interface IGenerateApiConfigParams {
config: Record<string, unknown>;
torrentBlockerState: {
enabled: boolean;
includeRuleTags: Set<string>;
};
internal: {
socketPath: string;
token: string;
},
): Record<string, unknown> => {
};
}
export const generateApiConfig = (args: IGenerateApiConfigParams): Record<string, unknown> => {
const { config, torrentBlockerState, internal } = args;
const policyConfig = config.policy as undefined | IPolicyConfig;
const serverCerts = getServerCerts();
const hasCapNetAdminResult = hasCapNetAdmin();
@ -39,7 +57,7 @@ export const generateApiConfig = (
system: XRAY_DEFAULT_POLICY_MODEL.policy.system,
};
return {
const result = {
...config,
...XRAY_DEFAULT_STATS_MODEL,
...XRAY_DEFAULT_API_MODEL,
@ -52,24 +70,44 @@ export const generateApiConfig = (
}),
...(Array.isArray(config.inbounds) ? config.inbounds : []),
],
outbounds: [
...(Array.isArray(config.outbounds) ? config.outbounds : []),
...(isTorrentBlockerEnabled ? [XRAY_TORRENT_BLOCKER_OUTBOUND_MODEL] : []),
],
outbounds: [...(Array.isArray(config.outbounds) ? config.outbounds : [])],
policy: builtPolicy,
routing: {
...(config.routing || {}),
rules: [
XRAY_ROUTING_RULES_MODEL,
...(isTorrentBlockerEnabled
? [
XRAY_TORRENT_BLOCKER_ROUTING_RULES_MODEL({
webhookUrl: `/${internal.socketPath}:${XRAY_INTERNAL_FULL_WEBHOOK_PATH}?token=${internal.token}`,
}),
]
: []),
...((config.routing as { rules?: unknown[] })?.rules || []),
],
},
};
if (torrentBlockerState.enabled) {
const webhookUrl = buildWebhookUrl(internal);
const routing = result.routing as IRoutingXrayConfig;
result.outbounds.push(XRAY_TORRENT_BLOCKER_OUTBOUND_MODEL);
routing.rules.splice(1, 0, XRAY_TORRENT_BLOCKER_ROUTING_RULES_MODEL({ webhookUrl }));
if (torrentBlockerState.includeRuleTags.size > 0) {
for (const rule of routing.rules) {
if (
rule.ruleTag &&
typeof rule.ruleTag === 'string' &&
torrentBlockerState.includeRuleTags.has(rule.ruleTag)
) {
rule.webhook = {
url: webhookUrl,
deduplication: 30,
};
}
}
}
}
return result;
};
const buildWebhookUrl = (internal: { socketPath: string; token: string }): string => {
return `/${internal.socketPath}:${XRAY_INTERNAL_FULL_WEBHOOK_PATH}?token=${internal.token}`;
};

View file

@ -95,9 +95,18 @@ export class PluginService {
this.logger.log('[PLUGIN] Plugins changed...');
if (currentTorrentBlocker && !pluginData.torrentBlocker?.enabled) {
await this.commandBus.execute(
new RemoveOutboundCommand(XRAY_TORRENT_BLOCKER_OUTBOUND_TAG),
);
if (!pluginData.torrentBlocker?.includeRuleTags) {
await this.commandBus.execute(
new RemoveOutboundCommand(XRAY_TORRENT_BLOCKER_OUTBOUND_TAG),
);
} else {
await this.commandBus.execute(
new StopXrayCommand({
withOnlineCheck: true,
withPluginCleanup: false,
}),
);
}
} else if (!currentTorrentBlocker && pluginData.torrentBlocker?.enabled) {
await this.commandBus.execute(
new StopXrayCommand({
@ -158,6 +167,7 @@ export class PluginService {
this.state.torrentBlocker.setIgnoredIps(ips);
this.state.torrentBlocker.setIgnoredUsers(users);
this.state.torrentBlocker.configure(blockDuration);
this.state.torrentBlocker.setIncludeRuleTags(pluginData.torrentBlocker.includeRuleTags);
this.logger.log(
`[PLUGIN] Torrent-Blocker: blockDuration=${blockDuration}s, ${ips.length} ignored IPs, ${users.length} ignored users`,

View file

@ -13,10 +13,16 @@ export class GetTorrentBlockerStateHandler implements IQueryHandler<GetTorrentBl
try {
const isEnabled = this.pluginState.torrentBlocker.isEnabled;
return isEnabled;
return {
enabled: isEnabled,
includeRuleTags: this.pluginState.torrentBlocker.includeRuleTagsSet,
};
} catch (error) {
this.logger.error(error);
return false;
return {
enabled: false,
includeRuleTags: new Set<string>(),
};
}
}
}

View file

@ -1,6 +1,9 @@
import { Query } from '@nestjs/cqrs';
export class GetTorrentBlockerStateQuery extends Query<boolean> {
export class GetTorrentBlockerStateQuery extends Query<{
enabled: boolean;
includeRuleTags: Set<string>;
}> {
constructor() {
super();
}

View file

@ -5,6 +5,7 @@ export class TorrentBlockerState {
private blockDuration: number | null = null;
private ignoredIps = new Set<string>();
private ignoredUsers = new Set<string>();
private includeRuleTags = new Set<string>();
private reports: TorrentBlockerReportModel[] = [];
get isEnabled(): boolean {
@ -55,5 +56,15 @@ export class TorrentBlockerState {
this.blockDuration = null;
this.ignoredIps.clear();
this.ignoredUsers.clear();
this.includeRuleTags.clear();
}
setIncludeRuleTags(tags: string[] | undefined): void {
if (!tags) return;
this.includeRuleTags = new Set(tags);
}
get includeRuleTagsSet(): Set<string> {
return this.includeRuleTags;
}
}

View file

@ -160,11 +160,11 @@ export class XrayService implements OnApplicationBootstrap {
new GetTorrentBlockerStateQuery(),
);
const fullConfig = generateApiConfig(
body.xrayConfig,
isTorrentBlockerEnabled,
this.internal,
);
const fullConfig = generateApiConfig({
config: body.xrayConfig,
torrentBlockerState: isTorrentBlockerEnabled,
internal: this.internal,
});
await this.internalService.extractUsersFromConfig(body.internals.hashes, fullConfig);