feat: add health-check endpoint

This commit is contained in:
kastov 2025-03-04 04:11:45 +03:00
parent dd4a95f4e5
commit d40387aa23
No known key found for this signature in database
GPG key ID: 1B27BE29057F4C90
13 changed files with 435 additions and 243 deletions

View file

@ -4,4 +4,5 @@ export const XRAY_ROUTES = {
START: 'start',
STOP: 'stop',
STATUS: 'status',
NODE_HEALTH_CHECK: 'healthcheck',
} as const;

View file

@ -7,6 +7,7 @@ export const REST_API = {
START: `${ROOT}/${CONTROLLERS.XRAY_CONTROLLER}/${CONTROLLERS.XRAY_ROUTES.START}`,
STOP: `${ROOT}/${CONTROLLERS.XRAY_CONTROLLER}/${CONTROLLERS.XRAY_ROUTES.STOP}`,
STATUS: `${ROOT}/${CONTROLLERS.XRAY_CONTROLLER}/${CONTROLLERS.XRAY_ROUTES.STATUS}`,
NODE_HEALTH_CHECK: `${ROOT}/${CONTROLLERS.XRAY_CONTROLLER}/${CONTROLLERS.XRAY_ROUTES.NODE_HEALTH_CHECK}`,
},
STATS: {
GET_USER_ONLINE_STATUS: `${ROOT}/${CONTROLLERS.STATS_CONTROLLER}/${CONTROLLERS.STATS_ROUTES.GET_USER_ONLINE_STATUS}`,

View file

@ -0,0 +1,17 @@
import { z } from 'zod';
import { REST_API } from '../../api';
export namespace GetNodeHealthCheckCommand {
export const url = REST_API.XRAY.NODE_HEALTH_CHECK;
export const ResponseSchema = z.object({
response: z.object({
isAlive: z.boolean(),
xrayInternalStatusCached: z.boolean(),
xrayVersion: z.string().nullable(),
}),
});
export type Response = z.infer<typeof ResponseSchema>;
}

View file

@ -1,3 +1,4 @@
export * from './get-node-health-check.command';
export * from './get-status-and-version.command';
export * from './start.command';
export * from './stop.command';

View file

@ -1,6 +1,6 @@
{
"name": "@remnawave/node-contract",
"version": "0.3.4",
"version": "0.3.5",
"description": "A node-contract library for Remnawave Panel",
"main": "build/index.js",
"types": "build/index.d.ts",

579
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "@remnawave/node",
"version": "1.1.0",
"version": "1.2.0",
"description": "",
"private": true,
"license": "AGPL-3.0-only",
@ -27,19 +27,19 @@
},
"dependencies": {
"@cjs-exporter/execa": "9.5.2",
"@nestjs/common": "11.0.10",
"@nestjs/config": "4.0.0",
"@nestjs/core": "11.0.10",
"@nestjs/common": "11.0.11",
"@nestjs/config": "4.0.1",
"@nestjs/core": "11.0.11",
"@nestjs/jwt": "11.0.0",
"@nestjs/passport": "11.0.5",
"@nestjs/platform-express": "11.0.10",
"@nestjs/platform-express": "11.0.11",
"@remnawave/xtls-sdk": "0.2.0",
"@remnawave/xtls-sdk-nestjs": "0.2.2",
"enhanced-ms": "^3.0.0",
"helmet": "^7.1.0",
"husky": "9.0.11",
"morgan": "^1.10.0",
"nest-winston": "^1.9.7",
"nest-winston": "^1.10.2",
"nestjs-zod": "3.0.0",
"node-object-hash": "^3.1.1",
"object-hash": "^3.0.0",
@ -47,23 +47,23 @@
"passport-jwt": "4.0.1",
"pretty-bytes": "^5.6.0",
"reflect-metadata": "0.2.2",
"rxjs": "7.8.1",
"rxjs": "7.8.2",
"semver": "^7.6.3",
"systeminformation": "^5.23.5",
"systeminformation": "^5.25.5",
"table": "^6.9.0",
"winston": "^3.17.0",
"zod": "^3.22.4"
},
"devDependencies": {
"@nestjs/cli": "11.0.2",
"@nestjs/schematics": "11.0.0",
"@nestjs/cli": "11.0.5",
"@nestjs/schematics": "11.0.2",
"@types/bcryptjs": "^2.4.6",
"@types/express": "^4.17.17",
"@types/express": "^5.0.0",
"@types/js-yaml": "^4.0.9",
"@types/jsonwebtoken": "^9.0.7",
"@types/mjml": "^4.7.4",
"@types/morgan": "^1.9.9",
"@types/node": "^20.3.1",
"@types/node": "^22.13.9",
"@types/nunjucks": "^3.2.6",
"@types/object-hash": "^3.0.6",
"@types/passport-jwt": "^4.0.1",
@ -82,4 +82,4 @@
"tsconfig-paths": "^4.2.0",
"typescript": "5.7.2"
}
}
}

