A multi-server Xray REALITY proxy provisioning system with Telegram bot control, per-server agents, bandwidth tracking, and admin UI.
┌─────────────────────────────────────────┐
│ fly.io (Bot Service) │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ Telegram Bot│ │ Bot API │ │
│ │ (grammy) │ │ :3000 │ │
│ └──────┬──────┘ └────────┬────────┘ │
│ │ SQLite │ │
│ └────────┬─────────┘ │
│ │ │
│ /app/data/users.db │
│ (persistent volume) │
└─────────────────┬───────────────────────┘
│
polls GET /stats every 5min
sends POST /apply-users on changes
│ ┌─────────────────┐
┌───────────┴───────────┐ │ Admin SPA │
▼ ▼ │ (Vercel) │
┌───────────┐ ┌───────────┐ │ React + Vite │
│ Agent #1 │──▶ Xray │ Agent #2 │──▶ Xray └───────┬─────────┘
│ (VPS) │ Server │ (VPS) │ Server │
└───────────┘ └───────────┘ │
calls Bot API via CORS
- Telegram Bot - User management via
/new,/delete,/usagecommands - Multi-Language - Bot messages in English or Farsi, configurable via Admin UI
- Bandwidth Tracking - Per-user usage tracking with configurable limits
- Multi-Server - Distribute users across multiple proxy servers
- Admin SPA - React-based web dashboard (deployed on Vercel) for user management, agent monitoring, and settings
- Agent Management - Dedicated Agents tab with health monitoring, manual agent addition, and status tracking
- Auto-Limiting - Users over bandwidth limit are automatically excluded
- Remote Deployment - Deploy new agents directly from the admin UI via SSH
# Clone the repo
git clone <repo-url>
cd xray-reality-proxy
# Create fly.io app
fly launch --no-deploy
# Create persistent volume for SQLite database
fly volumes create proxy_data --region ams --size 1
# Set required secrets
fly secrets set \
TELEGRAM_BOT_TOKEN="your-bot-token" \
ADMIN_USER="admin" \
ADMIN_PASSWORD="your-secure-password"
# Optional: Seed initial config (can also be set in Admin UI)
fly secrets set \
SERVERS="us.example.com:443:publicKey1:www.google.com;eu.example.com:443:publicKey2:www.google.com" \
REALITY_DEST="www.google.com:443" \
REALITY_SERVER_NAMES="www.google.com"
# Deploy
fly deploy-
Configure SSH access on fly.io:
fly secrets set SSH_PRIVATE_KEY="$(cat ~/.ssh/id_rsa)"
-
In Admin UI Settings, configure:
- Allowed IPs for Agent API: Your fly.io app's IP (for security)
- Default Tarball URL: URL to your agent tarball (e.g., GitHub release)
-
Click "Deploy New Agent" and enter the server IP
-
The deployment will automatically:
- Install Bun and Xray
- Generate REALITY keys
- Configure and start services
- Add the new agent to your configuration
# Clone the repo
git clone <repo-url> /opt/xray-agent
cd /opt/xray-agent
# Run setup script
chmod +x deploy/scripts/setup-agent.sh
./deploy/scripts/setup-agent.sh
# Edit configuration
nano /opt/xray-agent/.env
# Start services
systemctl start xray-agent
systemctl start xraycd packages/admin
# Install Vercel CLI if needed
bun add -g vercel
# Deploy to Vercel
vercel
# Set environment variable
vercel env add VITE_API_URL
# Enter: https://xray-proxy-bot.fly.dev (your fly.io app URL)
# Deploy production
vercel --prod
# Update fly.io with Admin SPA URL
fly secrets set \
ADMIN_ORIGIN="https://your-admin.vercel.app" \
ADMIN_URL="https://your-admin.vercel.app"| Variable | Description | Default |
|---|---|---|
TELEGRAM_BOT_TOKEN |
Telegram bot token from @BotFather | Required |
ADMIN_USER |
Admin UI username | Required |
ADMIN_PASSWORD |
Admin UI password | Required |
BANDWIDTH_LIMIT_GB |
Per-user bandwidth limit in GB | 50 |
STATS_POLL_INTERVAL |
Stats polling interval in seconds | 300 |
USAGE_RESET_DAY |
Day of month to reset usage (0=disabled) | 1 |
ADMIN_ORIGIN |
Admin SPA origin for CORS (e.g., https://your-admin.vercel.app) |
- |
ADMIN_URL |
Admin SPA URL for redirect (e.g., https://your-admin.vercel.app) |
- |
SSH_PRIVATE_KEY |
SSH private key for remote agent deployment | - |
DEFAULT_TARBALL_URL |
Fallback URL for agent tarball (can also be set in Admin UI) | - |
| Variable | Description |
|---|---|
SERVERS |
Server config string (seeds database on first run) |
REALITY_DEST |
REALITY destination (e.g., www.google.com:443) |
REALITY_SERVER_NAMES |
Comma-separated server names |
SERVERS format: address:port:publicKey:sni;address2:port2:publicKey2:sni2
Note: These config values are only used to seed the database on first run. After that, all configuration is managed through the Admin UI. Agents are managed via the dedicated Agents tab in the Admin UI.
# Agent HTTP port
AGENT_PORT=8080
# Allowed IPs (fly.io app IP or 0.0.0.0 for all)
ALLOWED_IPS=0.0.0.0
# Xray config path
XRAY_CONFIG_PATH=/usr/local/etc/xray/config.json
# Xray private key (generate with: xray x25519)
XRAY_PRIVATE_KEY=your_private_keyNote: REALITY settings (dest, server names) are now sent from the bot on each sync, so they don't need to be configured on agents.
| Command | Description |
|---|---|
/start, /help |
Show help message |
/new |
Create new connection (max 2 per user) |
/delete <id> |
Delete connection by short ID |
/usage |
Show bandwidth usage for all connections |
Language: Bot messages display in English (default) or Farsi. Configure via Admin UI Settings > Bot Language.
| Endpoint | Description |
|---|---|
GET /health |
Health check |
GET /api/users |
List all users with usage data |
POST /api/users |
Create new user |
DELETE /api/users/:shortId |
Delete user |
GET /api/servers |
List configured servers |
GET /api/config |
Get all configuration |
PUT /api/config |
Update configuration |
GET /api/agents |
List all agents with health status |
POST /api/agents |
Add agent manually |
DELETE /api/agents/:id |
Remove agent |
POST /api/agents/:id/health-check |
Trigger health check for agent |
POST /api/sync |
Force sync to all agents |
POST /api/deploy |
Deploy agent to new server (SSE stream) |
| Endpoint | Description |
|---|---|
GET /health |
Health check |
GET /stats |
Get usage stats (resets Xray counters) |
POST /apply-users |
Apply user list to Xray |
# Install dependencies
bun install
# Run bot locally (requires .env file)
bun run dev:bot
# Run agent locally
bun run dev:agent
# Run admin SPA locally (connects to localhost:3000)
bun run admin
# Build admin SPA
bun run admin:build
# Build Docker image locally
bun run docker:build
# Run Docker container locally
bun run docker:runThe system tracks bandwidth usage per user:
- Collection: Bot polls agents every 5 minutes via
GET /stats - Storage: Usage is accumulated in SQLite
bandwidth_usagetable - Enforcement: Users over limit are excluded from
apply-userssync - Reset: Usage resets on configured day of month (default: 1st)
Users over limit see their status in /usage and cannot connect until:
- Usage resets on the configured day
- Admin manually resets their usage
# Generate Xray x25519 keys
xray x25519
# Output:
# Private key: <use in agent .env as XRAY_PRIVATE_KEY>
# Public key: <use in bot SERVERS config># View logs
fly logs
# SSH into container
fly ssh console
# Check app status
fly status# Check service status
systemctl status xray-agent
systemctl status xray
# View logs
journalctl -u xray-agent -f
journalctl -u xray -f
# Test agent manually
curl http://localhost:8080/health
curl http://localhost:8080/stats
# Validate Xray config
xray run -test -c /usr/local/etc/xray/config.json- Bot Service: Deployed on fly.io with HTTPS
- Admin SPA: React app on Vercel, authenticates via HTTP Basic Auth to Bot API
- Bot API: Protected with HTTP Basic Auth (set
ADMIN_USERandADMIN_PASSWORD), CORS restricted toADMIN_ORIGIN - Agent API: Protected by IP allowlist (configure
ALLOWED_IPS)
MIT