Skip to content

feat(veil-core): add Veil VPN protocol module alongside Xray#38

Draft
Rxflex wants to merge 1 commit into
remnawave:devfrom
Rxflex:feat/veil-protocol
Draft

feat(veil-core): add Veil VPN protocol module alongside Xray#38
Rxflex wants to merge 1 commit into
remnawave:devfrom
Rxflex:feat/veil-protocol

Conversation

@Rxflex
Copy link
Copy Markdown

@Rxflex Rxflex commented May 12, 2026

Summary

Adds first-class support for the Veil VPN server to Remnawave Node. The implementation mirrors the existing xray-core module so the panel can drive Veil through the same start / stop / healthcheck shape it already uses for Xray — no panel-side special cases.

Why Draft. Two reasons: (1) the panel-side counterpart that actually pushes config to this endpoint is a separate PR not yet open; (2) Veil itself is pre-alpha (v0.1.0-alpha.1) and pending its first external audit. We don't want operators landing on this without that context.

What ships

REST surface under /node/veil, mirroring /node/xray:

Verb Path Purpose
POST /node/veil/start Push server.yaml + start the daemon
GET /node/veil/stop Graceful stop
GET /node/veil/healthcheck Node + daemon liveness

Contracts (libs/contract/{api,commands}/veil/):

StartVeilCommand carries:

  • serverConfig — the full server.yaml as a string. The node never parses it; veil itself does at start time. Future schema bumps don't require a node-side rebuild.
  • internals.configHash — SHA-256 the node uses to short-circuit a restart when the running config matches.
  • adminAddr — optional override for the admin API the node probes; defaults to 127.0.0.1:9090.

Service (src/modules/veil-core/veil.service.ts):

  • supervisord-controlled veil process (same pattern as XrayService)
  • Hash-based no-op restart so config-identical pushes don't tear down sessions
  • Liveness via fetch /api/version against the embedded admin API (Veil is HTTP-only; no gRPC stats analogue)
  • veil --version detection with VEIL_CORE_VERSION env override
  • Identical onModuleDestroy cleanup as XrayModule

supervisord.conf — new [program:veil] block, autostart=false / autorestart=false (panel manages lifecycle, same as Xray).

Dockerfile — dedicated veil builder stage that pulls the platform-matching binary (linux/amd64 + linux/arm64) from the upstream release, plus vlogs / verrors log aliases mirroring xlogs / xerrors.

Why this shape

Mirroring XrayModule was deliberate. Operators running mixed Xray + Veil fleets get one mental model: the panel speaks the same verbs to both daemons, and the node takes care of binary-specific lifecycle. Diverging architectures here would push that complexity into the panel where it doesn't belong.

The serverConfig string is intentionally opaque to the node so a future server.yaml schema bump in upstream doesn't require a node-side rebuild — only the veil binary in the image moves forward.

Backwards compat

Pure addition. No existing controller / contract / supervisord program is touched. Operators not running Veil see zero behavioural change; the binary is bundled in the image but never starts unless the panel explicitly issues POST /node/veil/start.

Test plan

The repo doesn't ship a unit-test harness for service classes today (no tests/ tree), so this PR adds none either. Manual verification I ran:

  • docker buildx build --platform linux/amd64,linux/arm64 . — both stages green
  • CLI smoke via supervisord:
    cli veil:start --hash <h> --addr 127.0.0.1:9090 < server.yaml
    cli veil:health
    cli veil:stop
    
  • End-to-end against a live Veil server (Lithuania, Debian 13):
    • panel → POST /node/veil/start → supervisord starts veil
    • veil binds Reality:443 / WSS:8443 / QUIC:8444
    • admin /api/version answers
    • /node/veil/healthcheck returns isVeilOnline=true

Out-of-scope (follow-ups)

  • Panel PR — the panel's add-server flow needs to emit the StartVeilCommand shape this PR introduces. Tracked separately; not opening it until this side has API approval.
  • Per-user usage accounting — Veil exposes per-user byte counters via its admin API (/api/users); wiring those into Remnawave's stats pipeline is a follow-up. Today's PR ships only lifecycle + liveness.
  • Linux AppImage / macOS DMG of the desktop client — separate concern over in the upstream Veil repo, doesn't touch the node.

File map

