{"id":47589280,"url":"https://github.com/alchemylink/raven-subscribe","last_synced_at":"2026-04-24T19:01:39.002Z","repository":{"id":344658167,"uuid":"1179237771","full_name":"AlchemyLink/Raven-subscribe","owner":"AlchemyLink","description":"Subscription server for Xray-core — generates per-user client configs, unique sub URLs, and supports VLESS, VMess, Trojan, Shadowsocks across all transports.","archived":false,"fork":false,"pushed_at":"2026-04-22T19:53:56.000Z","size":540,"stargazers_count":8,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-22T21:31:34.773Z","etag":null,"topics":["docker","go","shadowsocks","sqlite","trojan","vless","vmess","xhttp","xray","xray-core","xtls-rprx-vision"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/AlchemyLink.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-11T20:38:53.000Z","updated_at":"2026-04-22T19:54:00.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/AlchemyLink/Raven-subscribe","commit_stats":null,"previous_names":["alchemylink/raven-subscribe"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/AlchemyLink/Raven-subscribe","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlchemyLink%2FRaven-subscribe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlchemyLink%2FRaven-subscribe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlchemyLink%2FRaven-subscribe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlchemyLink%2FRaven-subscribe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/AlchemyLink","download_url":"https://codeload.github.com/AlchemyLink/Raven-subscribe/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/AlchemyLink%2FRaven-subscribe/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32236744,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T13:21:15.438Z","status":"ssl_error","status_checked_at":"2026-04-24T13:21:15.005Z","response_time":64,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","go","shadowsocks","sqlite","trojan","vless","vmess","xhttp","xray","xray-core","xtls-rprx-vision"],"created_at":"2026-04-01T17:04:40.810Z","updated_at":"2026-04-24T19:01:38.964Z","avatar_url":"https://github.com/AlchemyLink.png","language":"Go","readme":"# Raven Subscribe\n\nLanguages: **English** | [Русский](README.ru.md)\n\n[![Built for Xray-core](https://img.shields.io/badge/Built%20for-Xray--core-blue?logo=github)](https://github.com/XTLS/Xray-core)\n[![Test](https://github.com/AlchemyLink/Raven-subscribe/actions/workflows/test.yml/badge.svg)](https://github.com/AlchemyLink/Raven-subscribe/actions/workflows/test.yml)\n[![Security Scan](https://github.com/AlchemyLink/Raven-subscribe/actions/workflows/security.yml/badge.svg)](https://github.com/AlchemyLink/Raven-subscribe/actions/workflows/security.yml)\n[![Go Report Card](https://goreportcard.com/badge/github.com/alchemylink/raven-subscribe)](https://goreportcard.com/report/github.com/alchemylink/raven-subscribe)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![Stars](https://img.shields.io/github/stars/AlchemyLink/Raven-subscribe?style=flat)](https://github.com/AlchemyLink/Raven-subscribe/stargazers)\n[![Forks](https://img.shields.io/github/forks/AlchemyLink/Raven-subscribe?style=flat)](https://github.com/AlchemyLink/Raven-subscribe/network/members)\n[![Hits](https://hits.dwyl.com/AlchemyLink/Raven-subscribe.svg?style=flat)](https://hits.dwyl.com/AlchemyLink/Raven-subscribe)\n\n**Self-hosted subscription server for [XTLS/Xray-core](https://github.com/XTLS/Xray-core) and [sing-box](https://github.com/SagerNet/sing-box).** Auto-discovers users from your Xray server configs and gives each one a personal subscription URL — so V2RayNG, NekoBox, Hiddify, and other VPN clients always fetch the latest connection settings automatically.\n\nSupports **VLESS, VMess, Trojan, Shadowsocks, Hysteria2**, transports **XHTTP/SplitHTTP, WebSocket, gRPC, REALITY**, and serves configs in Xray JSON, sing-box JSON, and share-link formats.\n\n---\n\n## Table of Contents\n\n- [What it does](#what-it-does)\n- [Features](#features)\n- [How it works](#how-it-works)\n- [Quick Start](#quick-start)\n- [Configuration](#configuration)\n- [Subscription URLs](#subscription-urls)\n- [Admin API](#admin-api)\n- [Routing Rules](#routing-rules)\n- [Emergency Config Rotation](#emergency-config-rotation)\n- [Supported Protocols \u0026 Transports](#supported-protocols--transports)\n- [sing-box / Hysteria2](#sing-box--hysteria2)\n- [Docker](#docker)\n- [Contributing](#contributing)\n\n---\n\n## What it does\n\nWhen you run an Xray proxy server, each user needs a client config file with the correct server address, port, UUID, and transport settings. Keeping those configs up to date manually is tedious.\n\n**Raven Subscribe** solves this:\n\n1. It reads your Xray server configs from `/etc/xray/config.d`\n2. Discovers all users (clients) defined in those configs automatically\n3. Generates a ready-to-use Xray client config for each user\n4. Serves it via a unique subscription URL\n\nUsers just add their subscription URL to any Xray-compatible client (V2RayNG, NekoBox, V2Box, Hiddify, etc.) — and the client fetches fresh settings automatically.\n\n---\n\n## Features\n\n### For users\n- **Personal subscription URL** — one link that always returns the latest config\n- **Multiple formats** — full Xray JSON, sing-box JSON, plain share links, Base64-encoded links\n- **Protocol-specific links** — get only VLESS, VMess, Trojan, Shadowsocks, or Hysteria2 links\n- **Mobile-optimized configs** — auto-detected from User-Agent (Android, iPhone, NekoBox, V2RayNG) or via `?profile=mobile`\n- **Custom routing rules** — per-user rules to route specific sites directly, through proxy, or block them\n\n### For administrators\n- **Database as source of truth** — when `api_user_inbound_tag` is set, add/remove/enable/disable users via API and they sync to Xray immediately\n- **Zero-touch user sync** — users can also be discovered from Xray config `email` fields\n- **File watcher** — detects config changes instantly (fsnotify + periodic polling)\n- **Full REST API** — manage users, tokens, routing rules, and balancer settings\n- **Per-user client control** — enable/disable a user's access to specific inbounds\n- **Token rotation** — regenerate any user's subscription token without downtime\n- **Balancer support** — automatic load balancing across multiple outbounds (leastPing, leastLoad, random)\n- **Global routing rules** — apply routing rules to all users at once\n\n### Protocols \u0026 transports\n- **VLESS**, **VMess**, **Trojan**, **Shadowsocks**, **SOCKS** (via Xray-core)\n- **Hysteria2** (via sing-box) — QUIC-based protocol with Salamander obfuscation\n- **TCP**, **WebSocket**, **gRPC**, **HTTP/2**, **KCP**, **QUIC**, **HTTPUpgrade**, **XHTTP (SplitHTTP)**\n- **TLS** and **REALITY** security layers with automatic key derivation\n\n---\n\n## How it works\n\n```\n/etc/xray/config.d/          /etc/sing-box/config.json\n    ├── vless-reality.json        └── (hysteria2 inbound)\n    ├── vmess-ws.json\n    └── trojan-tls.json\n           │                              │\n           └──────────────┬───────────────┘\n                          ▼\n                   Raven Subscribe\n                   (watches for changes)\n                          │\n                          ├─ Parses inbounds, discovers users\n                          ├─ Stores in SQLite\n                          ├─ Serves subscription URLs\n                          └─ API-created users → Xray (config files or gRPC API)\n                                     │\n                                     ▼\n                   https://your-server.com/sub/{token}         ← Xray JSON\n                   https://your-server.com/sub/{token}/singbox  ← sing-box JSON\n                   https://your-server.com/sub/{token}/hysteria2 ← share links\n                                     │\n                                     ▼\n                   V2RayNG / NekoBox / Hiddify / V2Box / Hysteria2 app\n                   (fetches config automatically)\n```\n\nEach user gets a unique token. When their client fetches the subscription URL, Raven Subscribe builds a complete Xray client config on the fly — with all their enabled inbounds, routing rules, DNS settings, and balancer config.\n\n---\n\n## Quick Start\n\n### 1. Install\n\n**From binary:**\n```bash\ncurl -Lo xray-subscription https://github.com/AlchemyLink/Raven-subscribe/releases/latest/download/xray-subscription-linux-amd64\nchmod +x xray-subscription\nsudo mv xray-subscription /usr/local/bin/\n```\n\n**From source:**\n```bash\ngit clone https://github.com/AlchemyLink/Raven-subscribe.git\ncd Raven-subscribe\nmake build\nsudo cp build/xray-subscription /usr/local/bin/\n```\n\n### 2. Configure\n\n```bash\nsudo mkdir -p /etc/xray-subscription\nsudo cp config.json.example /etc/xray-subscription/config.json\nsudo nano /etc/xray-subscription/config.json\n```\n\nMinimum required settings:\n\n```json\n{\n  \"server_host\": \"your-server-ip-or-domain\",\n  \"admin_token\": \"your-secret-admin-token\",\n  \"base_url\": \"http://your-server-ip-or-domain:8080\"\n}\n```\n\n### 3. Run\n\n```bash\nxray-subscription -config /etc/xray-subscription/config.json\n```\n\n**As a systemd service:**\n```bash\nsudo cp xray-subscription.service /etc/systemd/system/\nsudo systemctl daemon-reload\nsudo systemctl enable --now xray-subscription\n```\n\nThe service runs as `User=xray` so Raven and Xray share ownership of config files. When `api_user_inbound_tag` is set, Raven writes to `config_dir`; Xray must read those files. Ensure:\n\n```bash\n# Create xray user/group if missing (Xray package usually does this)\nsudo useradd -r -s /usr/sbin/nologin xray 2\u003e/dev/null || true\n\n# Let xray own config_dir and data dir when using file-based sync\nsudo chown -R xray:xray /etc/xray/config.d /var/lib/xray-subscription\n```\n\n### 4. Get user subscription URLs\n\n```bash\n# List all users and their subscription URLs\ncurl -H \"X-Admin-Token: your-secret-admin-token\" http://localhost:8080/api/users\n```\n\nResponse:\n```json\n[\n  {\n    \"user\": {\n      \"id\": 1,\n      \"username\": \"alice\",\n      \"token\": \"a3f8c2...\",\n      \"enabled\": true\n    },\n    \"sub_url\": \"http://your-server:8080/sub/a3f8c2...\",\n    \"sub_urls\": {\n      \"full\":        \"http://your-server:8080/sub/a3f8c2...\",\n      \"links_txt\":   \"http://your-server:8080/sub/a3f8c2.../links.txt\",\n      \"links_b64\":   \"http://your-server:8080/sub/a3f8c2.../links.b64\",\n      \"compact\":     \"http://your-server:8080/c/a3f8c2...\",\n      \"compact_txt\": \"http://your-server:8080/c/a3f8c2.../links.txt\",\n      \"compact_b64\": \"http://your-server:8080/c/a3f8c2.../links.b64\",\n      \"singbox\":     \"http://your-server:8080/sub/a3f8c2.../singbox\",\n      \"hysteria2\":   \"http://your-server:8080/sub/a3f8c2.../hysteria2\"\n    }\n  }\n]\n```\n\nGive each user their `sub_urls.compact` URL — they add it to their VPN client and are ready to go. For Hysteria2 clients, use `sub_urls.singbox` or `sub_urls.hysteria2`.\n\n---\n\n## Configuration\n\nConfiguration is loaded from a JSON file (default: `config.json` in the current directory). Use `-config` flag to specify a path:\n\n```bash\nxray-subscription -config /etc/xray-subscription/config.json\n```\n\n### Full config reference\n\n```json\n{\n  \"listen_addr\": \":8080\",\n  \"server_host\": \"your-server.com\",\n  \"config_dir\": \"/etc/xray/config.d\",\n  \"db_path\": \"/var/lib/xray-subscription/db.sqlite\",\n  \"sync_interval_seconds\": 60,\n  \"base_url\": \"http://your-server.com:8080\",\n  \"admin_token\": \"your-secret-token\",\n  \"balancer_strategy\": \"leastPing\",\n  \"balancer_probe_url\": \"https://www.gstatic.com/generate_204\",\n  \"balancer_probe_interval\": \"30s\",\n  \"socks_inbound_port\": 2080,\n  \"http_inbound_port\": 1081,\n  \"rate_limit_sub_per_min\": 60,\n  \"rate_limit_admin_per_min\": 30,\n  \"api_user_inbound_tag\": \"vless-reality\",\n  \"xray_api_addr\": \"\",\n  \"xray_enabled\": true,\n  \"singbox_config\": \"/etc/sing-box/config.json\",\n  \"singbox_enabled\": true,\n  \"inbound_hosts\": {\n    \"vless-reality-in\": \"media.example.com\"\n  },\n  \"inbound_ports\": {\n    \"vless-reality-in\": 8445\n  }\n}\n```\n\n\u003e **Note:** `vless_client_encryption` is intentionally omitted from the example above — only add it when using VLESS Encryption (non-`\"none\"` decryption on the server inbound). Setting it to `false`, `\"none\"`, or `\"\"` causes a parse error.\n\n### Parameter reference\n\n#### Server\n\n| Field | Default | Description |\n|-------|---------|-------------|\n| `listen_addr` | `:8080` | Address and port to bind. Use `:8080` for all interfaces, or `127.0.0.1:8080` for localhost only (e.g. behind nginx). |\n| `server_host` | — | **Required.** Your server IP or domain. Used as the outbound address in generated client configs. Must match what clients will connect to. |\n| `base_url` | `http://localhost:8080` | Full base URL for subscription links. Shown to users in API responses. Use `https://` if behind TLS reverse proxy. |\n\n#### Storage \u0026 sync\n\n| Field | Default | Description |\n|-------|---------|-------------|\n| `config_dir` | `/etc/xray/config.d` | Directory containing Xray server inbound JSON configs. Raven watches this for changes (fsnotify + periodic scan). |\n| `db_path` | `/var/lib/xray-subscription/db.sqlite` | SQLite database path. Stores users, tokens, routing rules, and synced client data. |\n| `sync_interval_seconds` | `60` | Interval (seconds) for re-scanning `config_dir`. Also triggered on file changes. |\n\n#### Admin API\n\n| Field | Default | Description |\n|-------|---------|-------------|\n| `admin_token` | — | **Required.** Secret token for Admin API. Pass in `X-Admin-Token` header. Use a long random string; generate with `openssl rand -hex 32`. |\n\n#### Load balancer\n\nUsed when your Xray config has multiple outbounds (e.g. several proxy nodes). Controls how the client chooses between them.\n\n| Field | Default | Description |\n|-------|---------|-------------|\n| `balancer_strategy` | `leastPing` | Strategy: `leastPing` (lowest latency), `leastLoad` (least connections), `random`, `roundRobin`. |\n| `balancer_probe_url` | `https://www.gstatic.com/generate_204` | URL for latency probes (used by `leastPing`). Must be reachable from the server. |\n| `balancer_probe_interval` | `30s` | How often to probe outbounds. Go duration: `30s`, `1m`, etc. |\n\n#### Client config generation\n\n| Field | Default | Description |\n|-------|---------|-------------|\n| `socks_inbound_port` | `2080` | Local SOCKS5 proxy port in generated client configs. Clients use this for system/app proxy. |\n| `http_inbound_port` | `1081` | Local HTTP proxy port in generated client configs. |\n\n#### Rate limiting\n\nLimits requests per IP per minute. `0` = disabled. Helps prevent abuse.\n\n| Field | Default | Description |\n|-------|---------|-------------|\n| `rate_limit_sub_per_min` | `0` | Max requests/min per IP for `/sub/*` and `/c/*`. Recommended: 60 for production. |\n| `rate_limit_admin_per_min` | `0` | Max requests/min per IP for `/api/*`. Recommended: 30. |\n| `api_user_inbound_tag` | `\"\"` | When set, the database is the source of truth: users created via API are added to this Xray inbound; deleted users are removed; enable/disable syncs to Xray. Uses `config_dir` file write or Xray API (if `xray_api_addr` is set). |\n| `xray_api_addr` | `\"\"` | When set, users are synced via Xray gRPC API instead of config files. E.g. `127.0.0.1:8080`. Requires `api_user_inbound_tag`. Xray must have API enabled with HandlerService. |\n| `api_user_inbound_protocol` | `\"\"` | Fallback when `config_dir` has no inbounds: protocol (`vless`, `vmess`, `trojan`, `shadowsocks`) to create the inbound in DB. Use when Xray configs are elsewhere. |\n| `api_user_inbound_port` | `443` | Port for the inbound when using `api_user_inbound_protocol` fallback. |\n| `xray_config_file_mode` | *(omit)* | Octal mode for JSON files Raven writes under `config_dir` (e.g. `\"0644\"` so another local user can read configs for testing). Default **`0600`**. Only permission bits `0`–`7` (max `0777`). |\n| `vless_client_encryption` | *(omit)* | Map of inbound tag → client-side VLESS Encryption string (Xray-core ≥ v26.2.6, PR #5067). Only needed when the inbound uses VLESS Encryption (`decryption` ≠ `\"none\"`). Generate both strings with `xray vlessenc`. Example: `{\"vless-reality-in\": \"mlkem768x25519plus...\"}`. When set, flow is forced to `xtls-rprx-vision` and Mux is disabled. Omit or remove entirely when not using VLESS Encryption. |\n| `xray_enabled` | `true` | Set to `false` to disable Xray config sync (suppress warnings if Xray is not installed). |\n| `singbox_config` | `\"\"` | Path to sing-box server config file (e.g. `/etc/sing-box/config.json`). When set, Raven also syncs Hysteria2 inbounds from it. |\n| `singbox_enabled` | auto | Controls sing-box sync. Defaults to `true` when `singbox_config` is set. Set to `false` to temporarily disable without removing the path. |\n| `inbound_hosts` | `{}` | Per-inbound host overrides. Key: inbound tag, value: host/domain. Overrides `server_host` for matching inbounds in generated client configs. Falls back to `server_host` when tag is not listed. Example: `{\"vless-reality-in\": \"relay.example.com\"}` |\n| `inbound_ports` | `{}` | Per-inbound port overrides. Key: inbound tag, value: port number. Overrides the inbound's own port in generated client configs. Useful when clients connect through a relay that listens on a different port. Example: `{\"vless-reality-in\": 8445}` |\n\n**DB ↔ Xray sync (when `api_user_inbound_tag` is set):** The database is the source of truth. All changes propagate to Xray immediately:\n\n| Action | DB | Xray |\n|--------|----|------|\n| Create user (`POST /api/users`) | Add | Add to inbound |\n| Delete user (`DELETE /api/users/{id}`) | Remove | Remove from inbound |\n| Disable user (`PUT /api/users/{id}/disable`) | `enabled=false` | Remove from inbound |\n| Enable user (`PUT /api/users/{id}/enable`) | `enabled=true` | Add to inbound |\n\n**Xray API mode** (when `xray_api_addr` is set): Users are synced via gRPC instead of config files. Xray must have API enabled with `HandlerService` in `services`.\n\n- **Restore on startup:** Raven restores all users from the database to Xray via API (survives Xray restarts).\n- **Periodic DB→config sync:** Raven periodically writes users to config files, so they persist across both Raven and Xray restarts.\n\n### Example: minimal config\n\n```json\n{\n  \"server_host\": \"vpn.example.com\",\n  \"admin_token\": \"your-secret-token\",\n  \"base_url\": \"https://vpn.example.com\"\n}\n```\n\nAll other parameters use defaults.\n\n### Example: production with rate limits\n\n```json\n{\n  \"listen_addr\": \"127.0.0.1:8080\",\n  \"server_host\": \"vpn.example.com\",\n  \"base_url\": \"https://vpn.example.com\",\n  \"admin_token\": \"your-secret-token\",\n  \"rate_limit_sub_per_min\": 60,\n  \"rate_limit_admin_per_min\": 30\n}\n```\n\nUse `127.0.0.1` when running behind nginx/caddy as reverse proxy.\n\n---\n\n## Subscription URLs\n\nEach user has several subscription endpoints:\n\n| Endpoint | Description |\n|---|---|\n| `/c/{token}` | **Primary.** Lightweight Xray JSON config — `geosite:`/`geoip:` selectors stripped. Works great on all devices. |\n| `/sub/{token}` | Full Xray JSON config with all routing rules including geo databases. |\n| `/sub/{token}/singbox` | sing-box JSON config with Hysteria2 outbounds. For Hysteria2 clients. |\n| `/sub/{token}/hysteria2` | Hysteria2 share links (`hysteria2://…`), plain text. |\n| `/sub/{token}/hysteria2.b64` | Hysteria2 share links, Base64-encoded. |\n| `/sub/{token}/links` | JSON map of all share-link URLs grouped by protocol and inbound tag. |\n| `/sub/{token}/protocol/{protocol}` | Share links filtered by protocol name (e.g. `vless`, `vmess`, `trojan`, `ss`, `hysteria2`). |\n\n### `/c/{token}` — primary endpoint (recommended)\n\nThe compact endpoint is the recommended URL to give to users. It returns a complete Xray client config with routing rules optimized for lower memory usage — `geosite:` and `geoip:` selectors are stripped, keeping only explicit domain and IP rules.\n\nWorks on all clients: V2RayNG, NekoBox, V2Box, Hiddify, and desktop clients.\n\n| What you want | URL |\n|---|---|\n| Full Xray JSON config | `/c/{token}` |\n| All share links (plain text) | `/c/{token}/links.txt` |\n| All share links (Base64) | `/c/{token}/links.b64` |\n\n### `/sub/{token}` — full endpoint\n\nReturns the complete config including `geosite:` and `geoip:` routing rules. Use this if your client supports geo databases and you want full routing control.\n\n| What you want | URL |\n|---|---|\n| Full Xray JSON config | `/sub/{token}` |\n| All share links (plain text) | `/sub/{token}/links.txt` |\n| All share links (Base64) | `/sub/{token}/links.b64` |\n| VLESS links only | `/sub/{token}/vless` |\n| VMess links only | `/sub/{token}/vmess` |\n| Trojan links only | `/sub/{token}/trojan` |\n| Shadowsocks links only | `/sub/{token}/ss` |\n| Hysteria2 share links | `/sub/{token}/hysteria2` |\n| Hysteria2 share links (Base64) | `/sub/{token}/hysteria2.b64` |\n| sing-box JSON (Hysteria2) | `/sub/{token}/singbox` |\n| Specific inbound only | `/sub/{token}/inbound/{tag}` |\n| Lightweight config (explicit) | `/sub/{token}?profile=mobile` |\n\n### Example: import into V2RayNG\n\n1. Open V2RayNG → tap **+** → **Import config from URL**\n2. Paste: `http://your-server:8080/c/YOUR_TOKEN`\n3. Tap **OK** — done. The app fetches and imports all your connections.\n\n### Example: import into NekoBox / Hiddify\n\nUse the same `/c/{token}` URL. These clients support Xray JSON format natively.\n\n### Example: get plain share links\n\n```bash\ncurl http://your-server:8080/c/YOUR_TOKEN/links.txt\n```\n\nOutput:\n```\nvless://uuid@your-server:443?type=ws\u0026security=tls\u0026...#vless-ws-tls\nvmess://eyJ2IjoiMiIsInBzIjoidm1lc3MtdGNwIiwiYWRkIjoieW91ci1zZXJ2ZXIiLCJwb3J0IjoiODA4MCIsImlkIjoiLi4uIn0=\ntrojan://password@your-server:443?security=tls\u0026...#trojan-tls\n```\n\n### Auto-detection\n\nWhen a mobile client fetches `/sub/{token}`, Raven Subscribe automatically detects it from the `User-Agent` header (Android, iPhone, iPad, V2RayNG, NekoBox, V2Box) and applies the lightweight profile automatically. The `/c/{token}` endpoint always uses the lightweight profile regardless of User-Agent.\n\n---\n\n## Admin API\n\nAll admin endpoints require authentication. Pass your `admin_token` as a header or query parameter:\n\n```bash\n# As header (recommended)\ncurl -H \"X-Admin-Token: your-secret-token\" http://localhost:8080/api/users\n\n# As query parameter\ncurl \"http://localhost:8080/api/users?admin_token=your-secret-token\"\n```\n\n### Users\n\n#### List all users\n```bash\nGET /api/users\n```\n```bash\ncurl -H \"X-Admin-Token: secret\" http://localhost:8080/api/users\n```\n```json\n[\n  {\n    \"user\": {\"id\": 1, \"username\": \"alice@example.com\", \"token\": \"abc123\", \"enabled\": true},\n    \"sub_url\": \"http://your-server:8080/sub/abc123\",\n    \"sub_urls\": {\n      \"full\":        \"http://your-server:8080/sub/abc123\",\n      \"links_txt\":   \"http://your-server:8080/sub/abc123/links.txt\",\n      \"links_b64\":   \"http://your-server:8080/sub/abc123/links.b64\",\n      \"compact\":     \"http://your-server:8080/c/abc123\",\n      \"compact_txt\": \"http://your-server:8080/c/abc123/links.txt\",\n      \"compact_b64\": \"http://your-server:8080/c/abc123/links.b64\",\n      \"singbox\":     \"http://your-server:8080/sub/abc123/singbox\",\n      \"hysteria2\":   \"http://your-server:8080/sub/abc123/hysteria2\"\n    }\n  }\n]\n```\n\n#### Create a user\n```bash\nPOST /api/users\nContent-Type: application/json\n\n{\"username\": \"bob\"}\n```\nOn create, `email` is not accepted; internally it matches `username` for Xray. API JSON does **not** include `email` (use `username`).\n\n```bash\ncurl -X POST -H \"X-Admin-Token: secret\" -H \"Content-Type: application/json\" \\\n  -d '{\"username\":\"bob\"}' http://localhost:8080/api/users\n```\n```json\n{\n  \"user\": {\"id\": 2, \"username\": \"bob\", \"token\": \"xyz789\", \"enabled\": true},\n  \"sub_url\": \"http://your-server:8080/sub/xyz789\",\n  \"sub_urls\": {\n    \"full\":        \"http://your-server:8080/sub/xyz789\",\n    \"links_txt\":   \"http://your-server:8080/sub/xyz789/links.txt\",\n    \"links_b64\":   \"http://your-server:8080/sub/xyz789/links.b64\",\n    \"compact\":     \"http://your-server:8080/c/xyz789\",\n    \"compact_txt\": \"http://your-server:8080/c/xyz789/links.txt\",\n    \"compact_b64\": \"http://your-server:8080/c/xyz789/links.b64\",\n    \"singbox\":     \"http://your-server:8080/sub/xyz789/singbox\",\n    \"hysteria2\":   \"http://your-server:8080/sub/xyz789/hysteria2\"\n  }\n}\n```\nWhen `api_user_inbound_tag` is set, the user is also added to Xray (config file or API).\n\n#### Get a user\n```bash\nGET /api/users/{id}\n```\n\n#### Delete a user\n```bash\nDELETE /api/users/{id}\n```\n`{id}` accepts a **numeric id** or a **username** (including email format like `alice@example.com`). Applies to `GET`, `DELETE`, `enable`, `disable`, `token`, `routes`, and `clients` routes.\n\nWhen `api_user_inbound_tag` is set, the user is also removed from Xray.\n\n#### Example: create and delete (bash)\n\n```bash\nHOST=\"http://localhost:8080\"\nADMIN=\"your-secret-admin-token\"\n\n# 1) Create user\nCREATE_JSON=$(curl -sS -X POST \"$HOST/api/users\" \\\n  -H \"X-Admin-Token: $ADMIN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"username\":\"alice@example.com\"}')\necho \"$CREATE_JSON\"\n\n# 2) Delete by username (no jq needed)\ncurl -sS -X DELETE \"$HOST/api/users/alice@example.com\" \\\n  -H \"X-Admin-Token: $ADMIN\"\n# {\"status\":\"deleted\"}\n\n# — or by numeric id (needs jq)\nUSER_ID=$(echo \"$CREATE_JSON\" | jq -r '.user.id')\ncurl -sS -X DELETE \"$HOST/api/users/$USER_ID\" \\\n  -H \"X-Admin-Token: $ADMIN\"\n\n# 3) Confirm gone\ncurl -sS -H \"X-Admin-Token: $ADMIN\" \"$HOST/api/users/alice@example.com\"\n# {\"error\":\"user not found\"}\n```\n\n#### Enable / disable a user\n```bash\nPUT /api/users/{id}/enable\nPUT /api/users/{id}/disable\n```\nWhen `api_user_inbound_tag` is set, the user is added to or removed from Xray accordingly.\n\n#### Regenerate subscription token\n```bash\nPOST /api/users/{id}/token\n```\nReturns new `{token, sub_url}`. The old URL stops working immediately.\n\n#### List user's inbound connections\n```bash\nGET /api/users/{id}/clients\n```\nShows which inbounds the user is enrolled in and whether each is enabled.\n\n#### Add one inbound connection for an existing user\n```bash\nPOST /api/users/{id}/clients\nContent-Type: application/json\n\n{\n  \"tag\": \"vless-xhttp-in\",\n  \"protocol\": \"vless\"\n}\n```\nExample:\n```bash\ncurl -H \"X-Admin-Token: \u003cadmin-token\u003e\" \\\n  -X POST http://\u003chost\u003e:8080/api/users/16/clients \\\n  -d '{\"tag\":\"vless-xhttp-in\"}'\n```\n- `tag` is required.\n- `protocol` is optional. If omitted, it is resolved by `tag` from synced inbounds, then falls back to `api_user_inbound_protocol`.\n- If the user is already enrolled in this inbound, the existing client record is returned (idempotent behavior).\n\n#### Enable / disable a specific connection\n```bash\nPUT /api/users/{userId}/clients/{inboundId}/enable\nPUT /api/users/{userId}/clients/{inboundId}/disable\n```\nUse this to give a user access to only certain servers/protocols.\n\n### Inbounds\n\n#### List all synced inbounds\n```bash\nGET /api/inbounds\n```\n```json\n[\n  {\n    \"id\": 1,\n    \"tag\": \"vless-reality\",\n    \"protocol\": \"vless\",\n    \"port\": 443,\n    \"config_file\": \"/etc/xray/config.d/vless-reality.json\"\n  }\n]\n```\n\n#### Trigger manual sync\n```bash\nPOST /api/sync\n```\nForces an immediate re-scan of `config_dir`. Useful after editing Xray configs.\n\n### Balancer\n\n#### Get balancer config\n```bash\nGET /api/config/balancer\n```\n\n#### Update balancer settings at runtime\n```bash\nPUT /api/config/balancer\nContent-Type: application/json\n\n{\n  \"strategy\": \"leastPing\",\n  \"probe_url\": \"https://www.gstatic.com/generate_204\",\n  \"probe_interval\": \"30s\"\n}\n```\n\n#### Reset to config file defaults\n```bash\nPUT /api/config/balancer\nContent-Type: application/json\n\n{\"reset\": true}\n```\n\n### Health check\n```bash\nGET /health\n```\n```json\n{\"status\": \"ok\"}\n```\nNo authentication required. Use this for uptime monitoring.\n\n---\n\n## Routing Rules\n\nRaven Subscribe generates Xray client configs with a three-tier routing system:\n\n```\nUser rules  →  Global rules  →  Built-in defaults\n(highest priority)              (lowest priority)\n```\n\n### Built-in defaults\n\nEvery generated config includes these rules automatically:\n\n- **Direct**: Russian services (Yandex, VK, Lamoda, etc.), private IPs, `geoip:ru`\n- **Proxy**: `geosite:ru-blocked`, `geoip:ru-blocked`\n- **Block**: Ads and public torrent trackers\n\n### Add a global rule (applies to all users)\n\n```bash\nPOST /api/routes/global\nContent-Type: application/json\n\n{\n  \"type\": \"field\",\n  \"outboundTag\": \"direct\",\n  \"domain\": [\"example.com\", \"geosite:cn\"]\n}\n```\n\n### Add a per-user rule\n\n```bash\nPOST /api/users/{id}/routes\nContent-Type: application/json\n\n{\n  \"type\": \"field\",\n  \"outboundTag\": \"block\",\n  \"domain\": [\"ads.example.com\"]\n}\n```\n\n### Rule schema\n\n```json\n{\n  \"id\": \"optional-id\",\n  \"type\": \"field\",\n  \"outboundTag\": \"direct | proxy | block\",\n  \"domain\": [\"example.com\", \"geosite:ru-blocked\"],\n  \"ip\": [\"1.1.1.1/32\", \"geoip:ru\"],\n  \"network\": \"tcp | udp\",\n  \"port\": \"443\",\n  \"protocol\": [\"http\", \"tls\"],\n  \"inboundTag\": [\"socks\"]\n}\n```\n\n`outboundTag` must be one of: `direct`, `proxy`, `block`.\n\n---\n\n## Emergency Config Rotation\n\nWhen a VPN blocking event (e.g. DPI detection) hits your main inbounds, emergency mode lets you instantly redirect **all subscriptions** to a pre-configured set of fallback inbounds — with a single API call. Clients that re-fetch their subscription URL automatically receive the fallback config.\n\nWhen emergency mode is active, subscription responses include an `X-Emergency-Mode: active` header. If a user is not enrolled in any of the profile's inbounds, their normal subscription is served as a fallback.\n\n### Workflow\n\n1. Create an emergency profile with the inbound tags of your fallback server\n2. When a block occurs: activate the profile\n3. Clients that refresh their subscription automatically switch to fallbacks\n4. When the block is resolved: deactivate — all clients revert to normal configs\n\n### Create an emergency profile\n\n```bash\nPOST /api/emergency/profiles\nContent-Type: application/json\n\n{\n  \"name\": \"CDN fallback\",\n  \"description\": \"XHTTP over CDN, active when main Reality is blocked\",\n  \"inbound_tags\": [\"vless-cdn-in\", \"vless-xhttp-v2-in\"]\n}\n```\n\n```bash\ncurl -X POST -H \"X-Admin-Token: secret\" -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"CDN fallback\",\"description\":\"fallback via CDN\",\"inbound_tags\":[\"vless-cdn-in\"]}' \\\n  http://localhost:8080/api/emergency/profiles\n```\n\nResponse:\n```json\n{\"id\": 1, \"name\": \"CDN fallback\", \"description\": \"fallback via CDN\", \"inbound_tags\": [\"vless-cdn-in\"]}\n```\n\n### Activate emergency mode\n\n```bash\nPOST /api/emergency/activate\nContent-Type: application/json\n\n{\"profile_id\": 1}\n```\n\n```bash\ncurl -X POST -H \"X-Admin-Token: secret\" -H \"Content-Type: application/json\" \\\n  -d '{\"profile_id\":1}' http://localhost:8080/api/emergency/activate\n```\n\nResponse:\n```json\n{\n  \"active\": true,\n  \"profile_id\": 1,\n  \"profile\": {\"id\": 1, \"name\": \"CDN fallback\", \"inbound_tags\": [\"vless-cdn-in\"]},\n  \"activated_at\": \"2025-01-15T12:00:00Z\"\n}\n```\n\n### Deactivate emergency mode\n\n```bash\nPOST /api/emergency/deactivate\n```\n\n```bash\ncurl -X POST -H \"X-Admin-Token: secret\" http://localhost:8080/api/emergency/deactivate\n```\n\nResponse: `{\"active\": false}`\n\n### Check emergency status\n\n```bash\nGET /api/emergency/status\n```\n\n### Emergency profile management\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| `GET` | `/api/emergency/profiles` | List all profiles |\n| `POST` | `/api/emergency/profiles` | Create profile |\n| `GET` | `/api/emergency/profiles/{id}` | Get profile by ID |\n| `PUT` | `/api/emergency/profiles/{id}` | Update profile |\n| `DELETE` | `/api/emergency/profiles/{id}` | Delete profile (not allowed if active) |\n\n\u003e **Note:** You cannot delete a profile while it is the active emergency profile. Deactivate first.\n\n---\n\n## Supported Protocols \u0026 Transports\n\n### Protocols\n\n| Protocol | Core | Share link format | Notes |\n|---|---|---|---|\n| VLESS | Xray | `vless://uuid@host:port?...#tag` | Supports REALITY, TLS, plain |\n| VMess | Xray | `vmess://base64(json)` | |\n| Trojan | Xray | `trojan://password@host:port?...#tag` | |\n| Shadowsocks | Xray | `ss://base64(method:pass)@host:port#tag` | Single and multi-user |\n| SOCKS | Xray | — | No share link format |\n| Hysteria2 | sing-box | `hysteria2://password@host:port?...#tag` | QUIC-based, Salamander obfuscation |\n\n### Transport layers\n\n| Transport | Description |\n|---|---|\n| TCP | Raw TCP, with optional HTTP header obfuscation |\n| WebSocket | WS with path and host headers |\n| gRPC | gRPC with serviceName |\n| HTTP/2 | H2 with host and path |\n| mKCP | UDP-based, with header types |\n| QUIC | QUIC transport |\n| HTTPUpgrade | HTTP upgrade handshake |\n| XHTTP / SplitHTTP | Split HTTP for CDN-friendly connections |\n\n### Security layers\n\n| Security | Notes |\n|---|---|\n| TLS | Strips server certificates, sets `fingerprint: chrome` by default |\n| REALITY | Auto-derives `publicKey` from server's `privateKey`, picks first `serverName` and `shortId` |\n\n---\n\n## sing-box / Hysteria2\n\nRaven Subscribe can run alongside [sing-box](https://github.com/SagerNet/sing-box) and serve Hysteria2 subscriptions from the same service.\n\n### How it works\n\nWhen `singbox_config` is set, Raven parses the sing-box server config, discovers Hysteria2 inbounds and their users, and stores them in the same SQLite database alongside Xray users. Each user's subscription then includes Hysteria2 endpoints in `sub_urls`.\n\nXray sync and sing-box sync are fully independent — if one core is not installed, the other still works.\n\n**Important:** Hysteria2 users are excluded from Xray JSON subscriptions (`/sub/{token}`, `/c/{token}`). They are served exclusively via the dedicated Hysteria2 endpoints below.\n\n### Configuration\n\n```json\n{\n  \"server_host\": \"vpn.example.com\",\n  \"admin_token\": \"your-secret-token\",\n  \"base_url\": \"https://vpn.example.com\",\n  \"singbox_config\": \"/etc/sing-box/config.json\",\n  \"singbox_enabled\": true,\n  \"xray_enabled\": true\n}\n```\n\n| Parameter | Default | Description |\n|---|---|---|\n| `singbox_config` | `\"\"` | Path to sing-box server config file. When set, Raven syncs Hysteria2 inbounds from it. |\n| `singbox_enabled` | auto | `true` when `singbox_config` is set. Set to `false` to temporarily disable without removing the path. |\n| `xray_enabled` | `true` | Set to `false` to disable Xray sync (e.g. when running sing-box only). |\n\n### Subscription endpoints for Hysteria2\n\n| Endpoint | Format | Use with |\n|---|---|---|\n| `/sub/{token}/singbox` | sing-box JSON | sing-box client, NekoBox (sing-box mode) |\n| `/sub/{token}/hysteria2` | `hysteria2://` share links | Hysteria2 app, Hiddify |\n| `/sub/{token}/hysteria2.b64` | Base64-encoded links | Clients that require encoded input |\n\n### Generated sing-box client config\n\n`/sub/{token}/singbox` returns a ready-to-use sing-box client config:\n\n```json\n{\n  \"log\": {\"level\": \"warn\", \"timestamp\": true},\n  \"inbounds\": [\n    {\"type\": \"mixed\", \"tag\": \"mixed-in\", \"listen\": \"127.0.0.1\", \"listen_port\": 2080}\n  ],\n  \"outbounds\": [\n    {\n      \"type\": \"hysteria2\",\n      \"tag\": \"hysteria2-in-0\",\n      \"server\": \"vpn.example.com\",\n      \"server_port\": 443,\n      \"password\": \"\u003cuser-password\u003e\",\n      \"tls\": {\"enabled\": true, \"server_name\": \"vpn.example.com\"}\n    },\n    {\"type\": \"direct\", \"tag\": \"direct\"},\n    {\"type\": \"block\",  \"tag\": \"block\"}\n  ],\n  \"route\": {\n    \"auto_detect_interface\": true,\n    \"final\": \"hysteria2-in-0\"\n  }\n}\n```\n\nThe `mixed` inbound listens on `127.0.0.1:2080` and accepts both SOCKS5 and HTTP proxy connections. All traffic is routed through the first Hysteria2 outbound by default.\n\n### Salamander obfuscation\n\nIf your sing-box inbound has `obfs` configured, Raven automatically includes it in all generated links and configs:\n\n```json\n{\n  \"type\": \"hysteria2\",\n  \"tag\": \"hysteria2-in\",\n  \"listen_port\": 443,\n  \"obfs\": {\n    \"type\": \"salamander\",\n    \"password\": \"your-obfs-password\"\n  },\n  \"users\": [{\"name\": \"alice@example.com\", \"password\": \"user-password\"}],\n  \"tls\": {\"enabled\": true, \"server_name\": \"vpn.example.com\"}\n}\n```\n\nThe generated `hysteria2://` share link will contain `?obfs=salamander\u0026obfs-password=...` automatically, and the sing-box JSON config will include the `obfs` block in the outbound.\n\n---\n\n## Docker\n\n### Run with Docker Compose\n\n```yaml\n# docker-compose.yml\nservices:\n  raven-subscribe:\n    image: ghcr.io/alchemylink/raven-subscribe:latest\n    ports:\n      - \"8080:8080\"\n    volumes:\n      - ./config.json:/etc/xray-subscription/config.json:ro\n      - /etc/xray/config.d:/etc/xray/config.d:ro\n      - raven-data:/var/lib/xray-subscription\n    restart: unless-stopped\n\nvolumes:\n  raven-data:\n```\n\n```bash\ndocker compose up -d\n```\n\n### Build from source\n\n```bash\ndocker build -t raven-subscribe .\ndocker run -p 8080:8080 \\\n  -v ./config.json:/etc/xray-subscription/config.json:ro \\\n  -v /etc/xray/config.d:/etc/xray/config.d:ro \\\n  raven-subscribe\n```\n\n---\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.\n\n### Before submitting a PR\n\n```bash\ngo test ./... -race\ngolangci-lint run --timeout=5m\n```\n\n### Release\n\n```bash\nmake release VERSION=v1.2.3\n```\n\n---\n\n## License\n\n[MIT](LICENSE) © AlchemyLink\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falchemylink%2Fraven-subscribe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falchemylink%2Fraven-subscribe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falchemylink%2Fraven-subscribe/lists"}