Skip to content

RealLiuSha/fucksub

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

18 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

English | 简体中文

fucksub

fucksub is a subscription converter backend rewritten in Go. The goal is to make subconverter-style subscription conversion more maintainable, more testable, and far clearer about where and how things fail.

The original subconverter README defines the project as "a tool to convert between various proxy subscription formats", and ships a large target matrix: Clash, ClashR, Quantumult, Quantumult X, Loon, Mellow, SS, SSD, SSR, Surfboard, Surge, Trojan, V2Ray, Mixed, Auto, and more. SubConverter-Extended later added Mihomo-oriented enhancements, mainly to address the original project's slow adoption of newer protocols such as AnyTLS, Hysteria2, and TUIC.

fucksub starts from a more direct premise: SubConverter-Extended can no longer be relied on for maintenance — issues went unfixed for a long time and the repository was removed — so continuing to patch the old code is no longer worth it. fucksub is a rewrite with a smaller but controllable capability boundary.

Current Scope

  • Only three output targets are supported: clash, quanx, surge.
  • clash targets Clash/Mihomo-kernel clients, using Mihomo's Go parsing and validation.
  • quanx targets Quantumult X and emits Quantumult X config fragments.
  • surge targets Surge and emits Surge config fragments.
  • Byte-for-byte compatibility with the original subconverter or SubConverter-Extended is a non-goal.
  • Unsupported protocols, fields, rules, and group semantics are never dropped silently — they are surfaced via warnings.

Supported Clients

Client / Format target Status Notes
Clash/Mihomo clash Production candidate Emits Mihomo YAML with proxy-providers, rule-providers, custom proxy groups, and clash_rule_base merging, and validates the config through Mihomo.
Quantumult X quanx Gray-release ready Emits [server_local], [server_remote], [policy], [filter_remote], [filter_local]. Complex rulesets/providers are converted conservatively and warned.
Surge surge Gray-release ready Emits [General], [Proxy], [Proxy Group], [Rule]. With list=true, only [Proxy] is emitted.

The original subconverter targets clashr, quan, loon, mellow, ss, sssub, ssd, ssr, surfboard, trojan, v2ray, mixed, auto, etc. are not yet supported.

Shadowrocket has no dedicated target right now. The original subconverter usually suggests ss, ssr, v2ray, or mixed for Shadowrocket, and those targets are not implemented in this project yet.

Supported Protocols

Status meanings:

  • native: natively supported by the target and emitted directly.
  • mapped: the target uses different syntax, but the current implementation can map it.
  • degraded: only an approximate expression is possible; a warning is produced.
  • unsupported: no reliable equivalent semantics or the implementation is unconfirmed; it is skipped and warned.
Protocol Clash/Mihomo Surge Quantumult X
direct native native native
reject native native native
http native native native
https native native mapped
socks5 native native native
socks5-tls native native mapped
ss native native native
ssr native unsupported mapped
snell native native unsupported
vmess native native native
vless native unsupported mapped
trojan native native native
hysteria native unsupported unsupported
hysteria2 native native unsupported
tuic native native unsupported
anytls native native native
wireguard native native unsupported
ssh native native unsupported
mieru native unsupported unsupported
masque native unsupported unsupported
trust-tunnel native native unsupported
openvpn native unsupported unsupported

Notes:

  • direct and reject are mainly used as policy references, not emitted as remote nodes.
  • The clash target keeps the original Mihomo proxy map and lets the Mihomo adapter run proxy/config validation.
  • The surge and quanx targets only emit fields the mapper has confirmed; fields whose equivalent cannot be confirmed produce a warning.

Supported Input Formats

The url parameter accepts one or more subscriptions. Multiple subscriptions are joined with | and then URL-encoded.

Currently supported fetch schemes:

  • http://...
  • https://...
  • data:...

Default production security policy:

  • Rejects loopback, link-local, private CIDR, multicast, metadata-service, and reserved documentation ranges by default.
  • DNS resolution results and HTTP redirect targets are revalidated.
  • Error messages redact the URL — basic auth, full path, and query tokens are not returned.
  • Private deployments that genuinely need to fetch intranet subscriptions can set FUCKSUB_ALLOW_PRIVATE_NETWORKS=true to relax the address policy.

Currently supported parsing:

  • Clash/Mihomo YAML: proxies, proxy-providers
  • Base64 or plaintext node URI lists: ss://, vmess://, trojan://, vless://, hysteria2://, hy2://, anytls://, tuic://
  • Surge [Proxy]-style node text lines
  • Quantumult X server-style node text lines

Quick Start

The project requires Go 1.25.4.

go run ./cmd/server