.github/                   (untouched)
Dockerfile                 +30  -3   add veil builder stage + vlogs/verrors
supervisord.conf           +18  -0   [program:veil]
libs/contract/
  api/controllers/
    index.ts               +1   -0   re-export
    veil.ts                +7   -0   controller name + routes enum
  api/routes.ts            +5   -0   VEIL block in REST_API
  commands/
    index.ts               +1   -0   re-export
    veil/
      index.ts             +3   -0
      start.command.ts     +52  -0   Zod schemas
      stop.command.ts      +18  -0
      get-node-health-check.command.ts  +25  -0
src/modules/
  remnawave-node.modules.ts  +2  -0   wire VeilModule
  veil-core/
    veil.controller.ts     +56  -0
    veil.service.ts        +320 -0
    veil.module.ts         +24  -0
    commands/
      index.ts             +3   -0
      stop-veil/{...}.ts   +35  -0
    dtos/                  +25  -0   nestjs-zod DTOs
    models/                +60  -0   response models

Notes for reviewers

  • Style: Prettier + ESLint clean against the repo's existing config. 4-space indent, alphabetical @common / @libs import groups, same as xray-core.
  • No new top-level dependencies. Uses only what xray-core already imports (@kastov/node-supervisord, p-retry, enhanced-ms, semver, table, pkg-types).
  • Liveness probe uses Node 18+'s built-in fetch, no undici/axios dep.

Adds first-class support for the Veil server (https://github.com/redstone-md/veil)
to Remnawave Node. The implementation mirrors the existing xray-core
module so the panel side can drive Veil through the same start /
stop / healthcheck shape it already uses for Xray, no panel-side
special cases.

What ships
==========

* New REST surface under /node/veil:
    POST /node/veil/start         — push server.yaml + start daemon
    GET  /node/veil/stop          — graceful stop
    GET  /node/veil/healthcheck   — node + daemon liveness

* libs/contract/commands/veil — Zod schemas for the three commands.
  StartVeilCommand carries:
    - serverConfig:  the full server.yaml as a string (panel side
                     stays the source of truth for transport
                     topology, decoy origin, transports, etc.)
    - internals.configHash: SHA-256 the node uses to short-circuit a
                     restart when the running config already matches.
    - adminAddr:     optional override for the embedded admin API
                     address the node probes for liveness; defaults
                     to 127.0.0.1:9090.

* src/modules/veil-core/veil.service.ts:
    - supervisord-controlled veil process
    - hash-based no-op restart (matches XrayService's behaviour)
    - admin-API liveness probe via fetch to /api/version (replaces
      Xray's gRPC stats probe; Veil is HTTP-only)
    - veil --version detection with VEIL_CORE_VERSION env override
    - identical onModuleDestroy cleanup pathway as XrayModule

* supervisord.conf: new [program:veil] block, autostart=false /
  autorestart=false (panel manages lifecycle, same as Xray).

* Dockerfile:
    - dedicated builder stage that pulls the platform-matching
      `veil` static binary (linux/amd64 + linux/arm64) from the
      upstream release, COPIed into the runtime image
    - vlogs / verrors aliases mirroring xlogs / xerrors

Why this shape
==============

Mirroring XrayModule was deliberate. Operators running mixed
Xray + Veil fleets get one mental model: the panel speaks the same
verbs to both daemons, and the node takes care of binary-specific
lifecycle. Diverging architectures here would push that
complexity into the panel where it doesn't belong.

The serverConfig string is opaque to the node (parsed by veil
itself at start time) so a future server.yaml schema bump in
upstream doesn't require a node-side rebuild — only the
veil binary in the image moves forward.

Tests
=====

Existing repo has no unit-test harness for service classes (there
is no tests/ tree at all today), so this PR adds none. Manual
verification:

  - docker build .          # multi-arch, both stages green
  - cli mode + supervisord ctl
    cli veil:start --hash <h> --addr 127.0.0.1:9090 < server.yaml
    cli veil:health
    cli veil:stop
  - end-to-end:
    panel → POST /node/veil/start → supervisord starts veil →
    veil binds reality:443 / wss:8443 / quic:8444 → admin /api/version
    answers → healthcheck returns isVeilOnline=true.

Backwards compat
================

Pure addition: no existing controller / contract / supervisord
program touched. Operators not running Veil see zero behavioural
change; the binary is bundled in the image but never starts.

Marked Draft because:
  1. Panel-side counterpart (panel/src/.../veil) is a separate PR
     not yet open.
  2. Veil itself is pre-alpha (v0.1.0-alpha.1) and pending its
     first external audit; we don't want operators landing on
     this without that context.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant