Skip to content

Add Surge subscription support#179

Open
RickeyRen wants to merge 8 commits into
remnawave:mainfrom
RickeyRen:feature/surge-direct-import
Open

Add Surge subscription support#179
RickeyRen wants to merge 8 commits into
remnawave:mainfrom
RickeyRen:feature/surge-direct-import

Conversation

@RickeyRen
Copy link
Copy Markdown

@RickeyRen RickeyRen commented Jun 1, 2026

Summary

  • Add a Surge subscription template type and generator that emits full Surge config files.
  • Seed the default Surge template, response rule, and subscription page metadata for new installs.
  • Extend backend contracts and response-rule handling so Surge user agents receive Surge configs from the normal subscription URL.

Companion PRs

Validation

  • node -r ts-node/register -r tsconfig-paths/register test/surge-generator.test.ts
  • node -r ts-node/register -r tsconfig-paths/register test/surge-support.test.ts
  • npm run build
  • Docker smoke test with restored production-like data: base subscription URL returns [General], [Proxy], [Proxy Group], [Rule] and 6 proxy entries for a Surge user agent.

@snyk-io
Copy link
Copy Markdown

snyk-io Bot commented Jun 1, 2026

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@what-the-diff
Copy link
Copy Markdown

what-the-diff Bot commented Jun 1, 2026

PR Summary

  • Introduced 'SURGE' Response and Template Types
    The team has introduced a new response type called 'SURGE'. This also necessitated an update in the recognized template types to include 'SURGE'. Changes of this nature were done in multiple parts of the project such as the request template type, the schema, and other related areas.

  • Added Rules and Services for 'SURGE' Clients
    A new rule has been established for 'SURGE' clients that helps in generating specific responses suitable for them. In addition, a 'SURGE' generator service was created that's responsible for generating configurations specifically tailored to 'SURGE' based on the supplied proxy configurations. Logic to handle all this has been embedded within the response rule matcher service and the render templates service.

  • Created Default Surge Templates
    To facilitate ease of use, a default surge template has been created and seeded within the applicable areas of the project.

  • Supporting Changes in Project Structure and Config
    Necessary changes were made to the project's Docker build process by introducing a '.dockerignore' file. Furthermore, the project config was updated to define content types specific to 'SURGE'.

  • Comprehensive Testing for 'SURGE'
    Numerous tests have been added to ensure the proper functionality of all the 'SURGE'-related changes. These tests range from basic assertion functions to more complex validation tests for 'SURGE' configurations and response types. The platform compatibility for 'SURGE' has also been validated through tests.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Jun 1, 2026

Greptile Summary

This PR adds end-to-end Surge subscription support: a new SurgeGeneratorService that produces plain-text Surge config files, a default template and seed entry, a new SURGE response-rule type that matches on the Surge user-agent, and subscription-page metadata for both iOS and macOS.

  • New generator (surge.generator.service.ts): filters out unsupported protocols (vless, reality) and transports (grpc/kcp/xhttp), then emits trojan, shadowsocks, and hysteria2 proxy lines via marker-based template substitution.
  • Plumbing: SUBSCRIPTION_TEMPLATE_TYPE, REQUEST_TEMPLATE_TYPE, RESPONSE_RULES_RESPONSE_TYPES, the render-service switch, and the config-types content-type map are all extended consistently.
  • Seed/default: the default Surge template, a seeder case, and a new user-agent response rule are added; subscription-page configs for iOS and macOS include Surge app blocks.

Confidence Score: 3/5

Safe to merge after the httpupgrade/WebSocket transport mismatch in the Surge generator is resolved; all other changes are additive and well-isolated.

The generator emits ws = true for both ws and httpupgrade transports, but Surge's WebSocket mode and xray-core's httpupgrade transport use different HTTP-level handshakes. Trojan proxies backed by httpupgrade would silently receive a broken Surge config that fails to connect, with no error surfaced to the user beyond an empty subscription. The rest of the integration — constants, seeder, response-rule matching, template caching, render-service wiring — is correct and follows established patterns.

src/modules/subscription-template/generators/surge.generator.service.ts — specifically the buildTrojanProxy method and its handling of the httpupgrade transport.

Important Files Changed

Filename Overview
src/modules/subscription-template/generators/surge.generator.service.ts New Surge config generator; treats httpupgrade transport identically to WebSocket (ws=true), which may produce broken proxy configs
src/modules/subscription-template/subscription-template.service.ts Adds SURGE to isYamlTemplate group (correct for storage/update logic) and returns raw string from cache for SURGE type; semantically the naming is misleading since Surge templates are plain text, not YAML
prisma/seed/default/response-rules.ts Adds Surge Clients rule with ^Surge regex and caseSensitive:false; functionally correct, minor style inconsistency with other rules
src/modules/subscription-template/render-templates.service.ts Cleanly wires in SurgeGeneratorService for the SURGE response type case; follows the same pattern as other generators
src/modules/subscription-response-rules/services/response-rules-matcher.service.ts Adds SURGE to handleOverrideClientType switch; correctly maps REQUEST_TEMPLATE_TYPE.SURGE to SUBSCRIPTION_TEMPLATE_TYPE.SURGE
prisma/seed/seeders/4_seed-subscription-template.ts Adds SURGE case to the seeder switch; correctly idempotent (skips if Default already exists) and seeds from DEFAULT_TEMPLATE_SURGE
src/modules/subscription-template/constants/default-templates.ts Adds DEFAULT_TEMPLATE_SURGE as a plain-text Surge config with both remnawave marker placeholders; minimal but functional default

Sequence Diagram

sequenceDiagram
    participant C as Surge Client
    participant S as Subscription Endpoint
    participant RM as ResponseRulesMatcherService
    participant RT as RenderTemplatesService
    participant SG as SurgeGeneratorService
    participant TS as SubscriptionTemplateService
    participant DB as Database/Cache

    C->>S: GET /sub/:uuid (User-Agent: Surge/5.x)
    S->>RM: matchRules(headers, rules)
    RM-->>S: "{ matched: true, responseType: 'SURGE' }"
    S->>RT: generateSubscription(SURGE, hosts)
    RT->>SG: generateConfig(formattedHosts, templateName)
    SG->>TS: getCachedTemplateByType('SURGE')
    TS->>DB: check cache
    alt cache miss
        DB-->>TS: null
        TS->>DB: "query templateYaml where templateType=SURGE"
        DB-->>TS: raw plain-text template
        TS->>DB: set cache (raw string, 1hr)
    end
    DB-->>TS: template string
    TS-->>SG: template (string)
    SG->>SG: filter hosts (exclude vless, grpc/kcp/xhttp, reality)
    SG->>SG: buildProxyLine per host (trojan/ss/hysteria2)
    SG->>SG: renderTemplate (replace markers)
    SG-->>RT: Surge config text
    RT-->>S: "{ subscription, contentType: 'text/plain' }"
    S-->>C: 200 text/plain Surge config
Loading

Reviews (1): Last reviewed commit: "checkpoint: add backend dockerignore" | Re-trigger Greptile

Comment on lines +80 to +81
if (host.transport === 'ws' || host.transport === 'httpupgrade') {
params.ws = true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 HTTP Upgrade and WebSocket are distinct transport protocols. Surge's ws = true enables standard WebSocket framing (RFC 6455), while xray-core's httpupgrade transport uses a plain HTTP/1.1 Upgrade handshake without WebSocket framing. Generating ws = true for an httpupgrade-backed server will cause Surge to handshake incorrectly and the connection will fail. The safest fix is to return null for httpupgrade transports (skipping the proxy) rather than emitting a broken config line.

Suggested change
if (host.transport === 'ws' || host.transport === 'httpupgrade') {
params.ws = true;
if (host.transport === 'ws') {
params.ws = true;

Comment on lines +80 to +84
const isYamlTemplate =
template.templateType === 'MIHOMO' ||
template.templateType === 'STASH' ||
template.templateType === 'CLASH';
template.templateType === 'CLASH' ||
template.templateType === 'SURGE';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Surge templates are plain text, not YAML, but grouping them under isYamlTemplate means any update attempt with a JSON payload returns the error TEMPLATE_JSON_NOT_ALLOWED_FOR_YAML_TEMPLATE. While the validation logic itself is correct (SURGE templates live in templateYaml), this error message will confuse users who try to update a Surge template with JSON. Consider a more accurate variable name or a separate isPlainTextTemplate check.

Suggested change
const isYamlTemplate =
template.templateType === 'MIHOMO' ||
template.templateType === 'STASH' ||
template.templateType === 'CLASH';
template.templateType === 'CLASH' ||
template.templateType === 'SURGE';
const isYamlTemplate =
template.templateType === 'MIHOMO' ||
template.templateType === 'STASH' ||
template.templateType === 'CLASH';
const isPlainTextTemplate = template.templateType === 'SURGE';
const isTextBasedTemplate = isYamlTemplate || isPlainTextTemplate;

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +90 to +95
{
headerName: 'user-agent',
operator: 'REGEX',
value: '^Surge',
caseSensitive: false,
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 All other user-agent rules use lowercase patterns with caseSensitive: false (e.g., ^clash, ^stash, ^sfa|sfi|…). The matchCondition implementation lowercases the regex pattern itself when caseSensitive: false, so ^Surge becomes ^surge at match time. This is functionally correct, but the uppercase S is inconsistent and could mislead anyone who reads the seed and assumes the pattern is matched as-written. Aligning to the lowercase convention makes the intent clearer.

Suggested change
{
headerName: 'user-agent',
operator: 'REGEX',
value: '^Surge',
caseSensitive: false,
},
{
headerName: 'user-agent',
operator: 'REGEX',
value: '^surge',
caseSensitive: false,
},

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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