{"id":49691597,"url":"https://github.com/clickdevtech/celerity-panel","last_synced_at":"2026-05-07T17:02:03.235Z","repository":{"id":326278441,"uuid":"1104859219","full_name":"ClickDevTech/CELERITY-panel","owner":"ClickDevTech","description":"Self-hosted web panel for Hysteria 2 \u0026 Xray VLESS proxy servers. Features cascade network topology (Forward/Reverse Chain), SSH auto-setup, server groups, load balancing, ACL traffic filtering, API with scopes, webhooks, S3 backups, MCP integration, and subscriptions for Clash/Sing-box/Hiddify.","archived":false,"fork":false,"pushed_at":"2026-05-06T22:55:16.000Z","size":2535,"stargazers_count":130,"open_issues_count":17,"forks_count":21,"subscribers_count":9,"default_branch":"main","last_synced_at":"2026-05-07T00:32:40.220Z","etag":null,"topics":["ai-integration","censorship-resistant","docker","hiddify","hysteria","hysteria2","mcp","mongodb","nodejs","proxy","proxy-panel","reality","self-hosted","vless","vless-reality","vpn","vpn-panel","xray","xray-core"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/ClickDevTech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"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},"funding":{"custom":["https://celerity.help"]}},"created_at":"2025-11-26T19:43:16.000Z","updated_at":"2026-05-06T22:55:23.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ClickDevTech/CELERITY-panel","commit_stats":null,"previous_names":["clickdevtech/hysteria-panel"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/ClickDevTech/CELERITY-panel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClickDevTech%2FCELERITY-panel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClickDevTech%2FCELERITY-panel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClickDevTech%2FCELERITY-panel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClickDevTech%2FCELERITY-panel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ClickDevTech","download_url":"https://codeload.github.com/ClickDevTech/CELERITY-panel/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ClickDevTech%2FCELERITY-panel/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32747354,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-07T02:14:30.463Z","status":"ssl_error","status_checked_at":"2026-05-07T02:14:29.405Z","response_time":62,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: 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":["ai-integration","censorship-resistant","docker","hiddify","hysteria","hysteria2","mcp","mongodb","nodejs","proxy","proxy-panel","reality","self-hosted","vless","vless-reality","vpn","vpn-panel","xray","xray-core"],"created_at":"2026-05-07T17:02:00.520Z","updated_at":"2026-05-07T17:02:03.228Z","avatar_url":"https://github.com/ClickDevTech.png","language":"JavaScript","funding_links":["https://celerity.help"],"categories":[],"sub_categories":[],"readme":"# C³ CELERITY\n\n⚡ **Fast. Simple. Long-lasting.**\n\n**[English](README.md)** | [Русский](README.ru.md)\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![Docker Pulls](https://img.shields.io/docker/pulls/clickdevtech/hysteria-panel)](https://hub.docker.com/r/clickdevtech/hysteria-panel)\n[![Docker Image Size](https://img.shields.io/docker/image-size/clickdevtech/hysteria-panel/latest)](https://hub.docker.com/r/clickdevtech/hysteria-panel)\n[![Node.js](https://img.shields.io/badge/Node.js-20+-339933?logo=node.js\u0026logoColor=white)](package.json)\n[![Hysteria](https://img.shields.io/badge/Hysteria-2.x-9B59B6)](https://v2.hysteria.network/)\n[![Xray](https://img.shields.io/badge/Xray-VLESS-00ADD8)](https://xtls.github.io/)\n[![Telegram](https://img.shields.io/badge/Telegram-Chat-2CA5E0?logo=telegram\u0026logoColor=white)](https://t.me/+JKFdEr7TqvIyOTFi)\n[![Support](https://img.shields.io/badge/%E2%99%A5-Support-EC4899)](https://celerity.help)\n\n**C³ CELERITY** by Click Connect — modern web panel for managing [Hysteria 2](https://v2.hysteria.network/) and [Xray VLESS](https://xtls.github.io/) proxy servers with centralized authentication, one-click node setup, and flexible user-to-server group mapping.\n\n**Built for performance:** Lightweight architecture designed for speed at any scale.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/dashboard.png\" alt=\"C³ CELERITY Dashboard\" width=\"800\"\u003e\n  \u003cbr\u003e\n  \u003cem\u003eDashboard — real-time server monitoring and statistics\u003c/em\u003e\n\u003c/p\u003e\n\n## ⚡ Quick Start\n\n\u003e Updating an existing installation? See [Safe Production Updates](docs/safe-update.md).\n\n**1. Install Docker** (if not installed):\n```bash\ncurl -fsSL https://get.docker.com | sh\n```\n\n**2. Deploy panel (Docker Hub - recommended):**\n```bash\nmkdir hysteria-panel \u0026\u0026 cd hysteria-panel\n\n# Download required files\ncurl -O https://raw.githubusercontent.com/ClickDevTech/hysteria-panel/main/docker-compose.hub.yml\ncurl -O https://raw.githubusercontent.com/ClickDevTech/hysteria-panel/main/docker.env.example\n\n# Create Greenlock SSL config (required for HTTPS)\nmkdir -p greenlock.d\ncurl -o greenlock.d/config.json https://raw.githubusercontent.com/ClickDevTech/hysteria-panel/main/greenlock.d/config.json\n\ncp docker.env.example .env\nnano .env  # Set your domain, email, and secrets\ndocker compose -f docker-compose.hub.yml up -d\n```\n\n**Alternative: Build from source** (for development or customization)\n```bash\ngit clone https://github.com/ClickDevTech/hysteria-panel.git\ncd hysteria-panel\ncp docker.env.example .env\nnano .env  # Set your domain, email, and secrets\ndocker compose up -d\n```\n\n**3. Open** `https://your-domain/panel`\n\u003e Planning to manage the panel from AI assistants? See [MCP Setup Guide](docs/mcp-user-guide.md).\n\n**Required `.env` variables:**\n```env\nPANEL_DOMAIN=panel.example.com\nACME_EMAIL=admin@example.com\nENCRYPTION_KEY=your32characterkey  # openssl rand -hex 16\nSESSION_SECRET=yoursessionsecret   # openssl rand -hex 32\nMONGO_PASSWORD=yourmongopassword   # openssl rand -hex 16\n```\n\n---\n\n## 🐳 Dokploy (Development and Release)\n\nUse `docker-compose.dokploy.yml` when deploying through Dokploy with Traefik.\n\n### Development Mode (build from current branch)\n\n1. In Dokploy, create a project from this repository/branch.\n2. Set compose path to `docker-compose.dokploy.yml`.\n3. Add env vars from `docker.env.example` and set at least:\n   - `MONGO_PASSWORD`\n   - `PANEL_DOMAIN`\n   - `ACME_EMAIL`\n   - `ENCRYPTION_KEY`\n   - `SESSION_SECRET`\n   - `DOKPLOY_PANEL_HOST` (domain used in Traefik `Host(...)` rule)\n   - `DOKPLOY_TRAEFIK_SERVICE_PORT` (Traefik target/backend port, default `3000`)\n4. Deploy: Dokploy will run `build: .` and start the stack.\n\nThis mode is best when you test branch changes before publishing a release image.\n\n### Release Mode (Docker Hub image)\n\nIf you want stable deploys from Docker Hub tags (without building), replace `backend` image source in Dokploy compose with:\n\n```yaml\nbackend:\n  image: clickdevtech/hysteria-panel:latest\n  # or pin a release tag, e.g. clickdevtech/hysteria-panel:v1.2.3\n  restart: always\n  depends_on:\n    mongo:\n      condition: service_healthy\n    redis:\n      condition: service_healthy\n  expose:\n    - \"${DOKPLOY_TRAEFIK_SERVICE_PORT:-3000}\"\n  labels:\n    - \"traefik.enable=true\"\n    - \"traefik.http.routers.celerity.rule=Host(`${DOKPLOY_PANEL_HOST}`)\"\n    - \"traefik.http.routers.celerity.entrypoints=websecure\"\n    - \"traefik.http.routers.celerity.tls=true\"\n    - \"traefik.http.services.celerity.loadbalancer.server.port=${DOKPLOY_TRAEFIK_SERVICE_PORT:-3000}\"\n  env_file:\n    - .env\n```\n\nUse `latest` for fast updates, or pin an explicit tag for predictable production rollouts.\n\n---\n\n## ✨ Features\n\n- 🖥 **Web Panel** — Full UI for managing nodes and users\n- 🔐 **Dual Protocol** — Hysteria 2 and Xray VLESS on one panel\n- 🛡️ **Panel 2FA (TOTP)** — Unified TOTP verification flow for admin login and sensitive security actions\n- 🚀 **Auto Node Setup** — Install Hysteria/Xray, certs, port hopping in one click\n- 👥 **Server Groups** — Flexible user-to-node mapping\n- ⚖️ **Load Balancing** — Distribute users by server load\n- 🚫 **Traffic Filtering (ACL)** — Block ads, domains, IPs; route through custom proxies\n- 🧩 **Advanced Hysteria Config** — optional ACME challenge options, masquerade modes, resolver, speed test, sniffing, and QUIC tuning\n- 📊 **Statistics** — Online users, traffic, server status\n- 📱 **Subscriptions** — Auto-format for Clash, Sing-box, Shadowrocket, Hiddify\n- 🔄 **Backup/Restore** — Automatic backups with S3 support\n- 💻 **SSH Terminal** — Direct node access from browser\n- 🔑 **API Keys** — Secure external access with scopes, IP allowlist, rate limiting\n- 🪝 **Webhooks** — Real-time event notifications with HMAC-SHA256 signing\n- 🗺 **Network Map** — Visual cascade topology with Forward/Reverse chain routing *(beta)*\n- 🤖 **MCP Integration** — Native AI assistant support (Claude, Cursor, etc.) for panel management\n\n---\n\n## ⚠️ Beta Features\n\n### Network Map \u0026 Cascade Topology\n\n\u003e **Status:** beta — fully functional, but manual verification after deploy is recommended.\n\nCascade topology allows building server chains where clients connect to one node while traffic exits through another. This is useful in scenarios where the entry point must reside in a specific network or jurisdiction — for example, when connections must originate from local cloud provider IP ranges.\n\n#### Why Use This\n\nMany corporate and carrier networks apply IP-range filtering. Traffic to well-known local hosting providers may pass unrestricted, while connections to foreign IPs are blocked or throttled. Cascade topology solves this: clients connect to a server in a \"trusted\" IP range, and traffic is transparently proxied to an external server.\n\n#### Xray Mechanisms Used\n\nThe panel generates Xray-core configurations using the following mechanisms:\n\n| Mechanism | Purpose |\n|-----------|---------|\n| **Reverse Proxy** | Xray bridge/portal — allows a server behind NAT to initiate a connection to a public node and receive traffic through it |\n| **Outbound Chaining** | Sequential proxying through multiple outbounds via `proxySettings.tag` |\n| **REALITY** | TLS handshake camouflage to look like a connection to a legitimate site; no domain or certificate required |\n| **Transport Layer Proxy** | `transportLayer: true` mode for correct REALITY application in hop chains |\n\n#### Link Modes\n\n**Reverse Proxy** — classic Xray reverse scheme. The Bridge server (typically abroad) initiates a persistent connection to the Portal server. Clients connect to Portal, traffic exits through Bridge.\n\n```\nClient ──▶ Portal (entry) ◀── tunnel ── Bridge (exit) ──▶ Internet\n                           (bridge initiates connection)\n```\n\n- Portal can be behind NAT or firewall — no incoming connections required\n- Suitable for scenarios where the entry point must be in a specific network\n\n**Forward Chain** — direct outbound chain. Portal establishes connections through relay nodes to the exit Bridge.\n\n```\nClient ──▶ Portal ──▶ Relay (opt.) ──▶ Bridge (exit) ──▶ Internet\n           (chained outbounds)\n```\n\n- All nodes in the chain must have a public IP\n- REALITY is supported on each hop to encrypt inter-server traffic\n\n#### REALITY Between Nodes\n\nTunnel-REALITY is configured **independently** from the client-facing REALITY on the Portal node. This enables:\n\n- Encrypting inter-server traffic without drawing attention\n- Using different SNI/destination for clients vs. tunnels\n- Auto-generating x25519 keys and shortIds when creating links\n\n#### Post-Deploy Recommendations\n\n1. Check hop statuses on the Network Map\n2. Verify traffic exits through the expected Bridge (check exit IP)\n3. For Forward Chain — confirm each relay is reachable on its `tunnelPort`\n\n#### Limitations\n\n| Constraint | Reason |\n|------------|--------|\n| REALITY + WebSocket | WebSocket doesn't support uTLS fingerprint required by REALITY |\n| Forward Chain without public IP | Each hop must accept incoming connections |\n| Mixed modes in one chain | Reverse and Forward use different Xray mechanisms and cannot be combined |\n| Same port on relay for two hops | A relay that is both bridge and portal requires different ports for incoming and outgoing tunnels |\n\n---\n\n## 🏗 Architecture\n\n```\n                              ┌─────────────────┐\n                              │     CLIENTS     │\n                              │ Clash, Sing-box │\n                              │   Shadowrocket  │\n                              └────────┬────────┘\n                                       │\n                     hysteria2:// or vless://\n                                       │\n              ┌────────────────────────┼────────────────────────┐\n              ▼                        ▼                        ▼\n     ┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐\n     │  Hysteria Node  │      │   Xray Node     │      │  Hysteria Node  │\n     │   :443 + hop    │      │  VLESS Reality  │      │   :443 + hop    │\n     └────────┬────────┘      └────────┬────────┘      └────────┬────────┘\n              │                        │                        │\n              │    POST /api/auth      │   CC Agent API         │\n              │    GET /online         │                        │\n              └────────────────────────┼────────────────────────┘\n                                       ▼\n                          ┌────────────────────────┐\n                          │    HYSTERIA PANEL      │\n                          │                        │\n                          │  • Web UI (/panel)     │\n                          │  • HTTP Auth API       │\n                          │  • Subscriptions       │\n                          │  • SSH Terminal        │\n                          │  • Stats Collector     │\n                          └───────────┬────────────┘\n                                      │\n                                      ▼\n                          ┌────────────────────────┐\n                          │       MongoDB          │\n                          └────────────────────────┘\n```\n\n### How Authentication Works\n\n**Hysteria:**\n1. Client connects to node with `userId:password`\n2. Node sends `POST /api/auth` to panel\n3. Panel validates user and returns `{ \"ok\": true/false }`\n\n**Xray:**\n1. Client connects with UUID (xrayUuid)\n2. CC Agent on node manages user list via API\n3. Panel syncs users to node without restarting Xray\n\n### Server Groups\n\nInstead of rigid \"plans\", use flexible groups:\n- Create group (e.g., \"Europe\", \"Premium\")\n- Assign nodes to group\n- Assign users to group\n- User gets only nodes from their groups in subscription\n\n---\n\n## 🔧 Node Types\n\n### Hysteria 2\n\nFast UDP protocol based on QUIC with port hopping and obfuscation support.\n\n**Advantages:**\n- High speed on unstable networks\n- Port hopping to bypass blocks\n- Salamander obfuscation\n\n**Settings:**\n- Port, port range for hopping\n- ACME or self-signed certificates\n- Obfs (Salamander) with password\n\n**Advanced Hysteria settings in panel:**\n- Port Hopping interval (`hopInterval`)\n- ACME advanced options (challenge type, alt ports, DNS challenge provider and config)\n- Masquerade modes: `proxy` and `string`\n- Bandwidth limits (`up` / `down`) and `ignoreClientBandwidth`\n- Built-in `speedTest`, `disableUDP`, `udpIdleTimeout`\n- Protocol sniffing (`sniff`) and QUIC parameters (`quic`)\n- Custom DNS resolver (`resolver`)\n- ACL source mode (`inline` or `file`) + GeoIP/GeoSite paths\n- Advanced sections are optional and omitted from generated config until enabled in UI\n\n### Xray VLESS\n\nModern protocol with Reality support and various transports.\n\n**Advantages:**\n- Reality — disguise as legitimate HTTPS traffic\n- Multiple transports (TCP, WebSocket, gRPC, XHTTP)\n- No domain required for Reality\n\n**Transports:**\n\n| Transport | Description | Client Support |\n|-----------|-------------|----------------|\n| TCP | Direct connection, max speed | All clients |\n| WebSocket | Works through CDN and proxies | All clients |\n| gRPC | Multiplexing, good for CDN | All clients |\n| XHTTP | New splithttp transport | Limited* |\n\n*XHTTP is not supported by all clients (Clash/Sing-box don't support it yet)\n\n**Security:**\n\n| Mode | Description |\n|------|-------------|\n| Reality | Disguise as popular site, no domain needed |\n| TLS | Classic TLS with certificate |\n| None | No encryption (not recommended) |\n\n---\n\n## 🚀 Xray Node Setup\n\n### Automatic Setup (Recommended)\n\n1. Add node in panel:\n   - Type: **Xray**\n   - IP, SSH credentials\n   - Security: Reality (recommended)\n   - Transport: TCP (recommended for Reality)\n\n2. Click \"⚙️ Auto Setup\"\n\n3. Panel will automatically:\n   - Install Xray-core\n   - Generate Reality keys (x25519)\n   - Upload config\n   - Install CC Agent for user management\n   - Open firewall ports\n   - Start services\n\n### Reality Settings\n\n| Field | Description | Example |\n|-------|-------------|---------|\n| Dest | Disguise destination (domain:port) | `www.google.com:443` |\n| SNI | Server Name Indication | `www.google.com` |\n| Private Key | x25519 private key | Auto-generated |\n| Public Key | Public key (for clients) | Auto-generated |\n| Short IDs | Session identifiers | Auto-generated |\n\n### CC Agent\n\nCC Agent is a lightweight HTTP service on the node for managing Xray users without restart.\n\n**Features:**\n- Add/remove users on the fly\n- Traffic stats collection\n- Health check\n\nAgent is installed automatically during Xray node auto-setup.\n\n---\n\n## 🔧 Hysteria Node Setup\n\n### Understanding Node Configuration\n\n#### Ports\n- **Main port (443)** — Port Hysteria listens on\n- **Port hopping range (20000-50000)** — UDP ports for hopping\n- **Stats port (9999)** — Internal port for stats collection\n\n#### Domain vs SNI\n\n| Field | Purpose | Example |\n|-------|---------|---------|\n| **Domain** | For ACME/Let's Encrypt certificates | `de1.example.com` → `1.2.3.4` |\n| **SNI** | For masquerading (domain fronting) | `www.google.com` |\n\n**Scenarios:**\n1. **Simple setup**: Set domain, leave SNI empty\n2. **Domain fronting**: Set domain for certs, SNI as popular domain\n3. **No domain**: Leave empty — self-signed certificate will be used\n\n### Automatic Setup (Recommended)\n\n1. Add node in panel (IP, SSH credentials)\n2. Click \"⚙️ Auto Setup\"\n3. Panel will automatically:\n   - Install Hysteria 2\n   - Configure ACME or self-signed certificates\n   - Set up port hopping\n   - Open firewall ports\n   - Start service\n\n### Obfuscation (Salamander)\n\nHysteria supports obfuscation to disguise traffic:\n\n1. Enable **Obfs** in node settings\n2. Set **obfuscation password**\n3. Save and update config\n\nClients will automatically receive obfs params in subscription.\n\n### Single VPS Setup (Panel + Node)\n\nYou can run panel and node on the same VPS (panel TCP, node UDP on 443).\n\n**Option 1: Use panel domain (recommended)**\n- Set node domain same as panel domain\n- Panel certificates will be copied automatically\n\n**Option 2: No domain (self-signed)**\n- Leave domain field empty\n- Self-signed certificate will be generated\n\n---\n\n## 📖 API Reference\n\n### API Key Authentication\n\nAll `/api/*` endpoints (except `/api/auth` and `/api/files`) require authentication.\n\n**Create a key:** Settings → Security → API Keys → Create Key\n\n**Usage:**\n```http\n# Option 1 — header\nX-API-Key: ck_your_key_here\n\n# Option 2 — Bearer token\nAuthorization: Bearer ck_your_key_here\n```\n\n#### Scopes\n\n| Scope | Access |\n|-------|--------|\n| `users:read` | Read users |\n| `users:write` | Create / update / delete users |\n| `nodes:read` | Read nodes |\n| `nodes:write` | Create / update / delete / sync nodes |\n| `stats:read` | Read stats and groups |\n| `sync:write` | Trigger sync, kick users |\n\n#### Rate Limiting\n\nEach key has a configurable rate limit (default: 60 req/min).  \nExceeded requests return `429` with `X-RateLimit-Limit` / `X-RateLimit-Remaining` headers.\n\n---\n\n### Authentication (for nodes)\n\n#### POST `/api/auth`\n\nValidates user on Hysteria node connection.\n\n```json\n// Request\n{ \"addr\": \"1.2.3.4:12345\", \"auth\": \"userId:password\" }\n\n// Response (success)\n{ \"ok\": true, \"id\": \"userId\" }\n\n// Response (error)\n{ \"ok\": false }\n```\n\n### Subscriptions\n\n#### GET `/api/files/:token`\n\nUniversal subscription endpoint. Auto-detects format by User-Agent.\n\n| User-Agent | Format |\n|------------|--------|\n| `shadowrocket` | Base64 URI list |\n| `clash`, `stash`, `surge` | Clash YAML |\n| `hiddify`, `sing-box`, `karing` | Sing-box JSON |\n| Browser | HTML page with QR code |\n| Other | Plain URI list |\n\n**Query params:** `?format=clash`, `?format=singbox`, `?format=uri`\n\n#### GET `/api/files/info/:token`\n\nSubscription info (status, traffic, expiry).\n\n### Users\n\nRequired scope: `users:read` (GET) / `users:write` (POST, PUT, DELETE)\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/api/users` | List users (pagination, filtering, sorting) |\n| GET | `/api/users/:userId` | Get user |\n| POST | `/api/users` | Create user |\n| PUT | `/api/users/:userId` | Update user |\n| DELETE | `/api/users/:userId` | Delete user |\n| POST | `/api/users/:userId/enable` | Enable user |\n| POST | `/api/users/:userId/disable` | Disable user |\n| POST | `/api/users/:userId/groups` | Add user to groups |\n| DELETE | `/api/users/:userId/groups/:groupId` | Remove user from group |\n| POST | `/api/users/sync-from-main` | Sync from external DB |\n\n### Nodes\n\nRequired scope: `nodes:read` (GET) / `nodes:write` (POST, PUT, DELETE)\n\n| Method | Endpoint | Description |\n|--------|----------|-------------|\n| GET | `/api/nodes` | List nodes |\n| GET | `/api/nodes/:id` | Get node |\n| POST | `/api/nodes` | Create node |\n| PUT | `/api/nodes/:id` | Update node |\n| DELETE | `/api/nodes/:id` | Delete node |\n| GET | `/api/nodes/:id/config` | Get node config (YAML/JSON) |\n| GET | `/api/nodes/:id/status` | Get node status |\n| POST | `/api/nodes/:id/reset-status` | Reset status to online |\n| GET | `/api/nodes/:id/users` | Get users on node |\n| POST | `/api/nodes/:id/sync` | Sync specific node |\n| POST | `/api/nodes/:id/update-config` | Push config via SSH |\n| POST | `/api/nodes/:id/setup` | Auto-setup node via SSH |\n| POST | `/api/nodes/:id/setup-port-hopping` | Setup port hopping |\n| POST | `/api/nodes/:id/groups` | Add node to groups |\n| DELETE | `/api/nodes/:id/groups/:groupId` | Remove from group |\n| GET | `/api/nodes/:id/agent-info` | Get CC Agent info (Xray) |\n| POST | `/api/nodes/:id/generate-xray-keys` | Generate Reality keys |\n\n### Stats \u0026 Sync\n\n| Method | Endpoint | Scope | Description |\n|--------|----------|-------|-------------|\n| GET | `/api/stats` | `stats:read` | Panel statistics |\n| GET | `/api/groups` | `stats:read` | List server groups |\n| POST | `/api/sync` | `sync:write` | Sync all nodes |\n| POST | `/api/kick/:userId` | `sync:write` | Kick user from all nodes |\n\n---\n\n## 🪝 Webhooks\n\nSend real-time event notifications to any HTTP endpoint.\n\n**Configure:** Settings → Security → Webhooks\n\n### Request Format\n\n```http\nPOST https://your-endpoint.com/webhook\nContent-Type: application/json\nX-Webhook-Event: user.created\nX-Webhook-Timestamp: 1700000000\nX-Webhook-Signature: sha256=\u003chmac\u003e\nUser-Agent: C3-Celerity-Webhook/1.0\n\n{\n  \"event\": \"user.created\",\n  \"timestamp\": \"2024-01-01T00:00:00.000Z\",\n  \"data\": { ... }\n}\n```\n\n### Signature Verification\n\n```js\nconst crypto = require('crypto');\nconst expected = 'sha256=' + crypto\n  .createHmac('sha256', YOUR_SECRET)\n  .update(`${timestamp}.${rawBody}`)\n  .digest('hex');\n// compare with X-Webhook-Signature header\n```\n\n### Events\n\n| Event | Trigger |\n|-------|---------|\n| `user.created` | User created |\n| `user.updated` | User updated |\n| `user.deleted` | User deleted |\n| `user.enabled` | User enabled |\n| `user.disabled` | User disabled |\n| `user.traffic_exceeded` | User traffic limit reached |\n| `user.expired` | User subscription expired |\n| `node.online` | Node came online |\n| `node.offline` | Node went offline |\n| `node.error` | Node error |\n| `sync.completed` | Sync cycle finished |\n\n---\n\n## 📊 Data Models\n\n### User\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `userId` | String | Unique ID |\n| `username` | String | Display name |\n| `subscriptionToken` | String | URL token for subscription |\n| `xrayUuid` | String | UUID for Xray VLESS (auto-generated) |\n| `enabled` | Boolean | User active status |\n| `groups` | [ObjectId] | Server groups |\n| `nodes` | [ObjectId] | Direct node assignments |\n| `traffic` | Object | `{ tx, rx, lastUpdate }` — used traffic |\n| `trafficLimit` | Number | Traffic limit in bytes (0 = unlimited) |\n| `maxDevices` | Number | Device limit (0 = group limit, -1 = unlimited) |\n| `expireAt` | Date | Expiration date |\n\n### Node\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `type` | String | `hysteria` or `xray` |\n| `name` | String | Display name |\n| `flag` | String | Country flag (emoji) |\n| `ip` | String | IP address |\n| `domain` | String | Domain for SNI/ACME |\n| `sni` | String | Custom SNI for masquerading |\n| `port` | Number | Main port (443) |\n| `portRange` | String | Port hopping range |\n| `portConfigs` | Array | Multi-port: `[{ name, port, portRange, enabled }]` |\n| `obfs` | Object | Obfuscation: `{ type: 'salamander', password }` |\n| `statsPort` | Number | Hysteria stats port (9999) |\n| `statsSecret` | String | Stats API secret |\n| `groups` | [ObjectId] | Server groups |\n| `outbounds` | Array | Proxies for ACL: `[{ name, type, addr }]` |\n| `aclRules` | [String] | ACL rules |\n| `maxOnlineUsers` | Number | Max online for load balancing |\n| `rankingCoefficient` | Number | Sorting coefficient (1.0) |\n| `status` | String | online/offline/error/syncing |\n| `traffic` | Object | `{ tx, rx, lastUpdate }` — node traffic |\n| `xray` | Object | Xray settings (see below) |\n\n#### Xray Settings (node.xray)\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `transport` | String | tcp, ws, grpc, xhttp |\n| `security` | String | reality, tls, none |\n| `flow` | String | xtls-rprx-vision (for tcp) |\n| `fingerprint` | String | chrome, firefox, safari, etc. |\n| `alpn` | [String] | ALPN protocols (h3, h2, http/1.1) |\n| `realityDest` | String | Disguise destination |\n| `realitySni` | [String] | Server names |\n| `realityPrivateKey` | String | x25519 private key |\n| `realityPublicKey` | String | Public key |\n| `realityShortIds` | [String] | Short IDs |\n| `realitySpiderX` | String | Spider X path (default: /) |\n| `wsPath` | String | WebSocket path |\n| `wsHost` | String | WebSocket host header |\n| `grpcServiceName` | String | gRPC service name |\n| `xhttpPath` | String | XHTTP path |\n| `xhttpHost` | String | XHTTP host header |\n| `xhttpMode` | String | auto, packet-up, stream-up |\n| `apiPort` | Number | Xray gRPC API port (61000) |\n| `inboundTag` | String | Inbound tag (vless-in) |\n| `agentPort` | Number | CC Agent port (62080) |\n| `agentToken` | String | Agent token |\n| `agentTls` | Boolean | TLS for CC Agent |\n\n### ServerGroup\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `name` | String | Group name |\n| `description` | String | Description |\n| `color` | String | UI color (#hex) |\n| `maxDevices` | Number | Device limit for group |\n| `subscriptionTitle` | String | Title in subscription profile |\n\n---\n\n## 🚫 Traffic Filtering (ACL)\n\nControl traffic routing on each Hysteria node. Access: **Panel → Node → Traffic Filtering**.\n\n### Built-in Actions\n\n| Action | Description |\n|--------|-------------|\n| `reject(...)` | Block connection |\n| `direct(...)` | Allow through server |\n\n### Rule Examples\n\n```\nreject(suffix:doubleclick.net)     # Block ads\nreject(suffix:googlesyndication.com)\nreject(geoip:cn)                   # Block Chinese IPs\nreject(geoip:private)              # Block private IPs\ndirect(all)                        # Allow everything else\n```\n\n### Custom Proxy Routing\n\n1. Add proxy (e.g., `my-proxy`, SOCKS5, `1.2.3.4:1080`)\n2. Use in rules: `my-proxy(geoip:ru)`\n\n---\n\n## ⚖️ Load Balancing\n\nConfigure in **Settings**:\n\n- **Enable balancing** — Sort nodes by current load\n- **Hide overloaded** — Exclude nodes at capacity\n\nAlgorithm:\n1. Get user's nodes from groups\n2. Sort by load % (online/max)\n3. Filter overloaded if enabled\n4. Fall back to `rankingCoefficient`\n\n---\n\n## 🔒 Device Limits\n\n**Priority:**\n1. User's personal limit (`maxDevices \u003e 0`)\n2. Minimum limit from user's groups\n3. `-1` = unlimited\n\n**Device Grace Period** — delay (in seconds) before counting a disconnected device, to avoid false triggers during reconnections.\n\n---\n\n## 📱 Subscription Page Customization\n\nCustomize the HTML subscription page in **Settings → Subscription**:\n\n| Field | Description |\n|-------|-------------|\n| `Logo URL` | Logo URL for page header |\n| `Page Title` | Page title |\n| `Support URL` | Support link (button at bottom) |\n| `Web Page URL` | Profile URL (`profile-web-page-url` header) |\n\nThe subscription page automatically shows:\n- QR code for app import\n- Traffic stats and expiration\n- Location list with copy buttons\n\n---\n\n## 💾 Backups\n\n### Auto Backups\n\nConfigure in **Settings → Backups**:\n- Interval (in hours)\n- Number of local copies to keep\n\n### Manual Backup\n\nDashboard button — file auto-downloads.\n\n### Restore\n\nUpload `.tar.gz` archive via interface.\n\n### S3-Compatible Storage\n\nBackups can be automatically uploaded to S3-compatible storage (AWS S3, MinIO, Backblaze B2, Cloudflare R2, etc.).\n\n**Configure:** Settings → Backups → S3\n\n| Field | Description |\n|-------|-------------|\n| `Endpoint` | Storage URL (for MinIO, etc.). Leave empty for AWS S3 |\n| `Region` | Region (e.g., `us-east-1`) |\n| `Bucket` | Bucket name |\n| `Prefix` | Prefix/folder for backups |\n| `Access Key ID` | Access key |\n| `Secret Access Key` | Secret key |\n| `Keep Last` | How many backups to keep in S3 |\n\n**Configuration examples:**\n\n```env\n# AWS S3\nEndpoint: (empty)\nRegion: eu-central-1\nBucket: my-backups\n\n# MinIO\nEndpoint: https://minio.example.com\nRegion: us-east-1\nBucket: backups\n\n# Cloudflare R2\nEndpoint: https://\u003caccount-id\u003e.r2.cloudflarestorage.com\nRegion: auto\nBucket: my-backups\n```\n\n---\n\n## 🐳 Docker Compose\n\n```yaml\nversion: '3.8'\n\nservices:\n  mongo:\n    image: mongo:7\n    restart: always\n    volumes:\n      - mongo_data:/data/db\n    environment:\n      MONGO_INITDB_ROOT_USERNAME: ${MONGO_USER:-hysteria}\n      MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD}\n\n  backend:\n    image: clickdevtech/hysteria-panel:latest\n    restart: always\n    depends_on:\n      - mongo\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n    volumes:\n      - ./logs:/app/logs\n      - ./greenlock.d:/app/greenlock.d\n      - ./backups:/app/backups\n    env_file:\n      - .env\n\nvolumes:\n  mongo_data:\n```\n\n---\n\n## 📝 Environment Variables\n\n| Variable | Required | Description |\n|----------|----------|-------------|\n| `PANEL_DOMAIN` | ✅ | Panel domain |\n| `DOKPLOY_PANEL_HOST` | ❌ | Traefik host for Dokploy (`Host(...)` rule) |\n| `DOKPLOY_TRAEFIK_SERVICE_PORT` | ❌ | Traefik/backend service port in Dokploy (default: `3000`) |\n| `ACME_EMAIL` | ✅ | Let's Encrypt email |\n| `ENCRYPTION_KEY` | ✅ | SSH encryption key (32 chars) |\n| `SESSION_SECRET` | ✅ | Session secret |\n| `MONGO_PASSWORD` | ✅ | MongoDB password (for Docker) |\n| `MONGO_USER` | ❌ | MongoDB user (default: hysteria) |\n| `MONGO_URI` | ❌ | MongoDB connection URI (for non-Docker) |\n| `REDIS_URL` | ❌ | Redis URL for cache (default: in-memory) |\n| `PANEL_IP_WHITELIST` | ❌ | IP whitelist for panel |\n| `SYNC_INTERVAL` | ❌ | Sync interval in minutes (default: 2) |\n| `API_DOCS_ENABLED` | ❌ | Enable interactive API docs at `/api/docs` |\n| `LOG_LEVEL` | ❌ | Logging level (default: info) |\n\n---\n\n## 🤝 Contributing\n\nPull requests welcome!\n\n---\n\n## 📄 License\n\nMIT","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclickdevtech%2Fcelerity-panel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fclickdevtech%2Fcelerity-panel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fclickdevtech%2Fcelerity-panel/lists"}