Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/build-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,16 @@ jobs:
image:
- name: 'Main Image'
tag: 'remnawave/node:dev'
dockerfile: 'Dockerfile'
build_args: ''
- name: 'SNI Image'
tag: 'remnawave/node:sni-dev'
dockerfile: 'Dockerfile'
build_args: 'UPSTREAM_REPO=kastov'
- name: 'Bun Image'
tag: 'remnawave/node:bun-dev'
dockerfile: 'Dockerfile.Bun'
build_args: ''
steps:
- name: Checkout
uses: actions/checkout@v3
Expand All @@ -60,6 +66,7 @@ jobs:
uses: docker/build-push-action@v3
with:
context: .
file: ${{ matrix.image.dockerfile }}
platforms: linux/amd64,linux/arm64
push: true
build-args: ${{ matrix.image.build_args }}
Expand Down
44 changes: 44 additions & 0 deletions Dockerfile.Bun
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
FROM node:22-alpine AS build
WORKDIR /opt/app
ADD . .
RUN npm ci --legacy-peer-deps
RUN npm run build --omit=dev

RUN npm ci --omit=dev --legacy-peer-deps \
&& npm cache clean --force

FROM oven/bun:1.2.20-alpine AS base

ARG XRAY_CORE_VERSION=v25.8.3
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

WORKDIR /opt/app
COPY --from=build /opt/app/dist ./dist
COPY --from=build /opt/app/node_modules ./node_modules


RUN apk add --no-cache \
curl \
unzip \
bash \
git \
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} ${UPSTREAM_REPO} \
&& apk del curl git

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

ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]