It listens on :8080 by default, overridable via environment variable:

PORT=25500 go run ./cmd/server

API authentication is disabled by default, so the conversion capability can be opened directly to callers. Configure it explicitly when you need to protect /sub:

FUCKSUB_API_TOKEN=your-token go run ./cmd/server

Once enabled, /sub accepts X-Fucksub-Token: your-token, Authorization: Bearer your-token, or a token=your-token query parameter for subscription clients that cannot send custom headers. Header tokens take priority. /stats remains header-only. /health and the built-in web page do not require a token.

The built-in access log records only method, path, status, latency, and client IP. It does not log URL query strings, subscription URLs, token, sig, basic-auth credentials, or inline data: payloads. Runtime request deadlines are enabled by default:

FUCKSUB_REQUEST_TIMEOUT=60s \
FUCKSUB_PROFILE_UPDATE_INTERVAL=24h \
go run ./cmd/server

FUCKSUB_REQUEST_TIMEOUT accepts Go durations (60s, 2m) or plain seconds. FUCKSUB_PROFILE_UPDATE_INTERVAL controls the fallback Profile-Update-Interval response header when the request does not provide an interval parameter.

Optional server-side node insertion is configured at the service boundary:

FUCKSUB_INSERT_URL='https://example.com/ops-sub|data:,ss://...' \
FUCKSUB_PREPEND_INSERT=true \
go run ./cmd/server

When callers pass insert=true, configured insert sources are merged with user subscriptions before filtering, rename, emoji, and dedupe. Insert source failures return insert_fetch_failed warnings and do not block the user's subscription.

Generated profiles route HTTP(S) remote rulesets for all targets through this service's /getruleset endpoint. Clash/Mihomo currently receives the original provider payload, while Surge and Quantumult X receive target-specific text rulesets converted on demand. This keeps profile output consistent and leaves one place for future target-specific ruleset behavior. The link prefix is derived from the request Host by default (which may be an IP); to pin a stable public address (a domain, a reverse-proxy entry, ...) set FUCKSUB_MANAGED_CONFIG_PREFIX, the equivalent of subconverter's managed_config_prefix:

FUCKSUB_MANAGED_CONFIG_PREFIX=https://example.com go run ./cmd/server

Note: this prefix is written verbatim into generated profiles (including any basic-auth credentials in it) and must be reachable by clients. Do not hardcode a credentialed URL into source or a public repo — inject it through this environment variable at deploy time. /getruleset does not require the FUCKSUB_API_TOKEN header, because client applications fetch it after importing the generated profile; instead, generated managed ruleset URLs carry a sig query parameter when a signing key is available.

Managed ruleset signing:

FUCKSUB_API_TOKEN=your-token \
FUCKSUB_RULESET_SIGNATURE=enforce \
go run ./cmd/server

FUCKSUB_RULESET_SIGNING_KEY is used first when set. If it is not set but FUCKSUB_API_TOKEN is configured, fucksub derives a stable ruleset signing key from the API token, so deployments that protect /sub automatically protect /getruleset against unsigned arbitrary fetches. FUCKSUB_RULESET_SIGNATURE accepts enforce, warn, or off; with a key present the default is enforce. With no key source, signing is forced off and startup logs a warning that /getruleset is open.

/getruleset accepts text rulesets, Clash YAML provider payloads (payload:), and Clash/Mihomo MRS files. Full Clash profiles (proxies:, rule-providers:, rules:) are rejected as ruleset sources instead of being guessed. Surge/Quantumult X renderers drop unsupported external-ruleset rule types with X-Fucksub-Warnings that include the dropped type and samples.

Optional managed-ruleset runtime knobs:

FUCKSUB_RULESET_CACHE_TTL=5m \
FUCKSUB_RULESET_CACHE_MAX_ENTRIES=1024 \
FUCKSUB_RULESET_CACHE_MAX_BODY=16777216 \
FUCKSUB_RULESET_RATE_PER_MINUTE=600 \
FUCKSUB_RULESET_RATE_BURST=120 \
FUCKSUB_RULESET_CLIENT_RATE_PER_MINUTE=120 \
FUCKSUB_RULESET_CLIENT_RATE_BURST=30 \
FUCKSUB_RULESET_UPSTREAM_MIN_INTERVAL=100ms \
FUCKSUB_RULESET_UPSTREAM_MAX_CONCURRENT=4 \
FUCKSUB_GEOSITE_MAP='openai=https://example.com/openai.list;apple=https://example.com/apple.list' \
FUCKSUB_GEOSITE_MAX_RULES=10000 \
go run ./cmd/server