View file

@ -0,0 +1,7 @@
import { createZodDto } from 'nestjs-zod';
import { GetNodeHealthCheckCommand } from '@libs/contracts/commands';
export class GetNodeHealthCheckResponseDto extends createZodDto(
GetNodeHealthCheckCommand.ResponseSchema,
) {}

View file

@ -1,3 +1,4 @@
export * from './get-node-health-check.dto';
export * from './get-xray-status-and-version.dto';
export * from './start-xray.dto';
export * from './stop-xray.dto';

View file

@ -0,0 +1,10 @@
export class GetNodeHealthCheckResponseModel {
public isAlive: boolean;
public xrayInternalStatusCached: boolean;
public xrayVersion: null | string;
constructor(isAlive: boolean, xrayInternalStatusCached: boolean, xrayVersion: null | string) {
this.isAlive = isAlive;
this.xrayInternalStatusCached = xrayInternalStatusCached;
this.xrayVersion = xrayVersion;
}
}

View file

@ -1,3 +1,4 @@
export * from './get-node-health-check.model';
export * from './get-xray-status-and-version.response.model';
export * from './start-xray.response.model';
export * from './stop-xray.response.model';

View file

@ -6,6 +6,7 @@ import { JwtDefaultGuard } from '@common/guards/jwt-guards/def-jwt-guard';
import { errorHandler } from '@common/helpers/error-handler.helper';
import {
GetNodeHealthCheckResponseDto,
GetXrayStatusAndVersionResponseDto,
StartXrayRequestDto,
StartXrayResponseDto,
@ -53,4 +54,14 @@ export class XrayController {
response: data,
};
}
@Get(XRAY_ROUTES.NODE_HEALTH_CHECK)
public async getNodeHealthCheck(): Promise<GetNodeHealthCheckResponseDto> {
const response = await this.xrayService.getNodeHealthCheck();
const data = errorHandler(response);
return {
response: data,
};
}
}

View file

@ -14,6 +14,7 @@ import { generateApiConfig } from '@common/utils/generate-api-config';
import { getSystemStats } from '@common/utils/get-system-stats';
import {
GetNodeHealthCheckResponseModel,
GetXrayStatusAndVersionResponseModel,
StartXrayResponseModel,
StopXrayResponseModel,
@ -269,6 +270,26 @@ export class XrayService implements OnApplicationBootstrap, OnModuleInit {
}
}
public async getNodeHealthCheck(): Promise<ICommandResponse<GetNodeHealthCheckResponseModel>> {
try {
return {
isOk: true,
response: new GetNodeHealthCheckResponseModel(
true,
this.isXrayOnline,
this.xrayVersion,
),
};
} catch (error) {
this.logger.error(`Failed to get Xray status and version ${error}`);
return {
isOk: true,
response: new GetNodeHealthCheckResponseModel(false, false, null),
};
}
}
public async killAllXrayProcesses(): Promise<void> {
try {
await execa('supervisorctl', ['stop', 'xray'], { reject: false });