CMD ["bun", "run", "start:bun"]
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@remnawave/node",
"version": "2.1.0",
"version": "2.1.1",
"description": "Remnawave Node",
"private": false,
"type": "commonjs",
Expand All @@ -21,6 +21,7 @@
"start:dev": "NODE_ENV=development nest start --watch",
"start:debug": "NODE_ENV=development nest start --debug --watch",
"start:prod": "NODE_ENV=production node dist/src/main",
"start:bun": "NODE_ENV=production bun run dist/src/main",
"prepare": "husky",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
"lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
Expand Down
71 changes: 38 additions & 33 deletions src/modules/handler/handler.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,15 @@ export class HandlerService {
const { data: requestData, hashData } = data;
const response: Array<ISdkResponse<AddUserResponseModelFromSdk>> = [];

const inboundsTags = this.internalService.getXtlsConfigInbounds();
for (const item of requestData) {
this.internalService.addXtlsConfigInbound(item.tag);
}

for (const tag of inboundsTags) {
for (const tag of this.internalService.getXtlsConfigInbounds()) {
this.logger.debug(`Removing user: ${requestData[0].username} from tag: ${tag}`);

await this.xtlsApi.handler.removeUser(tag, requestData[0].username);

if (hashData.prevVlessUuid) {
this.internalService.removeUserFromInbound(tag, hashData.prevVlessUuid);
} else {
Expand Down Expand Up @@ -119,46 +123,18 @@ export class HandlerService {
}
}

public async getInboundUsers(
tag: string,
): Promise<ICommandResponse<GetInboundUsersResponseModel>> {
try {
// TODO: add a better way to return users (trojan, vless, etc)
const response = await this.xtlsApi.handler.getInboundUsers(tag);

if (!response.isOk || !response.data) {
return {
isOk: false,
code: ERRORS.FAILED_TO_GET_INBOUND_USERS.code,
response: new GetInboundUsersResponseModel([]),
};
}

return {
isOk: true,
response: new GetInboundUsersResponseModel(response.data.users),
};
} catch (error) {
this.logger.error(error);
return {
isOk: false,
code: ERRORS.FAILED_TO_GET_INBOUND_USERS.code,
response: new GetInboundUsersResponseModel([]),
};
}
}

public async removeUser(
data: IRemoveUserRequest,
): Promise<ICommandResponse<RemoveUserResponseModel>> {
try {
const { username, hashData } = data;
const response: Array<ISdkResponse<RemoveUserResponseModelFromSdk>> = [];

const inboundsTags = this.internalService.getXtlsConfigInbounds();
for (const tag of this.internalService.getXtlsConfigInbounds()) {
this.logger.debug(`Removing user: ${username} from tag: ${tag}`);

for (const tag of inboundsTags) {
const tempRes = await this.xtlsApi.handler.removeUser(tag, username);

this.internalService.removeUserFromInbound(tag, hashData.vlessUuid);
response.push(tempRes);
}
Expand Down Expand Up @@ -192,6 +168,35 @@ export class HandlerService {
}
}

public async getInboundUsers(
tag: string,
): Promise<ICommandResponse<GetInboundUsersResponseModel>> {
try {
// TODO: add a better way to return users (trojan, vless, etc)
const response = await this.xtlsApi.handler.getInboundUsers(tag);

if (!response.isOk || !response.data) {
return {
isOk: false,
code: ERRORS.FAILED_TO_GET_INBOUND_USERS.code,
response: new GetInboundUsersResponseModel([]),
};
}

return {
isOk: true,
response: new GetInboundUsersResponseModel(response.data.users),
};
} catch (error) {
this.logger.error(error);
return {
isOk: false,
code: ERRORS.FAILED_TO_GET_INBOUND_USERS.code,
response: new GetInboundUsersResponseModel([]),
};
}
}

public async getInboundUsersCount(
tag: string,
): Promise<ICommandResponse<GetInboundUsersCountResponseModel>> {
Expand Down
25 changes: 21 additions & 4 deletions src/modules/internal/internal.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export class InternalService {
private xrayConfig: null | Record<string, unknown> = null;
private emptyConfigHash: null | string = null;
private inboundsHashMap: Map<string, HashedSet> = new Map();
private xtlsConfigInbounds: string[] = [];
private xtlsConfigInbounds: Set<string> = new Set();

constructor() {}

Expand Down Expand Up @@ -69,7 +69,7 @@ export class InternalService {
}

for (const [inboundTag, usersSet] of this.inboundsHashMap) {
this.xtlsConfigInbounds.push(inboundTag);
this.xtlsConfigInbounds.add(inboundTag);
this.logger.log(`Inbound ${inboundTag} contains ${usersSet.size} user(s)`);
}
}
Expand Down Expand Up @@ -138,6 +138,12 @@ export class InternalService {
const usersSet = this.inboundsHashMap.get(inboundTag);

if (!usersSet) {
this.logger.warn(
`Inbound ${inboundTag} not found in inboundsHashMap, creating new one`,
);

this.inboundsHashMap.set(inboundTag, new HashedSet([user]));

return;
}

Expand All @@ -152,15 +158,26 @@ export class InternalService {
}

usersSet.delete(user);

if (usersSet.size === 0) {
this.xtlsConfigInbounds.delete(inboundTag);
this.inboundsHashMap.delete(inboundTag);

this.logger.warn(`Inbound ${inboundTag} has no users, clearing inboundsHashMap.`);
}
}

public getXtlsConfigInbounds(): string[] {
public getXtlsConfigInbounds(): Set<string> {
return this.xtlsConfigInbounds;
}

public addXtlsConfigInbound(inboundTag: string): void {
this.xtlsConfigInbounds.add(inboundTag);
}

public cleanup(): void {
this.inboundsHashMap.clear();
this.xtlsConfigInbounds = [];
this.xtlsConfigInbounds.clear();
this.xrayConfig = null;
this.emptyConfigHash = null;
}
Expand Down
24 changes: 18 additions & 6 deletions src/modules/xray-core/xray.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class XrayService implements OnApplicationBootstrap, OnModuleInit {
this.logger.error(errMessage);

return {
isOk: false,
isOk: true,
response: new StartXrayResponseModel(false, null, errMessage, null, {
version: this.nodeVersion,
}),
Expand All @@ -116,11 +116,21 @@ export class XrayService implements OnApplicationBootstrap, OnModuleInit {

this.isXrayStartedProccesing = true;

const fullConfig = generateApiConfig(config);

if (this.isXrayOnline && !this.disableHashedSetCheck) {
const isNeedRestart = this.internalService.isNeedRestartCore(hashPayload);
if (!isNeedRestart) {
const { isOk } = await this.xtlsSdk.stats.getSysStats();

let shouldRestart = false;

if (isOk) {
shouldRestart = this.internalService.isNeedRestartCore(hashPayload);
} else {
this.isXrayOnline = false;
shouldRestart = true;

this.logger.warn(`Xray Core health check failed, restarting...`);
}

if (!shouldRestart) {
return {
isOk: true,
response: new StartXrayResponseModel(
Expand All @@ -136,6 +146,8 @@ export class XrayService implements OnApplicationBootstrap, OnModuleInit {
}
}

const fullConfig = generateApiConfig(config);

this.internalService.extractUsersFromConfig(hashPayload, fullConfig);

const xrayProcess = await this.restartXrayProcess();
Expand All @@ -152,7 +164,7 @@ export class XrayService implements OnApplicationBootstrap, OnModuleInit {
}

return {
isOk: false,
isOk: true,
response: new StartXrayResponseModel(false, null, xrayProcess.error, null, {
version: this.nodeVersion,
}),
Expand Down