Subscription/config fetches go through a bounded in-memory cache with stale-on-error fallback: within FUCKSUB_SUB_CACHE_TTL a cached body is served directly; after that the next request refreshes upstream, and if the refresh fails a previously good copy is served for up to FUCKSUB_SUB_CACHE_STALE_TTL more (a failed upstream is then retried at most every 30s instead of per request). Concurrent requests for the same URL share one upstream fetch, and transport errors / gateway 5xx get a single automatic retry.

FUCKSUB_SUB_CACHE_TTL=5m \
FUCKSUB_SUB_CACHE_STALE_TTL=6h \
FUCKSUB_SUB_CACHE_MAX_ENTRIES=256 \
FUCKSUB_SUB_CACHE_MAX_BYTES=134217728 \
go run ./cmd/server

FUCKSUB_SUB_CACHE_TTL<=0 disables the cache entirely; FUCKSUB_SUB_CACHE_STALE_TTL<=0 disables only the stale-on-error window.

/sub has no rejecting rate limit by default. Deployments that need server-side backpressure can enable a global bucket, a per-client bucket, or both:

FUCKSUB_SUB_RATE_PER_MINUTE=120 \
FUCKSUB_SUB_RATE_BURST=30 \
FUCKSUB_SUB_CLIENT_RATE_PER_MINUTE=30 \
FUCKSUB_SUB_CLIENT_RATE_BURST=10 \
FUCKSUB_SUB_MAX_CLIENT_BUCKETS=1024 \
go run ./cmd/server

When enabled, rejected /sub requests return HTTP 429 and increment security.subscriptionRateLimited in /stats.

GEOSITE,<tag> is native for Clash/Mihomo. For Surge and Quantumult X it is expanded only when FUCKSUB_GEOSITE_MAP provides an explicit tag-to-ruleset URL; otherwise the rule is dropped with unsupported_geosite instead of silently disappearing.

Quantumult X Hysteria2 is intentionally skipped with a warning until its public server syntax is confirmed; emitting an unknown hysteria2= line can make the whole imported profile unusable.

Successful /sub responses pass through valid upstream Subscription-Userinfo. With multiple subscription URLs, upload, download, and total are summed and expire uses the earliest non-zero value. Add append_info=false to suppress this header. Successful responses also include Profile-Update-Interval; request interval is interpreted as seconds and rendered as client-compatible hours.

Runtime counters are exposed as JSON at /stats and grouped by purpose: request (totals by operation and target/status), subscription.cache (fetch cache hit/miss/bypass/stale/singleflight/eviction/size), ruleset.fetch, ruleset.renderCache, ruleset.dropped, warnings, and security (signature failures plus ruleset/subscription rate-limit rejects).

Health check:

curl http://127.0.0.1:8080/health

Convert a subscription:

curl -G 'http://127.0.0.1:8080/sub' \
  --data-urlencode 'target=clash' \
  --data-urlencode 'url=https://example.com/subscription'

Generate Quantumult X:

curl -G 'http://127.0.0.1:8080/sub' \
  --data-urlencode 'target=quanx' \
  --data-urlencode 'url=https://example.com/subscription'

Generate Surge:

curl -G 'http://127.0.0.1:8080/sub' \
  --data-urlencode 'target=surge' \
  --data-urlencode 'url=https://example.com/subscription'

Open / in a browser to use the built-in web page that builds subscription links.

API

/sub

