From 0c5332727431f54b0b1c46af6d7902cada3da30a Mon Sep 17 00:00:00 2001 From: kastov Date: Sun, 3 Aug 2025 04:25:49 +0300 Subject: [PATCH 01/11] feat: enhance internal service and user management - Added @remnawave/hashed-set dependency - Updated InternalService to manage user extraction and configuration checks. - Refactored HandlerService to utilize InternalService for user management. - Improved XrayService to handle hash payloads for configuration validation. --- .../contract/constants/hashes/hash-payload.ts | 8 + libs/contract/constants/hashes/index.ts | 1 + .../constants/headers/headers.contants.ts | 1 + libs/contract/constants/headers/index.ts | 1 + libs/contract/constants/index.ts | 2 + libs/contract/package.json | 2 +- package-lock.json | 7 + package.json | 5 +- .../get-hash-payload/get-hash-payload.ts | 21 +++ src/modules/handler/handler.service.ts | 19 ++- src/modules/internal/internal.service.ts | 151 ++++++++++++++++++ src/modules/vision/vision.controller.ts | 6 +- src/modules/xray-core/xray.controller.ts | 8 +- src/modules/xray-core/xray.service.ts | 57 ++++--- 14 files changed, 260 insertions(+), 29 deletions(-) create mode 100644 libs/contract/constants/hashes/hash-payload.ts create mode 100644 libs/contract/constants/hashes/index.ts create mode 100644 libs/contract/constants/headers/headers.contants.ts create mode 100644 libs/contract/constants/headers/index.ts create mode 100644 src/common/decorators/get-hash-payload/get-hash-payload.ts diff --git a/libs/contract/constants/hashes/hash-payload.ts b/libs/contract/constants/hashes/hash-payload.ts new file mode 100644 index 0000000..aa6ac44 --- /dev/null +++ b/libs/contract/constants/hashes/hash-payload.ts @@ -0,0 +1,8 @@ +export interface IHashPayload { + emptyConfig: string; + inbounds: { + usersCount: number; + hash: number; + tag: string; + }[]; +} diff --git a/libs/contract/constants/hashes/index.ts b/libs/contract/constants/hashes/index.ts new file mode 100644 index 0000000..8b113eb --- /dev/null +++ b/libs/contract/constants/hashes/index.ts @@ -0,0 +1 @@ +export * from './hash-payload'; diff --git a/libs/contract/constants/headers/headers.contants.ts b/libs/contract/constants/headers/headers.contants.ts new file mode 100644 index 0000000..5401533 --- /dev/null +++ b/libs/contract/constants/headers/headers.contants.ts @@ -0,0 +1 @@ +export const X_HASH_PAYLOAD = 'X-Hash-Payload'; diff --git a/libs/contract/constants/headers/index.ts b/libs/contract/constants/headers/index.ts new file mode 100644 index 0000000..2be1a2f --- /dev/null +++ b/libs/contract/constants/headers/index.ts @@ -0,0 +1 @@ +export * from './headers.contants'; diff --git a/libs/contract/constants/index.ts b/libs/contract/constants/index.ts index 04448ba..a77b81e 100644 --- a/libs/contract/constants/index.ts +++ b/libs/contract/constants/index.ts @@ -1,4 +1,6 @@ export * from './errors'; +export * from './hashes'; +export * from './headers'; export * from './internal'; export * from './roles'; export * from './xray'; diff --git a/libs/contract/package.json b/libs/contract/package.json index daa525c..279ba24 100644 --- a/libs/contract/package.json +++ b/libs/contract/package.json @@ -1,6 +1,6 @@ { "name": "@remnawave/node-contract", - "version": "0.5.3", + "version": "0.5.4", "description": "A node-contract library for Remnawave Panel", "main": "build/index.js", "types": "build/index.d.ts", diff --git a/package-lock.json b/package-lock.json index 73bd2ef..cfe6f05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@nestjs/jwt": "11.0.0", "@nestjs/passport": "11.0.5", "@nestjs/platform-express": "11.1.5", + "@remnawave/hashed-set": "^0.0.1", "@remnawave/supervisord-nestjs": "0.1.1", "@remnawave/xtls-sdk": "0.4.1", "@remnawave/xtls-sdk-nestjs": "0.4.0", @@ -1629,6 +1630,12 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "license": "BSD-3-Clause" }, + "node_modules/@remnawave/hashed-set": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@remnawave/hashed-set/-/hashed-set-0.0.1.tgz", + "integrity": "sha512-VZIcUwtDGcGLsAXbXMaP/5w2OtOynsqveWzKekxw6pA6ssS5U2zxNbGsHeAjORXu6nLNJgJazX7JBZSfRjKYzQ==", + "license": "AGPL-3.0-only" + }, "node_modules/@remnawave/supervisord-nestjs": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@remnawave/supervisord-nestjs/-/supervisord-nestjs-0.1.1.tgz", diff --git a/package.json b/package.json index bf4dd37..a340a69 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,10 @@ "@nestjs/jwt": "11.0.0", "@nestjs/passport": "11.0.5", "@nestjs/platform-express": "11.1.5", + "@remnawave/hashed-set": "^0.0.1", "@remnawave/supervisord-nestjs": "0.1.1", - "@remnawave/xtls-sdk-nestjs": "0.4.0", "@remnawave/xtls-sdk": "0.4.1", + "@remnawave/xtls-sdk-nestjs": "0.4.0", "compression": "^1.8.1", "enhanced-ms": "^4.1.0", "helmet": "^8.1.0", @@ -90,4 +91,4 @@ "tsconfig-paths": "^4.2.0", "typescript": "~5.8.2" } -} \ No newline at end of file +} diff --git a/src/common/decorators/get-hash-payload/get-hash-payload.ts b/src/common/decorators/get-hash-payload/get-hash-payload.ts new file mode 100644 index 0000000..16b12cd --- /dev/null +++ b/src/common/decorators/get-hash-payload/get-hash-payload.ts @@ -0,0 +1,21 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +import { IHashPayload, X_HASH_PAYLOAD } from '@libs/contracts/constants'; + +export const HashPayload = createParamDecorator((_, ctx: ExecutionContext): IHashPayload | null => { + const request = ctx.switchToHttp().getRequest(); + + if (request.headers[X_HASH_PAYLOAD.toLowerCase()]) { + try { + const hashPayload = request.headers[X_HASH_PAYLOAD.toLowerCase()] as string; + const decodedPayload = Buffer.from(hashPayload, 'base64').toString('utf-8'); + const hashPayloadJson = JSON.parse(decodedPayload); + + return hashPayloadJson as IHashPayload; + } catch { + return null; + } + } + + return null; +}); diff --git a/src/modules/handler/handler.service.ts b/src/modules/handler/handler.service.ts index 0d36056..36f2ae4 100644 --- a/src/modules/handler/handler.service.ts +++ b/src/modules/handler/handler.service.ts @@ -10,9 +10,9 @@ import { ICommandResponse } from '@common/types/command-response.type'; import { ERRORS } from '@libs/contracts/constants/errors'; import { AddUserResponseModel, RemoveUserResponseModel } from './models'; +import { InternalService } from '../internal/internal.service'; import { GetInboundUsersCountResponseModel } from './models'; import { GetInboundUsersResponseModel } from './models'; -import { XrayService } from '../xray-core/xray.service'; import { IRemoveUserRequest } from './interfaces'; import { TAddUserRequest } from './interfaces'; @@ -22,7 +22,7 @@ export class HandlerService { constructor( @InjectXtls() private readonly xtlsApi: XtlsApi, - private readonly xrayService: XrayService, + private readonly internalService: InternalService, ) {} public async addUser(data: TAddUserRequest): Promise> { @@ -30,11 +30,12 @@ export class HandlerService { const { data: requestData } = data; const response: Array> = []; - const inboundsTags = this.xrayService.getSavedInboundsTags(); + const inboundsTags = this.internalService.getXtlsConfigInbounds(); for (const tag of inboundsTags) { this.logger.debug(`Removing user: ${requestData[0].username} from tag: ${tag}`); await this.xtlsApi.handler.removeUser(tag, requestData[0].username); + this.internalService.removeUserFromInbound(tag, requestData[0].username); } for (const item of requestData) { @@ -50,6 +51,9 @@ export class HandlerService { password: item.password, level: item.level, }); + if (tempRes.isOk) { + this.internalService.addUserToInbound(item.tag, item.username); + } response.push(tempRes); break; case 'vless': @@ -60,6 +64,9 @@ export class HandlerService { flow: item.flow, level: item.level, }); + if (tempRes.isOk) { + this.internalService.addUserToInbound(item.tag, item.username); + } response.push(tempRes); break; case 'shadowsocks': @@ -71,6 +78,9 @@ export class HandlerService { ivCheck: item.ivCheck, level: item.level, }); + if (tempRes.isOk) { + this.internalService.addUserToInbound(item.tag, item.username); + } response.push(tempRes); break; } @@ -141,10 +151,11 @@ export class HandlerService { const { username } = data; const response: Array> = []; - const inboundsTags = this.xrayService.getSavedInboundsTags(); + const inboundsTags = this.internalService.getXtlsConfigInbounds(); for (const tag of inboundsTags) { const tempRes = await this.xtlsApi.handler.removeUser(tag, username); + this.internalService.removeUserFromInbound(tag, username); response.push(tempRes); } diff --git a/src/modules/internal/internal.service.ts b/src/modules/internal/internal.service.ts index 4271583..8d15988 100644 --- a/src/modules/internal/internal.service.ts +++ b/src/modules/internal/internal.service.ts @@ -1,9 +1,18 @@ +import ems from 'enhanced-ms'; + import { Injectable, Logger } from '@nestjs/common'; +import { HashedSet } from '@remnawave/hashed-set'; + +import { IHashPayload } from '@libs/contracts/constants'; + @Injectable() export class InternalService { private readonly logger = new Logger(InternalService.name); private xrayConfig: null | Record = null; + private emptyConfigHash: null | string = null; + private inboundsHashMap: Map> = new Map(); + private xtlsConfigInbounds: string[] = []; constructor() {} @@ -19,4 +28,146 @@ export class InternalService { this.logger.debug('Setting new xray config'); this.xrayConfig = config; } + + public extractUsersFromConfig( + hashPayload: IHashPayload, + newConfig: Record, + ): void { + this.cleanup(); + + this.emptyConfigHash = hashPayload.emptyConfig; + this.xrayConfig = newConfig; + + this.logger.log( + `Starting user extraction from inbounds with hash payload: ${JSON.stringify(hashPayload)}`, + ); + + const start = performance.now(); + if (newConfig.inbounds && Array.isArray(newConfig.inbounds)) { + for (const inbound of newConfig.inbounds) { + const inboundTag: string = inbound.tag; + + if (!inboundTag || !hashPayload.inbounds.find((item) => item.tag === inboundTag)) { + continue; + } + + const usersSet = new HashedSet(); + + if ( + inbound.settings && + inbound.settings.clients && + Array.isArray(inbound.settings.clients) + ) { + for (const client of inbound.settings.clients) { + if (client.email) { + usersSet.add(client.email); + } + } + } + + this.inboundsHashMap.set(inboundTag, usersSet); + } + + for (const [inboundTag, usersSet] of this.inboundsHashMap) { + this.xtlsConfigInbounds.push(inboundTag); + this.logger.log(`Inbound "${inboundTag}" contains ${usersSet.size} user(s)`); + } + } + + this.logger.log( + `User extraction completed in ${ems(performance.now() - start, { + extends: 'short', + includeMs: true, + })}`, + ); + } + + public isNeedRestartCore(incomingHashPayload: IHashPayload): boolean { + const start = performance.now(); + try { + if (!this.emptyConfigHash) { + return true; + } + + if (incomingHashPayload.emptyConfig !== this.emptyConfigHash) { + this.logger.log('Detected changes in Xray Core base configuration'); + return true; + } + + if (incomingHashPayload.inbounds.length !== this.inboundsHashMap.size) { + this.logger.log('Number of Xray Core inbounds has changed'); + return true; + } + + for (const [inboundTag, usersSet] of this.inboundsHashMap) { + const incomingInbound = incomingHashPayload.inbounds.find( + (item) => item.tag === inboundTag, + ); + + if (!incomingInbound) { + this.logger.log( + `Inbound "${inboundTag}" no longer exists in Xray Core configuration`, + ); + return true; + } + + if (usersSet.hash !== incomingInbound.hash) { + this.logger.log( + `User configuration changed for inbound "${inboundTag}" (hash: ${usersSet.hash} → ${incomingInbound.hash})`, + ); + return true; + } + } + + this.logger.log('Xray Core configuration is up-to-date - no restart required'); + + return false; + } catch (error) { + this.logger.error(`Failed to check if Xray Core restart is needed: ${error}`); + return true; + } finally { + this.logger.log( + 'Configuration hash check completed in ' + + ems(performance.now() - start, { + extends: 'short', + includeMs: true, + }), + ); + } + } + + public addUserToInbound(inboundTag: string, user: string): void { + const usersSet = this.inboundsHashMap.get(inboundTag); + + if (!usersSet) { + return; + } + + usersSet.add(user); + } + + public removeUserFromInbound(inboundTag: string, user: string): void { + const usersSet = this.inboundsHashMap.get(inboundTag); + + if (!usersSet) { + return; + } + + usersSet.delete(user); + } + + public getInboundHash(inboundTag: string): number { + return this.inboundsHashMap.get(inboundTag)?.hash || 0; + } + + public getXtlsConfigInbounds(): string[] { + return this.xtlsConfigInbounds; + } + + public cleanup(): void { + this.inboundsHashMap.clear(); + this.xtlsConfigInbounds = []; + this.xrayConfig = null; + this.emptyConfigHash = null; + } } diff --git a/src/modules/vision/vision.controller.ts b/src/modules/vision/vision.controller.ts index b382e2c..9368426 100644 --- a/src/modules/vision/vision.controller.ts +++ b/src/modules/vision/vision.controller.ts @@ -1,20 +1,20 @@ import { Body, Controller, Post, UseFilters, UseGuards } from '@nestjs/common'; -import { VISION_CONTROLLER, VISION_ROUTES } from '@libs/contracts/api/controllers/vision'; import { PortGuard } from '@common/guards/request-port-guard/request-port.guard'; import { HttpExceptionFilter } from '@common/exception/httpException.filter'; import { errorHandler } from '@common/helpers/error-handler.helper'; -import { XRAY_INTERNAL_API_PORT } from '@libs/contracts/constants'; import { OnPort } from '@common/decorators/port/port.decorator'; +import { VISION_CONTROLLER, VISION_ROUTES } from '@libs/contracts/api/controllers/vision'; +import { XRAY_INTERNAL_API_PORT } from '@libs/contracts/constants'; import { UnblockIpRequestDto, UnblockIpResponseDto } from './dtos/unblock-ip.dto'; import { BlockIpRequestDto, BlockIpResponseDto } from './dtos/block-ip.dto'; import { VisionService } from './vision.service'; -@Controller(VISION_CONTROLLER) @OnPort(XRAY_INTERNAL_API_PORT) @UseFilters(HttpExceptionFilter) @UseGuards(PortGuard) +@Controller(VISION_CONTROLLER) export class VisionController { constructor(private readonly visionService: VisionService) {} diff --git a/src/modules/xray-core/xray.controller.ts b/src/modules/xray-core/xray.controller.ts index 4343142..6062433 100644 --- a/src/modules/xray-core/xray.controller.ts +++ b/src/modules/xray-core/xray.controller.ts @@ -1,9 +1,11 @@ import { Body, Controller, Get, Ip, Logger, Post, UseFilters, UseGuards } from '@nestjs/common'; +import { HashPayload } from '@common/decorators/get-hash-payload/get-hash-payload'; import { HttpExceptionFilter } from '@common/exception/httpException.filter'; import { JwtDefaultGuard } from '@common/guards/jwt-guards/def-jwt-guard'; import { errorHandler } from '@common/helpers/error-handler.helper'; import { XRAY_CONTROLLER, XRAY_ROUTES } from '@libs/contracts/api/controllers/xray'; +import { IHashPayload } from '@libs/contracts/constants'; import { GetNodeHealthCheckResponseDto, @@ -26,8 +28,12 @@ export class XrayController { public async startXray( @Body() body: StartXrayRequestDto, @Ip() ip: string, + @HashPayload() hashPayload: IHashPayload | null, ): Promise { - const response = await this.xrayService.startXray(body, ip); + // TODO: remove this after testing + this.logger.log(`Hash payload: ${JSON.stringify(hashPayload)}`); + + const response = await this.xrayService.startXray(body, ip, hashPayload); const data = errorHandler(response); return { diff --git a/src/modules/xray-core/xray.service.ts b/src/modules/xray-core/xray.service.ts index e88442a..72d07b5 100644 --- a/src/modules/xray-core/xray.service.ts +++ b/src/modules/xray-core/xray.service.ts @@ -17,7 +17,7 @@ import { ISystemStats } from '@common/utils/get-system-stats/get-system-stats.in import { ICommandResponse } from '@common/types/command-response.type'; import { generateApiConfig } from '@common/utils/generate-api-config'; import { getSystemStats } from '@common/utils/get-system-stats'; -import { KNOWN_ERRORS, REMNAWAVE_NODE_KNOWN_ERROR } from '@libs/contracts/constants'; +import { IHashPayload, KNOWN_ERRORS, REMNAWAVE_NODE_KNOWN_ERROR } from '@libs/contracts/constants'; import { GetNodeHealthCheckResponseModel, @@ -39,7 +39,6 @@ export class XrayService implements OnApplicationBootstrap, OnModuleInit { private isXrayOnline: boolean = false; private systemStats: ISystemStats | null = null; private isXrayStartedProccesing: boolean = false; - private xtlsConfigInbounds: Array = []; private nodeVersion: string | null = null; constructor( @InjectXtls() private readonly xtlsSdk: XtlsApi, @@ -51,7 +50,6 @@ export class XrayService implements OnApplicationBootstrap, OnModuleInit { this.systemStats = null; this.isXrayStartedProccesing = false; this.nodeVersion = null; - this.xtlsConfigInbounds = []; } async onModuleInit() { @@ -76,10 +74,29 @@ export class XrayService implements OnApplicationBootstrap, OnModuleInit { public async startXray( config: Record, ip: string, + hashPayload: IHashPayload | null, ): Promise> { const tm = performance.now(); try { + if (!hashPayload) { + this.logger.error( + 'Hash payload is null. Update Remnawave to version 2.1.0 or downgrade @remnawave/node to 2.0.0.', + ); + return { + isOk: false, + response: new StartXrayResponseModel( + false, + null, + 'Hash payload is null', + null, + { + version: this.nodeVersion, + }, + ), + }; + } + if (this.isXrayStartedProccesing) { this.logger.warn('Request already in progress'); return { @@ -100,9 +117,25 @@ export class XrayService implements OnApplicationBootstrap, OnModuleInit { const fullConfig = generateApiConfig(config); - this.xtlsConfigInbounds = await this.extractInboundTags(fullConfig); + if (this.isXrayOnline) { + const isNeedRestart = this.internalService.isNeedRestartCore(hashPayload); + if (!isNeedRestart) { + return { + isOk: true, + response: new StartXrayResponseModel( + true, + this.xrayVersion, + null, + this.systemStats, + { + version: this.nodeVersion, + }, + ), + }; + } + } - this.internalService.setXrayConfig(fullConfig); + this.internalService.extractUsersFromConfig(hashPayload, fullConfig); const xrayProcess = await this.restartXrayProcess(); @@ -228,7 +261,7 @@ export class XrayService implements OnApplicationBootstrap, OnModuleInit { await this.killAllXrayProcesses(); this.isXrayOnline = false; - this.internalService.setXrayConfig({}); + this.internalService.cleanup(); return { isOk: true, @@ -408,16 +441,4 @@ export class XrayService implements OnApplicationBootstrap, OnModuleInit { }; } } - - private async extractInboundTags(config: Record): Promise { - if (!config.inbounds || !Array.isArray(config.inbounds)) { - return []; - } - - return config.inbounds.map((inbound: { tag: string }) => inbound.tag); - } - - public getSavedInboundsTags(): string[] { - return this.xtlsConfigInbounds; - } } From 8bb733ae9bdd781bc858f014b46144bd370e7941 Mon Sep 17 00:00:00 2001 From: kastov Date: Sun, 3 Aug 2025 19:38:51 +0300 Subject: [PATCH 02/11] chore: update dependencies and refactor internal service - Changed hash type from number to string in IHashPayload interface. --- libs/contract/constants/hashes/hash-payload.ts | 2 +- libs/contract/package.json | 2 +- package-lock.json | 8 ++++---- package.json | 4 ++-- src/modules/internal/internal.service.ts | 10 +++------- 5 files changed, 11 insertions(+), 15 deletions(-) diff --git a/libs/contract/constants/hashes/hash-payload.ts b/libs/contract/constants/hashes/hash-payload.ts index aa6ac44..f58982d 100644 --- a/libs/contract/constants/hashes/hash-payload.ts +++ b/libs/contract/constants/hashes/hash-payload.ts @@ -2,7 +2,7 @@ export interface IHashPayload { emptyConfig: string; inbounds: { usersCount: number; - hash: number; + hash: string; tag: string; }[]; } diff --git a/libs/contract/package.json b/libs/contract/package.json index 279ba24..dbf38c8 100644 --- a/libs/contract/package.json +++ b/libs/contract/package.json @@ -1,6 +1,6 @@ { "name": "@remnawave/node-contract", - "version": "0.5.4", + "version": "0.5.6", "description": "A node-contract library for Remnawave Panel", "main": "build/index.js", "types": "build/index.d.ts", diff --git a/package-lock.json b/package-lock.json index cfe6f05..9d401c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@nestjs/jwt": "11.0.0", "@nestjs/passport": "11.0.5", "@nestjs/platform-express": "11.1.5", - "@remnawave/hashed-set": "^0.0.1", + "@remnawave/hashed-set": "^0.0.3", "@remnawave/supervisord-nestjs": "0.1.1", "@remnawave/xtls-sdk": "0.4.1", "@remnawave/xtls-sdk-nestjs": "0.4.0", @@ -1631,9 +1631,9 @@ "license": "BSD-3-Clause" }, "node_modules/@remnawave/hashed-set": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@remnawave/hashed-set/-/hashed-set-0.0.1.tgz", - "integrity": "sha512-VZIcUwtDGcGLsAXbXMaP/5w2OtOynsqveWzKekxw6pA6ssS5U2zxNbGsHeAjORXu6nLNJgJazX7JBZSfRjKYzQ==", + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@remnawave/hashed-set/-/hashed-set-0.0.3.tgz", + "integrity": "sha512-rdyHgUDAqgR2YTCyzeZYWA6nrtY3196tsOnY3xcpjj+JN/IBCtNHdUcorG4s2lOUlTgV1qxW4HkG8oGNWEnBaQ==", "license": "AGPL-3.0-only" }, "node_modules/@remnawave/supervisord-nestjs": { diff --git a/package.json b/package.json index a340a69..48e07dd 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@nestjs/jwt": "11.0.0", "@nestjs/passport": "11.0.5", "@nestjs/platform-express": "11.1.5", - "@remnawave/hashed-set": "^0.0.1", + "@remnawave/hashed-set": "^0.0.3", "@remnawave/supervisord-nestjs": "0.1.1", "@remnawave/xtls-sdk": "0.4.1", "@remnawave/xtls-sdk-nestjs": "0.4.0", @@ -91,4 +91,4 @@ "tsconfig-paths": "^4.2.0", "typescript": "~5.8.2" } -} +} \ No newline at end of file diff --git a/src/modules/internal/internal.service.ts b/src/modules/internal/internal.service.ts index 8d15988..94a4a3a 100644 --- a/src/modules/internal/internal.service.ts +++ b/src/modules/internal/internal.service.ts @@ -11,7 +11,7 @@ export class InternalService { private readonly logger = new Logger(InternalService.name); private xrayConfig: null | Record = null; private emptyConfigHash: null | string = null; - private inboundsHashMap: Map> = new Map(); + private inboundsHashMap: Map = new Map(); private xtlsConfigInbounds: string[] = []; constructor() {} @@ -111,9 +111,9 @@ export class InternalService { return true; } - if (usersSet.hash !== incomingInbound.hash) { + if (usersSet.hash64String !== incomingInbound.hash) { this.logger.log( - `User configuration changed for inbound "${inboundTag}" (hash: ${usersSet.hash} → ${incomingInbound.hash})`, + `User configuration changed for inbound "${inboundTag}" (hash: ${usersSet.hash64String} → ${incomingInbound.hash})`, ); return true; } @@ -156,10 +156,6 @@ export class InternalService { usersSet.delete(user); } - public getInboundHash(inboundTag: string): number { - return this.inboundsHashMap.get(inboundTag)?.hash || 0; - } - public getXtlsConfigInbounds(): string[] { return this.xtlsConfigInbounds; } From 5a28b8d709275a0d8cd4874f59652b490a7569cf Mon Sep 17 00:00:00 2001 From: kastov Date: Mon, 4 Aug 2025 02:27:53 +0300 Subject: [PATCH 03/11] feat: enhance user management with hash data support - Bumped version to 0.5.8 in package.json. - Updated AddUserCommand and RemoveUserCommand to include hashData for user identification. - Modified HandlerService to utilize hashData for user addition and removal. - Adjusted interfaces for add and remove user requests to include hashData structure. --- .../commands/handler/add-user.command.ts | 4 ++++ .../commands/handler/remove-user.command.ts | 3 +++ libs/contract/package.json | 2 +- src/modules/handler/handler.service.ts | 18 +++++++++++------- .../handler/interfaces/add-user.interface.ts | 4 ++++ .../interfaces/remove-user.interface.ts | 3 +++ src/modules/internal/internal.service.ts | 4 ++-- 7 files changed, 28 insertions(+), 10 deletions(-) diff --git a/libs/contract/commands/handler/add-user.command.ts b/libs/contract/commands/handler/add-user.command.ts index 2281ee7..49aee14 100644 --- a/libs/contract/commands/handler/add-user.command.ts +++ b/libs/contract/commands/handler/add-user.command.ts @@ -79,6 +79,10 @@ export namespace AddUserCommand { BaseHttpUser, ]), ), + hashData: z.object({ + vlessUuid: z.string().uuid(), + prevVlessUuid: z.optional(z.string().uuid()), + }), }); export type Request = z.infer; diff --git a/libs/contract/commands/handler/remove-user.command.ts b/libs/contract/commands/handler/remove-user.command.ts index f6dfbdb..cc1e75c 100644 --- a/libs/contract/commands/handler/remove-user.command.ts +++ b/libs/contract/commands/handler/remove-user.command.ts @@ -7,6 +7,9 @@ export namespace RemoveUserCommand { export const RequestSchema = z.object({ username: z.string(), + hashData: z.object({ + vlessUuid: z.string().uuid(), + }), }); export type Request = z.infer; diff --git a/libs/contract/package.json b/libs/contract/package.json index dbf38c8..96a5713 100644 --- a/libs/contract/package.json +++ b/libs/contract/package.json @@ -1,6 +1,6 @@ { "name": "@remnawave/node-contract", - "version": "0.5.6", + "version": "0.5.8", "description": "A node-contract library for Remnawave Panel", "main": "build/index.js", "types": "build/index.d.ts", diff --git a/src/modules/handler/handler.service.ts b/src/modules/handler/handler.service.ts index 36f2ae4..c67992c 100644 --- a/src/modules/handler/handler.service.ts +++ b/src/modules/handler/handler.service.ts @@ -27,7 +27,7 @@ export class HandlerService { public async addUser(data: TAddUserRequest): Promise> { try { - const { data: requestData } = data; + const { data: requestData, hashData } = data; const response: Array> = []; const inboundsTags = this.internalService.getXtlsConfigInbounds(); @@ -35,7 +35,11 @@ export class HandlerService { for (const tag of inboundsTags) { this.logger.debug(`Removing user: ${requestData[0].username} from tag: ${tag}`); await this.xtlsApi.handler.removeUser(tag, requestData[0].username); - this.internalService.removeUserFromInbound(tag, requestData[0].username); + if (hashData.prevVlessUuid) { + this.internalService.removeUserFromInbound(tag, hashData.prevVlessUuid); + } else { + this.internalService.removeUserFromInbound(tag, hashData.vlessUuid); + } } for (const item of requestData) { @@ -52,7 +56,7 @@ export class HandlerService { level: item.level, }); if (tempRes.isOk) { - this.internalService.addUserToInbound(item.tag, item.username); + this.internalService.addUserToInbound(item.tag, hashData.vlessUuid); } response.push(tempRes); break; @@ -65,7 +69,7 @@ export class HandlerService { level: item.level, }); if (tempRes.isOk) { - this.internalService.addUserToInbound(item.tag, item.username); + this.internalService.addUserToInbound(item.tag, hashData.vlessUuid); } response.push(tempRes); break; @@ -79,7 +83,7 @@ export class HandlerService { level: item.level, }); if (tempRes.isOk) { - this.internalService.addUserToInbound(item.tag, item.username); + this.internalService.addUserToInbound(item.tag, hashData.vlessUuid); } response.push(tempRes); break; @@ -148,14 +152,14 @@ export class HandlerService { data: IRemoveUserRequest, ): Promise> { try { - const { username } = data; + const { username, hashData } = data; const response: Array> = []; const inboundsTags = this.internalService.getXtlsConfigInbounds(); for (const tag of inboundsTags) { const tempRes = await this.xtlsApi.handler.removeUser(tag, username); - this.internalService.removeUserFromInbound(tag, username); + this.internalService.removeUserFromInbound(tag, hashData.vlessUuid); response.push(tempRes); } diff --git a/src/modules/handler/interfaces/add-user.interface.ts b/src/modules/handler/interfaces/add-user.interface.ts index 04ec559..1765b77 100644 --- a/src/modules/handler/interfaces/add-user.interface.ts +++ b/src/modules/handler/interfaces/add-user.interface.ts @@ -1,6 +1,10 @@ import { CipherType } from '@remnawave/xtls-sdk/build/src/xray-protos/proxy/shadowsocks/config'; export interface TAddUserRequest { + hashData: { + vlessUuid: string; + prevVlessUuid?: string; + }; data: Array< | { cipherType: CipherType; diff --git a/src/modules/handler/interfaces/remove-user.interface.ts b/src/modules/handler/interfaces/remove-user.interface.ts index b9129fd..24ca0c9 100644 --- a/src/modules/handler/interfaces/remove-user.interface.ts +++ b/src/modules/handler/interfaces/remove-user.interface.ts @@ -1,3 +1,6 @@ export interface IRemoveUserRequest { username: string; + hashData: { + vlessUuid: string; + }; } diff --git a/src/modules/internal/internal.service.ts b/src/modules/internal/internal.service.ts index 94a4a3a..b863e7a 100644 --- a/src/modules/internal/internal.service.ts +++ b/src/modules/internal/internal.service.ts @@ -59,8 +59,8 @@ export class InternalService { Array.isArray(inbound.settings.clients) ) { for (const client of inbound.settings.clients) { - if (client.email) { - usersSet.add(client.email); + if (client.id) { + usersSet.add(client.id); } } } From fc18509e46f9ba70164d0e555d592b084569a4a2 Mon Sep 17 00:00:00 2001 From: kastov Date: Sat, 9 Aug 2025 16:20:10 +0300 Subject: [PATCH 04/11] chore: version bump --- 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 9d401c6..42280fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@remnawave/node", - "version": "2.0.0", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@remnawave/node", - "version": "2.0.0", + "version": "2.1.0", "license": "AGPL-3.0-only", "dependencies": { "@cjs-exporter/execa": "9.5.2", diff --git a/package.json b/package.json index 48e07dd..b7991ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@remnawave/node", - "version": "2.0.0", + "version": "2.1.0", "description": "Remnawave Node", "private": false, "type": "commonjs", From 2e3603efea7f051d2086f16b494be9a0788f8f58 Mon Sep 17 00:00:00 2001 From: kastov Date: Sun, 10 Aug 2025 14:53:07 +0300 Subject: [PATCH 05/11] refactor: update Dockerfile, clean up debug logs - Introduced XRAY_CORE_VERSION and XRAY_CORE_INSTALL_SCRIPT as build arguments in Dockerfile. - Removed legacy Dockerfile. --- Dockerfile | 7 +- DockerfileLegacy | 39 --- package-lock.json | 379 ++++++----------------- package.json | 12 +- src/modules/internal/internal.service.ts | 32 +- src/modules/xray-core/xray.controller.ts | 3 - src/modules/xray-core/xray.service.ts | 19 +- 7 files changed, 121 insertions(+), 370 deletions(-) delete mode 100644 DockerfileLegacy diff --git a/Dockerfile b/Dockerfile index 22ede36..48ef962 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,9 +7,10 @@ RUN npm run build --omit=dev FROM node:22-alpine -RUN mkdir -p /var/log/supervisor /var/lib/rnode/xray \ - && echo '{}' > /var/lib/rnode/xray/xray-config.json +ARG XRAY_CORE_VERSION=v25.8.3 +ARG XRAY_CORE_INSTALL_SCRIPT=https://raw.githubusercontent.com/remnawave/scripts/main/scripts/install-latest-xray.sh +RUN mkdir -p /var/log/supervisor WORKDIR /opt/app COPY --from=build /opt/app/dist ./dist @@ -23,7 +24,7 @@ RUN apk add --no-cache \ python3 \ py3-pip \ && pip3 install --break-system-packages git+https://github.com/Supervisor/supervisor.git@4bf1e57cbf292ce988dc128e0d2c8917f18da9be \ - && curl -L https://raw.githubusercontent.com/remnawave/scripts/main/scripts/install-latest-xray.sh | bash -s -- v25.6.8 \ + && curl -L ${XRAY_CORE_INSTALL_SCRIPT} | bash -s -- ${XRAY_CORE_VERSION} \ && apk del curl git COPY supervisord.conf /etc/supervisord.conf diff --git a/DockerfileLegacy b/DockerfileLegacy deleted file mode 100644 index d56064e..0000000 --- a/DockerfileLegacy +++ /dev/null @@ -1,39 +0,0 @@ -FROM node:22-alpine AS build -WORKDIR /opt/app -ADD . . -RUN npm ci --legacy-peer-deps -RUN npm run build --omit=dev - - -FROM node:22-alpine - -RUN mkdir -p /var/log/supervisor /var/lib/rnode/xray \ - && echo '{}' > /var/lib/rnode/xray/xray-config.json - - -WORKDIR /opt/app -COPY --from=build /opt/app/dist ./dist - - -RUN apk add --no-cache \ - curl \ - unzip \ - bash \ - supervisor \ - && curl -L https://raw.githubusercontent.com/remnawave/scripts/main/scripts/install-latest-xray.sh | bash -s -- v1.8.23 \ - && apk del curl - -COPY supervisord.conf /etc/supervisord.conf -COPY docker-entrypoint.sh /usr/local/bin/ -RUN chmod +x /usr/local/bin/docker-entrypoint.sh - - -COPY package*.json ./ -COPY ./libs ./libs - -RUN npm ci --omit=dev --legacy-peer-deps \ - && npm cache clean --force - -ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] - -CMD ["npm", "run", "start:prod"] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 42280fe..14e2e82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,13 +10,13 @@ "license": "AGPL-3.0-only", "dependencies": { "@cjs-exporter/execa": "9.5.2", - "@nestjs/common": "11.1.5", + "@nestjs/common": "11.1.6", "@nestjs/config": "4.0.2", - "@nestjs/core": "11.1.5", + "@nestjs/core": "11.1.6", "@nestjs/jwt": "11.0.0", "@nestjs/passport": "11.0.5", - "@nestjs/platform-express": "11.1.5", - "@remnawave/hashed-set": "^0.0.3", + "@nestjs/platform-express": "11.1.6", + "@remnawave/hashed-set": "^0.0.4", "@remnawave/supervisord-nestjs": "0.1.1", "@remnawave/xtls-sdk": "0.4.1", "@remnawave/xtls-sdk-nestjs": "0.4.0", @@ -46,8 +46,8 @@ "zod": "^3.24.2" }, "devDependencies": { - "@nestjs/cli": "11.0.9", - "@nestjs/schematics": "11.0.6", + "@nestjs/cli": "11.0.10", + "@nestjs/schematics": "11.0.7", "@types/compression": "^1.8.1", "@types/express": "^5.0.3", "@types/js-yaml": "^4.0.9", @@ -75,9 +75,9 @@ } }, "node_modules/@angular-devkit/core": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-20.1.3.tgz", - "integrity": "sha512-23neiDOsq9cprozgBbnWo2nRTE4xYMjcAN59QcS4yYPccDkxbr3AazFHhlTSZWLp63hhTlT+B2AA47W7cUqhUQ==", + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.15.tgz", + "integrity": "sha512-pU2RZYX6vhd7uLSdLwPnuBcr0mXJSjp3EgOXKsrlQFQZevc+Qs+2JdXgIElnOT/aDqtRtriDmLlSbtdE8n3ZbA==", "dev": true, "license": "MIT", "dependencies": { @@ -85,11 +85,11 @@ "ajv-formats": "3.0.1", "jsonc-parser": "3.3.1", "picomatch": "4.0.2", - "rxjs": "7.8.2", + "rxjs": "7.8.1", "source-map": "0.7.4" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, @@ -102,64 +102,75 @@ } } }, + "node_modules/@angular-devkit/core/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/@angular-devkit/schematics": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-20.1.3.tgz", - "integrity": "sha512-VPwCeKsJE6FEwjIWoUL221Iqh/0Lbml/c+xjISIMXf58qinFlQj1k/5LNLlVrn56QLSHUpxoXIsVek/ME3x6/A==", + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.15.tgz", + "integrity": "sha512-kNOJ+3vekJJCQKWihNmxBkarJzNW09kP5a9E1SRNiQVNOUEeSwcRR0qYotM65nx821gNzjjhJXnAZ8OazWldrg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.1.3", + "@angular-devkit/core": "19.2.15", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", - "ora": "8.2.0", - "rxjs": "7.8.2" + "ora": "5.4.1", + "rxjs": "7.8.1" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@angular-devkit/schematics-cli": { - "version": "20.1.3", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-20.1.3.tgz", - "integrity": "sha512-pUnd3LRCMTsRsNeOi1xm9QImPGbB7pfy7XT8rHoamrinQxOe8G6Dz8qhKnInsxGCWsXKjmLPbeDFy3lG6yiiCg==", + "version": "19.2.15", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-19.2.15.tgz", + "integrity": "sha512-1ESFmFGMpGQmalDB3t2EtmWDGv6gOFYBMxmHO2f1KI/UDl8UmZnCGL4mD3EWo8Hv0YIsZ9wOH9Q7ZHNYjeSpzg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.1.3", - "@angular-devkit/schematics": "20.1.3", - "@inquirer/prompts": "7.6.0", + "@angular-devkit/core": "19.2.15", + "@angular-devkit/schematics": "19.2.15", + "@inquirer/prompts": "7.3.2", "ansi-colors": "4.1.3", - "yargs-parser": "22.0.0" + "symbol-observable": "4.0.0", + "yargs-parser": "21.1.1" }, "bin": { "schematics": "bin/schematics.js" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@angular-devkit/schematics-cli/node_modules/@inquirer/prompts": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.6.0.tgz", - "integrity": "sha512-jAhL7tyMxB3Gfwn4HIJ0yuJ5pvcB5maYUcouGcgd/ub79f9MqZ+aVnBtuFf+VC2GTkCBF+R+eo7Vi63w5VZlzw==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.3.2.tgz", + "integrity": "sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==", "dev": true, "license": "MIT", "dependencies": { - "@inquirer/checkbox": "^4.1.9", - "@inquirer/confirm": "^5.1.13", - "@inquirer/editor": "^4.2.14", - "@inquirer/expand": "^4.0.16", - "@inquirer/input": "^4.2.0", - "@inquirer/number": "^3.0.16", - "@inquirer/password": "^4.0.16", - "@inquirer/rawlist": "^4.1.4", - "@inquirer/search": "^3.0.16", - "@inquirer/select": "^4.2.4" + "@inquirer/checkbox": "^4.1.2", + "@inquirer/confirm": "^5.1.6", + "@inquirer/editor": "^4.2.7", + "@inquirer/expand": "^4.0.9", + "@inquirer/input": "^4.1.6", + "@inquirer/number": "^3.0.9", + "@inquirer/password": "^4.0.9", + "@inquirer/rawlist": "^4.0.9", + "@inquirer/search": "^3.0.9", + "@inquirer/select": "^4.0.9" }, "engines": { "node": ">=18" @@ -173,197 +184,14 @@ } } }, - "node_modules/@angular-devkit/schematics-cli/node_modules/yargs-parser": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", - "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=23" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@angular-devkit/schematics/node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "node_modules/@angular-devkit/schematics/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/log-symbols": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", - "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "is-unicode-supported": "^1.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/ora": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", - "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^5.0.0", - "cli-spinners": "^2.9.2", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.0.0", - "log-symbols": "^6.0.0", - "stdin-discarder": "^0.2.2", - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@angular-devkit/schematics/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "tslib": "^2.1.0" } }, "node_modules/@babel/code-frame": { @@ -1297,15 +1125,15 @@ } }, "node_modules/@nestjs/cli": { - "version": "11.0.9", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.9.tgz", - "integrity": "sha512-pSxiAl5eE4CnobEB4+pBoqHoTpXeQLwZh3Iig22v8IZBSQHHik9aZMWqm/fvIJjqK5qClPvLiiCJ5AIEBW/86Q==", + "version": "11.0.10", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.10.tgz", + "integrity": "sha512-4waDT0yGWANg0pKz4E47+nUrqIJv/UqrZ5wLPkCqc7oMGRMWKAaw1NDZ9rKsaqhqvxb2LfI5+uXOWr4yi94DOQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.1.3", - "@angular-devkit/schematics": "20.1.3", - "@angular-devkit/schematics-cli": "20.1.3", + "@angular-devkit/core": "19.2.15", + "@angular-devkit/schematics": "19.2.15", + "@angular-devkit/schematics-cli": "19.2.15", "@inquirer/prompts": "7.8.0", "@nestjs/schematics": "^11.0.1", "ansis": "4.1.0", @@ -1343,9 +1171,9 @@ } }, "node_modules/@nestjs/common": { - "version": "11.1.5", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.5.tgz", - "integrity": "sha512-DQpWdr3ShO0BHWkHl3I4W/jR6R3pDtxyBlmrpTuZF+PXxQyBXNvsUne0Wyo6QHPEDi+pAz9XchBFoKbqOhcdTg==", + "version": "11.1.6", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.6.tgz", + "integrity": "sha512-krKwLLcFmeuKDqngG2N/RuZHCs2ycsKcxWIDgcm7i1lf3sQ0iG03ci+DsP/r3FcT/eJDFsIHnKtNta2LIi7PzQ==", "license": "MIT", "dependencies": { "file-type": "21.0.0", @@ -1389,9 +1217,9 @@ } }, "node_modules/@nestjs/core": { - "version": "11.1.5", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.5.tgz", - "integrity": "sha512-Qr25MEY9t8VsMETy7eXQ0cNXqu0lzuFrrTr+f+1G57ABCtV5Pogm7n9bF71OU2bnkDD32Bi4hQLeFR90cku3Tw==", + "version": "11.1.6", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.6.tgz", + "integrity": "sha512-siWX7UDgErisW18VTeJA+x+/tpNZrJewjTBsRPF3JVxuWRuAB1kRoiJcxHgln8Lb5UY9NdvklITR84DUEXD0Cg==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -1462,9 +1290,9 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "11.1.5", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.5.tgz", - "integrity": "sha512-OsoiUBY9Shs5IG3uvDIt9/IDfY5OlvWBESuB/K4Eun8xILw1EK5d5qMfC3d2sIJ+kA3l+kBR1d/RuzH7VprLIg==", + "version": "11.1.6", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.6.tgz", + "integrity": "sha512-HErwPmKnk+loTq8qzu1up+k7FC6Kqa8x6lJ4cDw77KnTxLzsCaPt+jBvOq6UfICmfqcqCCf3dKXg+aObQp+kIQ==", "license": "MIT", "dependencies": { "cors": "2.8.5", @@ -1483,14 +1311,14 @@ } }, "node_modules/@nestjs/schematics": { - "version": "11.0.6", - "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.6.tgz", - "integrity": "sha512-vrrC6Znlv3JNisR0YPaNX30vLkM00Pydc6L7KgcC6mOplkJ/8r1t++BIdQLeWmGSj+jXQ6YWhaHT6kz+5UayMw==", + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.7.tgz", + "integrity": "sha512-t8dNYYMwEeEsrlwc2jbkfwCfXczq4AeNEgx1KVQuJ6wYibXk0ZbXbPdfp8scnEAaQv1grpncNV5gWgzi7ZwbvQ==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "20.1.3", - "@angular-devkit/schematics": "20.1.3", + "@angular-devkit/core": "19.2.15", + "@angular-devkit/schematics": "19.2.15", "comment-json": "4.2.5", "jsonc-parser": "3.3.1", "pluralize": "8.0.0" @@ -1631,9 +1459,9 @@ "license": "BSD-3-Clause" }, "node_modules/@remnawave/hashed-set": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@remnawave/hashed-set/-/hashed-set-0.0.3.tgz", - "integrity": "sha512-rdyHgUDAqgR2YTCyzeZYWA6nrtY3196tsOnY3xcpjj+JN/IBCtNHdUcorG4s2lOUlTgV1qxW4HkG8oGNWEnBaQ==", + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@remnawave/hashed-set/-/hashed-set-0.0.4.tgz", + "integrity": "sha512-YFIpbEbdxxC/2ReEKeRyiO7Vb6Zwkr6BooczsE0dNMXj1+nPtPNDrYui6/wmGLXHgzL9E8tgmqArkR4zbJ5+ng==", "license": "AGPL-3.0-only" }, "node_modules/@remnawave/supervisord-nestjs": { @@ -4356,19 +4184,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/get-east-asian-width": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", - "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-intrinsic": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", @@ -5349,19 +5164,6 @@ "node": ">=6" } }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -6825,19 +6627,6 @@ "node": ">= 0.8" } }, - "node_modules/stdin-discarder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -6981,6 +6770,16 @@ "node": ">=8" } }, + "node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/synckit": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.10.3.tgz", diff --git a/package.json b/package.json index b7991ad..9524de8 100644 --- a/package.json +++ b/package.json @@ -28,13 +28,13 @@ }, "dependencies": { "@cjs-exporter/execa": "9.5.2", - "@nestjs/common": "11.1.5", + "@nestjs/common": "11.1.6", "@nestjs/config": "4.0.2", - "@nestjs/core": "11.1.5", + "@nestjs/core": "11.1.6", "@nestjs/jwt": "11.0.0", "@nestjs/passport": "11.0.5", - "@nestjs/platform-express": "11.1.5", - "@remnawave/hashed-set": "^0.0.3", + "@nestjs/platform-express": "11.1.6", + "@remnawave/hashed-set": "^0.0.4", "@remnawave/supervisord-nestjs": "0.1.1", "@remnawave/xtls-sdk": "0.4.1", "@remnawave/xtls-sdk-nestjs": "0.4.0", @@ -64,8 +64,8 @@ "zod": "^3.24.2" }, "devDependencies": { - "@nestjs/cli": "11.0.9", - "@nestjs/schematics": "11.0.6", + "@nestjs/cli": "11.0.10", + "@nestjs/schematics": "11.0.7", "@types/compression": "^1.8.1", "@types/express": "^5.0.3", "@types/js-yaml": "^4.0.9", diff --git a/src/modules/internal/internal.service.ts b/src/modules/internal/internal.service.ts index b863e7a..290a759 100644 --- a/src/modules/internal/internal.service.ts +++ b/src/modules/internal/internal.service.ts @@ -39,7 +39,7 @@ export class InternalService { this.xrayConfig = newConfig; this.logger.log( - `Starting user extraction from inbounds with hash payload: ${JSON.stringify(hashPayload)}`, + `Starting user extraction from inbounds... Hash payload: ${JSON.stringify(hashPayload)}`, ); const start = performance.now(); @@ -70,16 +70,16 @@ export class InternalService { for (const [inboundTag, usersSet] of this.inboundsHashMap) { this.xtlsConfigInbounds.push(inboundTag); - this.logger.log(`Inbound "${inboundTag}" contains ${usersSet.size} user(s)`); + this.logger.log(`Inbound ${inboundTag} contains ${usersSet.size} user(s)`); } } - this.logger.log( - `User extraction completed in ${ems(performance.now() - start, { - extends: 'short', - includeMs: true, - })}`, - ); + const result = ems(performance.now() - start, { + extends: 'short', + includeMs: true, + }); + + this.logger.log(`User extraction completed in ${result ? result : '0ms'}`); } public isNeedRestartCore(incomingHashPayload: IHashPayload): boolean { @@ -106,14 +106,14 @@ export class InternalService { if (!incomingInbound) { this.logger.log( - `Inbound "${inboundTag}" no longer exists in Xray Core configuration`, + `Inbound ${inboundTag} no longer exists in Xray Core configuration`, ); return true; } if (usersSet.hash64String !== incomingInbound.hash) { this.logger.log( - `User configuration changed for inbound "${inboundTag}" (hash: ${usersSet.hash64String} → ${incomingInbound.hash})`, + `User configuration changed for inbound ${inboundTag} (${usersSet.hash64String} → ${incomingInbound.hash})`, ); return true; } @@ -126,13 +126,11 @@ export class InternalService { this.logger.error(`Failed to check if Xray Core restart is needed: ${error}`); return true; } finally { - this.logger.log( - 'Configuration hash check completed in ' + - ems(performance.now() - start, { - extends: 'short', - includeMs: true, - }), - ); + const result = ems(performance.now() - start, { + extends: 'short', + includeMs: true, + }); + this.logger.log(`Configuration hash check completed in ${result ? result : '0ms'}`); } } diff --git a/src/modules/xray-core/xray.controller.ts b/src/modules/xray-core/xray.controller.ts index 6062433..f4aa218 100644 --- a/src/modules/xray-core/xray.controller.ts +++ b/src/modules/xray-core/xray.controller.ts @@ -30,9 +30,6 @@ export class XrayController { @Ip() ip: string, @HashPayload() hashPayload: IHashPayload | null, ): Promise { - // TODO: remove this after testing - this.logger.log(`Hash payload: ${JSON.stringify(hashPayload)}`); - const response = await this.xrayService.startXray(body, ip, hashPayload); const data = errorHandler(response); diff --git a/src/modules/xray-core/xray.service.ts b/src/modules/xray-core/xray.service.ts index 72d07b5..f5a5054 100644 --- a/src/modules/xray-core/xray.service.ts +++ b/src/modules/xray-core/xray.service.ts @@ -80,20 +80,15 @@ export class XrayService implements OnApplicationBootstrap, OnModuleInit { try { if (!hashPayload) { - this.logger.error( - 'Hash payload is null. Update Remnawave to version 2.1.0 or downgrade @remnawave/node to 2.0.0.', - ); + const errMessage = + 'Hash payload is null. Update Remnawave to version 2.1.0 or downgrade @remnawave/node to 2.0.0.'; + this.logger.error(errMessage); + return { isOk: false, - response: new StartXrayResponseModel( - false, - null, - 'Hash payload is null', - null, - { - version: this.nodeVersion, - }, - ), + response: new StartXrayResponseModel(false, null, errMessage, null, { + version: this.nodeVersion, + }), }; } From 1bea86c019caab14f4091bf6a4119682338ee330 Mon Sep 17 00:00:00 2001 From: kastov Date: Sun, 10 Aug 2025 16:13:07 +0300 Subject: [PATCH 06/11] chore: bump @remnawave/xtls-sdk to version 0.5.0 in package.json and package-lock.json --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 14e2e82..c99d214 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@nestjs/platform-express": "11.1.6", "@remnawave/hashed-set": "^0.0.4", "@remnawave/supervisord-nestjs": "0.1.1", - "@remnawave/xtls-sdk": "0.4.1", + "@remnawave/xtls-sdk": "0.5.0", "@remnawave/xtls-sdk-nestjs": "0.4.0", "compression": "^1.8.1", "enhanced-ms": "^4.1.0", @@ -1478,9 +1478,9 @@ } }, "node_modules/@remnawave/xtls-sdk": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@remnawave/xtls-sdk/-/xtls-sdk-0.4.1.tgz", - "integrity": "sha512-2aBGIFTrRe7ahzERLWNITiMyS2q6Mg5togHE9paMF1c2o1zZ+w3mFfoq7qvzhATcLPkjNKnGADHrNyjqJ5Pvkg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@remnawave/xtls-sdk/-/xtls-sdk-0.5.0.tgz", + "integrity": "sha512-QYqJDVDuidss6CTyzxjNf3SHYrKyvF1JHI58QAjXJ2KMqtIG+1D/gEA2V7MlyywlLnNdaMdeWAdwiABape4UIQ==", "license": "AGPL-3.0-only", "dependencies": { "@bufbuild/protobuf": "^2.2.2", diff --git a/package.json b/package.json index 9524de8..1809630 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "@nestjs/platform-express": "11.1.6", "@remnawave/hashed-set": "^0.0.4", "@remnawave/supervisord-nestjs": "0.1.1", - "@remnawave/xtls-sdk": "0.4.1", + "@remnawave/xtls-sdk": "0.5.0", "@remnawave/xtls-sdk-nestjs": "0.4.0", "compression": "^1.8.1", "enhanced-ms": "^4.1.0", From 606f8d6909c1c38c0d8ccd63fe18b22ecb34e88d Mon Sep 17 00:00:00 2001 From: kastov Date: Mon, 11 Aug 2025 17:29:47 +0300 Subject: [PATCH 07/11] feat: add DISABLE_HASHED_SET_CHECK configuration option --- src/common/config/app-config/config.schema.ts | 4 ++++ src/modules/xray-core/xray.service.ts | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/common/config/app-config/config.schema.ts b/src/common/config/app-config/config.schema.ts index 108ebfb..e720761 100644 --- a/src/common/config/app-config/config.schema.ts +++ b/src/common/config/app-config/config.schema.ts @@ -13,6 +13,10 @@ export const configSchema = z JWT_PUBLIC_KEY: z.string().optional(), XTLS_IP: z.string().default('127.0.0.1'), XTLS_PORT: z.string().default('61000'), + DISABLE_HASHED_SET_CHECK: z + .string() + .default('false') + .transform((val) => val === 'true'), }) .superRefine((data, ctx) => { if (data.SSL_CERT) { diff --git a/src/modules/xray-core/xray.service.ts b/src/modules/xray-core/xray.service.ts index f5a5054..73ce3d3 100644 --- a/src/modules/xray-core/xray.service.ts +++ b/src/modules/xray-core/xray.service.ts @@ -8,6 +8,7 @@ import pRetry from 'p-retry'; import semver from 'semver'; import { Injectable, Logger, OnApplicationBootstrap, OnModuleInit } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { InjectSupervisord } from '@remnawave/supervisord-nestjs'; import { InjectXtls } from '@remnawave/xtls-sdk-nestjs'; @@ -32,6 +33,7 @@ const XRAY_PROCESS_NAME = 'xray' as const; @Injectable() export class XrayService implements OnApplicationBootstrap, OnModuleInit { private readonly logger = new Logger(XrayService.name); + private readonly disableHashedSetCheck: boolean; private readonly xrayPath: string; @@ -44,12 +46,16 @@ export class XrayService implements OnApplicationBootstrap, OnModuleInit { @InjectXtls() private readonly xtlsSdk: XtlsApi, @InjectSupervisord() private readonly supervisordApi: SupervisordClient, private readonly internalService: InternalService, + private readonly configService: ConfigService, ) { this.xrayPath = '/usr/local/bin/xray'; this.xrayVersion = null; this.systemStats = null; this.isXrayStartedProccesing = false; this.nodeVersion = null; + this.disableHashedSetCheck = this.configService.getOrThrow( + 'DISABLE_HASHED_SET_CHECK', + ); } async onModuleInit() { @@ -112,7 +118,7 @@ export class XrayService implements OnApplicationBootstrap, OnModuleInit { const fullConfig = generateApiConfig(config); - if (this.isXrayOnline) { + if (this.isXrayOnline && !this.disableHashedSetCheck) { const isNeedRestart = this.internalService.isNeedRestartCore(hashPayload); if (!isNeedRestart) { return { From 5335acce9a58920a4198964079cece7b178ceefc Mon Sep 17 00:00:00 2001 From: kastov Date: Mon, 11 Aug 2025 18:50:07 +0300 Subject: [PATCH 08/11] chore: update Dockerfile and GitHub Actions for multi-image build - Modified Dockerfile to include UPSTREAM_REPO as a build argument and adjusted the installation script call. - Updated GitHub Actions workflow to support building multiple Docker images with a matrix strategy. --- .github/workflows/build-dev.yml | 24 +++++++++++++++++------- Dockerfile | 5 +++-- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index 951464b..2b0b21b 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -21,10 +21,20 @@ jobs: thread_id: ${{ secrets.TELEGRAM_TOPIC_ID }} status: pending notify_fields: 'repo_with_tag,commit,workflow' - title: 'Building docker image.' + title: 'Building docker images.' - build-docker-image: + build-docker-images: + name: Build ${{ matrix.image.name }} runs-on: ubuntu-latest + strategy: + matrix: + image: + - name: 'Main Image' + tag: 'remnawave/node:dev' + build_args: '' + - name: 'SNI Image' + tag: 'remnawave/node:sni-dev' + build_args: 'UPSTREAM_REPO=kastov' steps: - name: Checkout uses: actions/checkout@v3 @@ -46,18 +56,18 @@ jobs: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push + - name: Build and push ${{ matrix.image.name }} uses: docker/build-push-action@v3 with: context: . platforms: linux/amd64,linux/arm64 push: true - tags: | - remnawave/node:dev + build-args: ${{ matrix.image.build_args }} + tags: ${{ matrix.image.tag }} send-finish-tg-msg: name: Send TG message - needs: [build-docker-image] + needs: [build-docker-images] runs-on: ubuntu-latest steps: - name: Checkout source code @@ -75,7 +85,7 @@ jobs: notify-on-error: runs-on: ubuntu-latest - needs: [build-docker-image] + needs: [build-docker-images] if: failure() steps: - name: Checkout source code diff --git a/Dockerfile b/Dockerfile index 48ef962..dadd715 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,8 @@ RUN npm run build --omit=dev FROM node:22-alpine ARG XRAY_CORE_VERSION=v25.8.3 -ARG XRAY_CORE_INSTALL_SCRIPT=https://raw.githubusercontent.com/remnawave/scripts/main/scripts/install-latest-xray.sh +ARG UPSTREAM_REPO=XTLS +ARG XRAY_CORE_INSTALL_SCRIPT=https://raw.githubusercontent.com/remnawave/scripts/main/scripts/install-xray.sh RUN mkdir -p /var/log/supervisor @@ -24,7 +25,7 @@ RUN apk add --no-cache \ python3 \ py3-pip \ && pip3 install --break-system-packages git+https://github.com/Supervisor/supervisor.git@4bf1e57cbf292ce988dc128e0d2c8917f18da9be \ - && curl -L ${XRAY_CORE_INSTALL_SCRIPT} | bash -s -- ${XRAY_CORE_VERSION} \ + && curl -L ${XRAY_CORE_INSTALL_SCRIPT} | bash -s -- ${XRAY_CORE_VERSION} ${UPSTREAM_REPO} \ && apk del curl git COPY supervisord.conf /etc/supervisord.conf From dfb3861d3ab70b1b5eadcb58ce9f560d6d18f4c5 Mon Sep 17 00:00:00 2001 From: kastov Date: Tue, 12 Aug 2025 19:17:29 +0300 Subject: [PATCH 09/11] chore: refactor GitHub Actions workflow for multi-image Docker builds --- .github/workflows/build-and-push.yml | 33 +++++++++++++++++++--------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-and-push.yml b/.github/workflows/build-and-push.yml index 1abc57c..32a83c4 100644 --- a/.github/workflows/build-and-push.yml +++ b/.github/workflows/build-and-push.yml @@ -24,8 +24,24 @@ jobs: ⏱️ ${{ github.event.head_commit.timestamp }} - build-docker-image: + build-docker-images: + name: Build ${{ matrix.image.name }} runs-on: ubuntu-latest + strategy: + matrix: + image: + - name: 'Main Image' + tags: | + remnawave/node:latest + remnawave/node:${{github.ref_name}} + ghcr.io/remnawave/node:latest + ghcr.io/remnawave/node:${{github.ref_name}} + build_args: '' + - name: 'SNI Image' + tags: | + remnawave/node:sni-latest + remnawave/node:sni-${{github.ref_name}} + build_args: 'UPSTREAM_REPO=kastov' steps: - name: Checkout uses: actions/checkout@v3 @@ -54,20 +70,17 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.TOKEN_GH_DEPLOY }} - - name: Build and push + - name: Build and push ${{ matrix.image.name }} uses: docker/build-push-action@v3 with: context: . platforms: linux/amd64,linux/arm64 push: true - tags: | - remnawave/node:latest - remnawave/node:${{github.ref_name}} - ghcr.io/remnawave/node:latest - ghcr.io/remnawave/node:${{github.ref_name}} + build-args: ${{ matrix.image.build_args }} + tags: ${{ matrix.image.tags }} create-release: - needs: [build-docker-image] + needs: [build-docker-images] runs-on: ubuntu-latest steps: - name: NewTag @@ -101,7 +114,7 @@ jobs: send-telegram-message: name: Send Telegram message - needs: [build-docker-image, create-release] + needs: [build-docker-images, create-release] runs-on: ubuntu-latest steps: - name: Checkout source code @@ -120,7 +133,7 @@ jobs: notify-on-error: runs-on: ubuntu-latest - needs: [build-docker-image] + needs: [build-docker-images] if: failure() steps: - name: Checkout source code From 9b800cb6052cc4a376e776f804aa3485f229a0ad Mon Sep 17 00:00:00 2001 From: kastov Date: Tue, 12 Aug 2025 19:48:00 +0300 Subject: [PATCH 10/11] fix: improve hash payload handling in get-hash-payload decorator --- src/common/decorators/get-hash-payload/get-hash-payload.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/common/decorators/get-hash-payload/get-hash-payload.ts b/src/common/decorators/get-hash-payload/get-hash-payload.ts index 16b12cd..9fe6297 100644 --- a/src/common/decorators/get-hash-payload/get-hash-payload.ts +++ b/src/common/decorators/get-hash-payload/get-hash-payload.ts @@ -5,10 +5,11 @@ import { IHashPayload, X_HASH_PAYLOAD } from '@libs/contracts/constants'; export const HashPayload = createParamDecorator((_, ctx: ExecutionContext): IHashPayload | null => { const request = ctx.switchToHttp().getRequest(); - if (request.headers[X_HASH_PAYLOAD.toLowerCase()]) { + const hashPayload = request.headers[X_HASH_PAYLOAD.toLowerCase()]; + + if (hashPayload) { try { - const hashPayload = request.headers[X_HASH_PAYLOAD.toLowerCase()] as string; - const decodedPayload = Buffer.from(hashPayload, 'base64').toString('utf-8'); + const decodedPayload = Buffer.from(hashPayload as string, 'base64').toString('utf-8'); const hashPayloadJson = JSON.parse(decodedPayload); return hashPayloadJson as IHashPayload; From d11637729ca30728f6b42fa5eaf75cee7ab91aa2 Mon Sep 17 00:00:00 2001 From: kastov Date: Tue, 12 Aug 2025 20:11:32 +0300 Subject: [PATCH 11/11] refactor: clean up logging in HttpExceptionFilter - Commented out detailed error logging to streamline response handling in the exception filter. --- src/common/exception/httpException.filter.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/exception/httpException.filter.ts b/src/common/exception/httpException.filter.ts index 5057469..ac0d1e1 100644 --- a/src/common/exception/httpException.filter.ts +++ b/src/common/exception/httpException.filter.ts @@ -30,12 +30,12 @@ export class HttpExceptionFilter implements ExceptionFilter { this.logger.error(exception.getResponse()); response.status(status).json(exception.getResponse()); } else { - this.logger.error({ - timestamp: new Date().toISOString(), - code: errorCode, - path: request.url, - message: errorMessage, - }); + // this.logger.error({ + // timestamp: new Date().toISOString(), + // code: errorCode, + // path: request.url, + // message: errorMessage, + // }); response.status(status).json({ timestamp: new Date().toISOString(), path: request.url,