From 4e4f5596e9bfdf5879cdaf6742cd2a52b86192ae Mon Sep 17 00:00:00 2001 From: kastov Date: Fri, 14 Aug 2026 14:26:45 +0300 Subject: [PATCH] refactor: integration modules --- .github/workflows/build-and-push.yml | 4 ---- docker/Dockerfile | 4 ++++ libs/contract/commands/xray/start.command.ts | 3 ++- libs/contract/models/index.ts | 1 + libs/contract/models/node-metadata.schema.ts | 11 ++++++++++ libs/contract/package.json | 2 +- .../integrations.contract.ts | 15 ++++++++++++- .../integrations.module.ts | 22 ++++++++++++++++--- .../integrations.service.ts | 19 ++++++++++++++-- src/modules/xray-core/xray.service.ts | 5 ++++- 10 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 libs/contract/models/node-metadata.schema.ts diff --git a/.github/workflows/build-and-push.yml b/.github/workflows/build-and-push.yml index afb0525..5088a89 100644 --- a/.github/workflows/build-and-push.yml +++ b/.github/workflows/build-and-push.yml @@ -192,10 +192,6 @@ jobs: - `remnawave/node:${{github.ref_name}}` - `ghcr.io/remnawave/node:${{github.ref_name}}` - Docker images with bundled HAProxy: - - `remnawave/node:latest-hp` / `remnawave/node:${{github.ref_name}}-hp` - - `ghcr.io/remnawave/node:latest-hp` / `ghcr.io/remnawave/node:${{github.ref_name}}-hp` - send-telegram-message: name: Send Telegram message needs: [merge, create-release] diff --git a/docker/Dockerfile b/docker/Dockerfile index 1d715cb..a3a1c96 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -30,6 +30,9 @@ RUN apk add --no-cache curl \ && rm -f /tmp/asn-prefixes-lmdb.tar.gz +FROM scratch AS integration-bin + + FROM node:24.19-trixie-slim ARG S6_OVERLAY_VERSION=3.2.3.0 @@ -80,6 +83,7 @@ RUN apt-get update \ ARG INTEGRATIONS="" RUN --mount=type=bind,source=docker/integrations,target=/mnt/integrations \ + --mount=type=bind,from=integration-bin,target=/mnt/bin \ sh /mnt/integrations/install.sh "${INTEGRATIONS}" ENV NODE_ENV=production diff --git a/libs/contract/commands/xray/start.command.ts b/libs/contract/commands/xray/start.command.ts index 4af6776..097bdb7 100644 --- a/libs/contract/commands/xray/start.command.ts +++ b/libs/contract/commands/xray/start.command.ts @@ -1,12 +1,13 @@ import { z } from 'zod'; -import { NodeSystemSchema } from '../../models'; import { REST_API } from '../../api'; +import { NodeMetadataSchema, NodeSystemSchema } from '../../models'; export namespace StartXrayCommand { export const url = REST_API.XRAY.START; export const RequestSchema = z.object({ internals: z.object({ + metadata: NodeMetadataSchema.optional(), forceRestart: z.boolean().default(false), hashes: z.object({ emptyConfig: z.string(), diff --git a/libs/contract/models/index.ts b/libs/contract/models/index.ts index bd19a76..821d616 100644 --- a/libs/contract/models/index.ts +++ b/libs/contract/models/index.ts @@ -1,3 +1,4 @@ export * from './node-system.schema'; export * from './torrent-blocker.report.schema'; export * from './xray-webhook.schema'; +export * from './node-metadata.schema'; diff --git a/libs/contract/models/node-metadata.schema.ts b/libs/contract/models/node-metadata.schema.ts new file mode 100644 index 0000000..0535215 --- /dev/null +++ b/libs/contract/models/node-metadata.schema.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +export const NodeMetadataSchema = z.object({ + name: z.string(), + uuid: z.string(), + id: z.number(), + tags: z.array(z.string()), + countryCode: z.string(), +}); + +export type TNodeMetadata = z.infer; diff --git a/libs/contract/package.json b/libs/contract/package.json index 2e4b6d8..3ce8d37 100644 --- a/libs/contract/package.json +++ b/libs/contract/package.json @@ -1,6 +1,6 @@ { "name": "@remnawave/node-contract", - "version": "2.9.0", + "version": "2.9.1", "description": "A node-contract library for Remnawave Panel", "keywords": [], "homepage": "https://github.com/remnawave", diff --git a/src/integration-modules/integrations.contract.ts b/src/integration-modules/integrations.contract.ts index 5e860e9..ff507ee 100644 --- a/src/integration-modules/integrations.contract.ts +++ b/src/integration-modules/integrations.contract.ts @@ -1,3 +1,7 @@ +import { Type } from '@nestjs/common'; + +import { TNodeMetadata } from '@libs/contracts/models'; + export const NODE_INTEGRATIONS = 'NODE_INTEGRATIONS' as const; export interface INodeIntegrationResult { @@ -7,7 +11,16 @@ export interface INodeIntegrationResult { export interface INodeIntegration { readonly name: string; - sync(coreConfig: unknown): Promise; + sync( + integrationConfig: Record, + nodeMetadata: TNodeMetadata, + ): Promise; stop(): Promise; } + +export interface INodeIntegrationDescriptor { + module: Type; + service: Type; + isAvailable: () => boolean; +} diff --git a/src/integration-modules/integrations.module.ts b/src/integration-modules/integrations.module.ts index 4fdf736..86aed3a 100644 --- a/src/integration-modules/integrations.module.ts +++ b/src/integration-modules/integrations.module.ts @@ -1,18 +1,34 @@ import { Global, Module } from '@nestjs/common'; +import { ConditionalModule } from '@nestjs/config'; -import { INodeIntegration, NODE_INTEGRATIONS } from './integrations.contract'; +import { + INodeIntegration, + INodeIntegrationDescriptor, + NODE_INTEGRATIONS, +} from './integrations.contract'; import { IntegrationsService } from './integrations.service'; +const context = require.context('./', true, /\.integration\.ts$/); + +const DESCRIPTORS: INodeIntegrationDescriptor[] = context + .keys() + .map((key) => (context(key) as { descriptor: INodeIntegrationDescriptor }).descriptor); + @Global() @Module({ - imports: [], + imports: DESCRIPTORS.map((descriptor) => + ConditionalModule.registerWhen(descriptor.module, descriptor.isAvailable, { debug: false }), + ), providers: [ IntegrationsService, { provide: NODE_INTEGRATIONS, useFactory: (...integrations: (INodeIntegration | undefined)[]) => integrations.filter((integration) => integration !== undefined), - inject: [], + inject: DESCRIPTORS.map((descriptor) => ({ + token: descriptor.service, + optional: true, + })), }, ], exports: [IntegrationsService], diff --git a/src/integration-modules/integrations.service.ts b/src/integration-modules/integrations.service.ts index 83282df..5d3fa74 100644 --- a/src/integration-modules/integrations.service.ts +++ b/src/integration-modules/integrations.service.ts @@ -1,5 +1,7 @@ import { Inject, Injectable, Logger } from '@nestjs/common'; +import { TNodeMetadata } from '@libs/contracts/models'; + import { INodeIntegration, INodeIntegrationResult, @@ -18,14 +20,21 @@ export class IntegrationsService { } } - public async sync(coreConfig: unknown): Promise { + public async sync( + integrationConfig: unknown | undefined, + nodeMetadata: TNodeMetadata | undefined, + ): Promise { if (this.integrations.length === 0) return { error: null }; + if (!nodeMetadata) return { error: 'Node metadata is missing.' }; const errors: string[] = []; for (const integration of this.integrations) { try { - const { error } = await integration.sync(coreConfig); + const { error } = await integration.sync( + this.asRecord(integrationConfig), + nodeMetadata, + ); if (error) errors.push(`[${integration.name}] ${error}`); } catch (error) { @@ -49,4 +58,10 @@ export class IntegrationsService { } } } + + private asRecord(value: unknown): Record { + return typeof value === 'object' && value !== null && !Array.isArray(value) + ? (value as Record) + : {}; + } } diff --git a/src/modules/xray-core/xray.service.ts b/src/modules/xray-core/xray.service.ts index 4b59f05..5f506a6 100644 --- a/src/modules/xray-core/xray.service.ts +++ b/src/modules/xray-core/xray.service.ts @@ -124,7 +124,10 @@ export class XrayService implements OnApplicationBootstrap { this.isXrayStartedProccesing = true; try { - const integrations = await this.integrations.sync(body.xrayConfig); + const integrations = await this.integrations.sync( + body.xrayConfig.integrations, + body.internals.metadata, + ); if (integrations.error) { this.logger.error(`Failed to sync integrations: ${integrations.error}`);