GET /sub?target=%TARGET%&url=%URL%&config=%CONFIG%
Parameter Required Description
target Yes Output target: clash, quanx, surge.
url Yes Subscription address; supports http, https, data; multiple subscriptions joined with `
config No External config address; supports http, https, data.
filename No Download filename; the server strips control characters and path separators.
probe No When set to true, returns only a JSON probe result instead of the config body.
token No /sub query-token fallback when FUCKSUB_API_TOKEN is enabled. Not accepted by /stats. Because token is a known query key, an unencoded upstream URL containing &token= is split at that point — percent-encode the url value (& as %26) to keep an upstream token parameter intact.
append_info No Defaults to true. Set append_info=false to suppress Subscription-Userinfo.
interval No Subscription update interval in seconds; response header is emitted in hours.

Compatibility toggle parameters:

Parameter Description
list Emit node-related content only. Clash drops groups/rules; Surge emits only [Proxy]; QuanX emits only the local node section.
tfo Emit TCP Fast Open related fields for mappable targets.
udp Emit UDP related fields for mappable targets.
scv / skip-cert-verify Apply global certificate verification skipping to nodes that do not already declare it.
sort Sort by node name.
add_emoji / remove_old_emoji Add or strip emoji in node names.
rename Rename nodes, formatted as match@replace.
include_remarks / exclude_remarks Keep or exclude by node-name regex.
fdn Add built-in filters for expired/traffic-info style nodes.
expand Inline remote rulesets into the generated profile when possible; per-ruleset failures fall back to remote references with expand_ruleset_failed.
insert Merge server-configured insert sources when FUCKSUB_INSERT_URL is configured; otherwise returns unsupported_insert.
new_name Kept as a known compatibility key only. new_name=false returns unsupported_new_name; output always uses Mihomo field names.

probe=true

curl -G 'http://127.0.0.1:8080/sub' \
  --data-urlencode 'target=clash' \
  --data-urlencode 'url=https://example.com/subscription' \
  --data-urlencode 'probe=true'

Sample response:

{
  "target": "clash",
  "sourceCount": 1,
  "nodeCount": 12,
  "warnings": []
}

External Config

config currently supports the common [custom] fields from subconverter-style external config. When a file has no explicit [custom] section, top-level config lines are treated as [custom].

Option Status Description
custom_proxy_group Supported Parses proxy groups and maps them to Mihomo/Surge/QuanX per target.
ruleset Supported Parses rules and remote rulesets; warns when the target cannot express them.
enable_rule_generator Supported Controls whether rules are generated.
overwrite_original_rules Supported Controls rule overwrite semantics.
clash_rule_base Supported Only effective for target=clash, used as the base YAML.
surge_rule_base Supported Only effective for target=surge, merged by profile section before generated sections are written. Generated [Proxy], [Proxy Group], and [Rule] sections replace same-named base sections, so base [Rule] entries do not participate in overwrite_original_rules.
quanx_rule_base Supported Only effective for target=quanx, merged by profile module before generated modules are written. Generated modules replace same-named base modules.
rename Supported Node-name regex replacement.
emoji Supported Node-name emoji rules.
add_emoji / remove_old_emoji Supported Controls emoji adding and stripping.
include_remarks / exclude_remarks Supported Node filtering.
!!import Not yet supported Returns an unsupported_config_directive warning.

Warning Contract

When the conversion encounters degradation, skips, unsupported constructs, or config risks, fucksub tries to keep producing a usable result and surfaces warnings.

  • Successful response: if warnings exist, the response includes the X-Fucksub-Warnings header.
  • Failed response: if warnings were already identified before the failure, the response still includes the X-Fucksub-Warnings header.
  • probe=true: warnings appear in the warnings field of the JSON response.

Public Deployment

  • The conversion capability is open to callers by default and requires no token.
  • If your deployment needs to protect /sub, set FUCKSUB_API_TOKEN. Browser/probe flows should use a request header; copied subscription links may use token= query for clients that cannot send headers.
  • For internet-facing deployments, configure basic rate limiting, an access-log retention policy, and abnormal-request alerting at the reverse-proxy layer.
  • Setting FUCKSUB_ALLOW_PRIVATE_NETWORKS=true on a public service is not recommended; that switch is only for private deployments that explicitly need to reach intranet subscription sources.
  • Access logs record only method/path/status/latency/client IP. Conversion logs record target, source/node counts, warning codes, status, and latency. Neither log includes the full subscription URL or query string.

Examples

Copy-and-run samples are in examples/.

Design Trade-offs

Why Not Keep Forking SubConverter-Extended

SubConverter-Extended solved the original project's slow adoption of newer protocols such as AnyTLS, but it still inherited a large body of legacy implementation and a complex compatibility surface. For a project whose maintenance has stalled, whose issues go unfixed, and whose repository can no longer be relied on, piling on patches makes its behavior increasingly unpredictable.

fucksub chose a rewrite:

  • Unify the HTTP service, parsing, conversion, validation, and testing in Go.
  • Keep only the three client targets that actually need to be delivered right now.
  • Use a Mihomo adapter for Clash/Mihomo protocol parsing and config validation.
  • Use explicit mappers for Surge/Quantumult X, warning whenever something cannot be expressed safely.
  • Pin behavior with a compatibility matrix and a golden corpus to reduce the hidden risk of output that "looks convertible but is actually unusable".

Why Fewer Targets

The original subconverter has many targets, but the more targets there are, the easier it is for protocols, fields, and rule semantics to masquerade as one another. fucksub currently prioritizes keeping the behavior of clash, quanx, and surge explainable, testable, and maintainable.

Verification

Requires Go 1.25.4. Recommended to run before committing:

zsh -ic 'goup <<< 4; GO111MODULE=on gofmt -l .'
zsh -ic 'goup <<< 4; GO111MODULE=on go test ./...'
zsh -ic 'goup <<< 4; GO111MODULE=on go vet ./...'

About

Utility to convert between various subscription format

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages