Files
OutFleet/vpn/views.py

636 lines
27 KiB
Python
Raw Normal View History

2025-07-20 23:04:58 +03:00
def userPortal(request, user_hash):
2025-08-08 05:46:36 +03:00
"""HTML portal for user to view their VPN access links and subscription groups"""
from .models import User
from .models_xray import UserSubscription, SubscriptionGroup, Inbound
2025-07-21 12:47:47 +03:00
from django.utils import timezone
2025-07-21 13:23:10 +03:00
from datetime import timedelta
2025-07-20 23:04:58 +03:00
import logging
logger = logging.getLogger(__name__)
try:
user = get_object_or_404(User, hash=user_hash)
logger.info(f"User portal accessed for user {user.username}")
except Http404:
logger.warning(f"User portal access attempt with invalid hash: {user_hash}")
return render(request, 'vpn/user_portal_error.html', {
'error_title': 'Access Denied',
'error_message': 'Invalid access link. Please contact your administrator.'
}, status=403)
try:
2025-08-08 05:46:36 +03:00
# Get all active subscription groups for the user
user_subscriptions = UserSubscription.objects.filter(
user=user,
active=True,
subscription_group__is_active=True
).select_related('subscription_group').prefetch_related('subscription_group__inbounds')
2025-07-21 12:47:47 +03:00
2025-08-08 05:46:36 +03:00
logger.info(f"Found {user_subscriptions.count()} active subscription groups for user {user.username}")
2025-08-08 06:50:04 +03:00
# Calculate overall Xray subscription statistics
from .models import AccessLog
total_connections = AccessLog.objects.filter(
user=user.username,
action='Success',
server='Xray-Subscription'
).count()
recent_connections = AccessLog.objects.filter(
user=user.username,
action='Success',
server='Xray-Subscription',
timestamp__gte=timezone.now() - timedelta(days=30)
).count()
logger.info(f"Xray statistics for user {user.username}: total={total_connections}, recent={recent_connections}")
2025-07-20 23:04:58 +03:00
2025-08-05 01:50:11 +03:00
# Determine protocol scheme
2025-08-08 10:32:14 +03:00
scheme = 'https' # Always use HTTPS as SSL is handled by ingress
2025-08-05 01:50:11 +03:00
2025-08-08 05:46:36 +03:00
# Group inbounds by subscription group
groups_data = {}
total_inbounds = 0
2025-07-20 23:04:58 +03:00
2025-08-08 05:46:36 +03:00
for subscription in user_subscriptions:
group = subscription.subscription_group
group_name = group.name
logger.debug(f"Processing subscription group {group_name}")
2025-07-20 23:04:58 +03:00
2025-08-08 07:39:01 +03:00
# Get all deployed inbounds for this group (count actual server deployments)
from .models_xray import ServerInbound
deployed_inbounds = ServerInbound.objects.filter(
inbound__in=group.inbounds.all(),
active=True
).select_related('inbound', 'server')
2025-07-21 12:47:47 +03:00
2025-08-08 06:50:04 +03:00
# Calculate connections for this specific group
group_connections = AccessLog.objects.filter(
user=user.username,
action='Success',
server='Xray-Subscription',
data__icontains=f'"group": "{group_name}"'
).count()
2025-08-08 05:46:36 +03:00
groups_data[group_name] = {
'group': group,
'subscription': subscription,
'inbounds': [],
2025-08-08 06:50:04 +03:00
'total_connections': group_connections,
2025-08-08 07:39:01 +03:00
'deployed_count': deployed_inbounds.count(), # Actual deployed inbounds count
2025-08-08 05:46:36 +03:00
}
2025-07-21 12:47:47 +03:00
2025-08-08 07:39:01 +03:00
# Process each deployed inbound (each server-inbound combination)
for server_inbound in deployed_inbounds:
inbound = server_inbound.inbound
2025-08-08 05:46:36 +03:00
logger.debug(f"Processing inbound {inbound.name} in group {group_name}")
2025-07-21 13:23:10 +03:00
2025-08-08 05:46:36 +03:00
# Generate connection URLs based on protocol
connection_urls = []
2025-07-21 13:23:10 +03:00
2025-08-08 05:46:36 +03:00
if inbound.protocol == 'vless':
# Generate VLESS URL - this is a placeholder implementation
# In the real implementation, you'd generate proper VLESS URLs with user UUID
2025-08-08 10:32:14 +03:00
connection_url = f"vless://user-uuid@{get_external_host()}:{inbound.port}#{inbound.name}"
2025-08-08 05:46:36 +03:00
connection_urls.append({
'url': connection_url,
'protocol': 'VLESS',
'name': inbound.name
})
elif inbound.protocol == 'vmess':
# Generate VMess URL - placeholder
2025-08-08 10:32:14 +03:00
connection_url = f"vmess://user-config@{get_external_host()}:{inbound.port}#{inbound.name}"
2025-08-08 05:46:36 +03:00
connection_urls.append({
'url': connection_url,
'protocol': 'VMess',
'name': inbound.name
})
elif inbound.protocol == 'trojan':
# Generate Trojan URL - placeholder
2025-08-08 10:32:14 +03:00
connection_url = f"trojan://user-password@{get_external_host()}:{inbound.port}#{inbound.name}"
2025-08-08 05:46:36 +03:00
connection_urls.append({
'url': connection_url,
'protocol': 'Trojan',
'name': inbound.name
})
elif inbound.protocol == 'shadowsocks':
# Generate Shadowsocks URL - placeholder
2025-08-08 10:32:14 +03:00
connection_url = f"ss://user-config@{get_external_host()}:{inbound.port}#{inbound.name}"
2025-08-08 05:46:36 +03:00
connection_urls.append({
'url': connection_url,
'protocol': 'Shadowsocks',
'name': inbound.name
})
2025-07-21 12:47:47 +03:00
2025-08-08 05:46:36 +03:00
inbound_data = {
'inbound': inbound,
'connection_urls': connection_urls,
'protocol': inbound.protocol.upper(),
'port': inbound.port,
2025-08-08 10:32:14 +03:00
'domain': get_external_host(),
2025-08-08 05:46:36 +03:00
'network': inbound.network,
'security': inbound.security,
'connections': 0, # Placeholder during transition
'last_access_display': "Never used", # Placeholder
2025-08-08 07:39:01 +03:00
'server': server_inbound.server, # Server that deployed this inbound
'server_name': server_inbound.server.name, # Server name for display
2025-08-08 05:46:36 +03:00
}
2025-07-21 12:47:47 +03:00
2025-08-08 05:46:36 +03:00
groups_data[group_name]['inbounds'].append(inbound_data)
total_inbounds += 1
logger.debug(f"Added inbound data for {inbound.name}")
2025-07-21 12:47:47 +03:00
2025-08-08 05:46:36 +03:00
logger.info(f"Prepared data for {len(groups_data)} subscription groups and {total_inbounds} total inbounds")
2025-07-21 12:47:47 +03:00
logger.info(f"Portal statistics: total_connections={total_connections}, recent_connections={recent_connections}")
2025-07-20 23:04:58 +03:00
2025-08-08 05:46:36 +03:00
# Check if user has any Xray subscription groups
has_xray_access = user_subscriptions.exists()
2025-08-05 01:23:07 +03:00
2025-08-08 05:46:36 +03:00
# Also get old-style ACL links for backwards compatibility
acl_links = []
try:
from .models import ACLLink
acl_links = ACLLink.objects.filter(acl__user=user).select_related('acl__server', 'acl')
except:
pass
2025-07-20 23:04:58 +03:00
context = {
'user': user,
2025-08-08 05:46:36 +03:00
'user_subscriptions': user_subscriptions, # For accessing user's subscriptions in template
'groups_data': groups_data,
'total_groups': len(groups_data),
'total_inbounds': total_inbounds,
2025-07-21 12:47:47 +03:00
'total_connections': total_connections,
'recent_connections': recent_connections,
2025-08-08 10:32:14 +03:00
'external_address': get_external_host(),
2025-08-08 05:46:36 +03:00
'has_xray_access': has_xray_access,
2025-08-05 01:50:11 +03:00
'force_scheme': scheme, # Override request.scheme in template
2025-08-08 05:46:36 +03:00
'acl_links': acl_links, # For backwards compatibility
'has_old_links': len(acl_links) > 0,
2025-08-08 10:32:14 +03:00
'xray_subscription_url': f"https://{request.get_host()}/xray/{user.hash}",
2025-07-20 23:04:58 +03:00
}
2025-07-21 12:47:47 +03:00
logger.debug(f"Context prepared with keys: {list(context.keys())}")
2025-08-08 05:46:36 +03:00
logger.debug(f"Groups in context: {list(groups_data.keys())}")
2025-07-21 12:47:47 +03:00
2025-08-08 05:46:36 +03:00
# Log sample group data for debugging
for group_name, group_data in groups_data.items():
logger.debug(f"Group {group_name}: total_connections={group_data['total_connections']}, inbounds_count={len(group_data['inbounds'])}")
for i, inbound_data in enumerate(group_data['inbounds']):
logger.debug(f" Inbound {i}: protocol={inbound_data['protocol']}, port={inbound_data['port']}, connections={inbound_data['connections']}")
2025-07-21 12:47:47 +03:00
2025-07-20 23:04:58 +03:00
return render(request, 'vpn/user_portal.html', context)
except Exception as e:
2025-07-21 12:47:47 +03:00
logger.error(f"Error loading user portal for {user.username}: {e}", exc_info=True)
2025-07-20 23:04:58 +03:00
return render(request, 'vpn/user_portal_error.html', {
'error_title': 'Server Error',
'error_message': 'Unable to load your VPN information. Please try again later or contact support.'
}, status=500)
2025-02-23 19:18:23 +00:00
import yaml
2025-02-23 19:33:54 +00:00
import json
2025-07-20 23:04:58 +03:00
import logging
from django.shortcuts import get_object_or_404, render
2024-10-28 17:15:49 +00:00
from django.http import JsonResponse, HttpResponse, Http404
2025-02-25 12:39:08 +02:00
from mysite.settings import EXTERNAL_ADDRESS
2025-08-08 10:32:14 +03:00
from urllib.parse import urlparse
def get_external_host():
"""Extract hostname from EXTERNAL_ADDRESS, removing scheme"""
parsed = urlparse(EXTERNAL_ADDRESS)
if parsed.hostname:
return parsed.hostname
# If no scheme, assume it's just a hostname
return EXTERNAL_ADDRESS
2025-02-25 12:39:08 +02:00
def userFrontend(request, user_hash):
from .models import User, ACLLink
try:
user = get_object_or_404(User, hash=user_hash)
except Http404:
return JsonResponse({"error": "Not allowed"}, status=403)
acl_links = {}
for link in ACLLink.objects.filter(acl__user=user).select_related('acl__server'):
server_name = link.acl.server.name
if server_name not in acl_links:
acl_links[server_name] = []
2025-08-08 10:32:14 +03:00
acl_links[server_name].append({"link": f"https://{request.get_host()}/ss/{link.link}#{link.acl.server.name}", "comment": link.comment})
2025-02-25 12:39:08 +02:00
return JsonResponse(acl_links)
2024-10-20 21:57:12 +00:00
def shadowsocks(request, link):
2024-10-28 17:15:49 +00:00
from .models import ACLLink, AccessLog
2025-07-20 22:30:04 +03:00
import logging
2025-07-21 12:12:31 +03:00
from django.utils import timezone
2025-07-20 22:30:04 +03:00
logger = logging.getLogger(__name__)
2024-10-28 17:15:49 +00:00
try:
acl_link = get_object_or_404(ACLLink, link=link)
acl = acl_link.acl
2025-07-20 22:30:04 +03:00
logger.info(f"Found ACL link for user {acl.user.username} on server {acl.server.name}")
2024-10-28 17:15:49 +00:00
except Http404:
2025-07-20 22:30:04 +03:00
logger.warning(f"ACL link not found: {link}")
2025-07-21 13:49:43 +03:00
AccessLog.objects.create(
user=None,
server="Unknown",
acl_link_id=link,
action="Failed",
data=f"ACL not found for link: {link}"
)
2024-10-28 17:15:49 +00:00
return JsonResponse({"error": "Not allowed"}, status=403)
2025-02-23 21:22:12 +00:00
2024-10-21 13:22:03 +00:00
try:
server_user = acl.server.get_user(acl.user, raw=True)
2025-07-20 22:30:04 +03:00
logger.info(f"Successfully retrieved user credentials for {acl.user.username} from {acl.server.name}")
2024-10-26 12:22:19 +00:00
except Exception as e:
2025-07-20 22:30:04 +03:00
logger.error(f"Failed to get user credentials for {acl.user.username} from {acl.server.name}: {e}")
2025-07-21 13:49:43 +03:00
AccessLog.objects.create(
user=acl.user.username,
server=acl.server.name,
acl_link_id=acl_link.link,
action="Failed",
data=f"Failed to get credentials: {e}"
)
2025-07-20 22:30:04 +03:00
return JsonResponse({"error": f"Couldn't get credentials from server. {e}"}, status=500)
2024-10-21 13:22:03 +00:00
2025-08-08 05:46:36 +03:00
# Handle both dict and object formats for server_user
if isinstance(server_user, dict):
password = server_user.get('password', '')
method = server_user.get('method', 'aes-128-gcm')
port = server_user.get('port', 8080)
access_url = server_user.get('access_url', '')
else:
password = getattr(server_user, 'password', '')
method = getattr(server_user, 'method', 'aes-128-gcm')
port = getattr(server_user, 'port', 8080)
access_url = getattr(server_user, 'access_url', '')
2025-02-23 21:22:12 +00:00
if request.GET.get('mode') == 'json':
config = {
2025-02-25 12:39:08 +02:00
"info": "Managed by OutFleet_2 [github.com/house-of-vanity/OutFleet/]",
2025-08-08 05:46:36 +03:00
"password": password,
"method": method,
2025-02-23 21:22:12 +00:00
"prefix": "\u0005\u00dc_\u00e0\u0001",
"server": acl.server.client_hostname,
2025-08-08 05:46:36 +03:00
"server_port": port,
"access_url": access_url,
2025-02-23 21:22:12 +00:00
"outfleet": {
"acl_link": link,
"server_name": acl.server.name,
"server_type": acl.server.server_type,
}
}
2025-02-23 21:29:03 +00:00
response = json.dumps(config, indent=2)
2025-02-23 21:22:12 +00:00
else:
config = {
"transport": {
"$type": "tcpudp",
"tcp": {
"$type": "shadowsocks",
2025-08-08 05:46:36 +03:00
"endpoint": f"{acl.server.client_hostname}:{port}",
"cipher": f"{method}",
"secret": f"{password}",
2025-02-23 21:22:12 +00:00
"prefix": "\u0005\u00dc_\u00e0\u0001"
},
"udp": {
"$type": "shadowsocks",
2025-08-08 05:46:36 +03:00
"endpoint": f"{acl.server.client_hostname}:{port}",
"cipher": f"{method}",
"secret": f"{password}",
2025-02-23 21:22:12 +00:00
"prefix": "\u0005\u00dc_\u00e0\u0001"
}
}
}
2025-02-23 21:29:03 +00:00
response = yaml.dump(config, allow_unicode=True)
2025-02-23 21:22:12 +00:00
2025-07-21 12:12:31 +03:00
# Update last access time for this specific link
acl_link.last_access_time = timezone.now()
acl_link.save(update_fields=['last_access_time'])
2025-07-21 13:49:43 +03:00
# Create AccessLog with specific link tracking
AccessLog.objects.create(
user=acl.user.username,
server=acl.server.name,
acl_link_id=acl_link.link,
action="Success",
data=response
)
2025-02-23 19:18:23 +00:00
2025-02-23 21:22:12 +00:00
return HttpResponse(response, content_type=f"{ 'application/json; charset=utf-8' if request.GET.get('mode') == 'json' else 'text/html' }")
2025-02-23 19:18:23 +00:00
2025-08-05 01:23:07 +03:00
2025-08-08 05:46:36 +03:00
def xray_subscription(request, user_hash):
2025-08-05 01:23:07 +03:00
"""
Return Xray subscription with all available protocols for the user.
2025-08-08 05:46:36 +03:00
This generates configs based on user's subscription groups.
2025-08-05 01:23:07 +03:00
"""
2025-08-08 05:46:36 +03:00
from .models import User, AccessLog
from .models_xray import UserSubscription
2025-08-05 01:23:07 +03:00
import logging
from django.utils import timezone
import base64
2025-08-08 05:46:36 +03:00
import uuid
import json
2025-08-05 01:23:07 +03:00
logger = logging.getLogger(__name__)
2025-08-08 05:46:36 +03:00
# Clean user_hash from any trailing slashes
user_hash = user_hash.rstrip('/')
2025-08-05 01:23:07 +03:00
try:
2025-08-08 05:46:36 +03:00
user = get_object_or_404(User, hash=user_hash)
logger.info(f"Found user {user.username} for Xray subscription generation")
2025-08-05 01:23:07 +03:00
except Http404:
2025-08-08 05:46:36 +03:00
logger.warning(f"User not found for hash: {user_hash}")
2025-08-05 01:23:07 +03:00
AccessLog.objects.create(
user=None,
server="Unknown",
2025-08-08 05:46:36 +03:00
acl_link_id=user_hash,
2025-08-05 01:23:07 +03:00
action="Failed",
2025-08-08 05:46:36 +03:00
data=f"User not found for hash: {user_hash}"
2025-08-05 01:23:07 +03:00
)
return HttpResponse("Not found", status=404)
2025-08-08 05:46:36 +03:00
# Check if this is a JSON request for web display
if request.GET.get('format') == 'json':
return xray_subscription_json(request, user, user_hash)
2025-08-05 01:23:07 +03:00
try:
2025-08-08 05:46:36 +03:00
# Check if specific group is requested
group_filter = request.GET.get('group')
# Get subscription groups for the user
user_subscriptions = UserSubscription.objects.filter(
user=user,
active=True,
subscription_group__is_active=True
).select_related('subscription_group').prefetch_related('subscription_group__inbounds')
# Filter by specific group if requested
if group_filter:
user_subscriptions = user_subscriptions.filter(subscription_group__name=group_filter)
logger.info(f"Filtering subscription for group: {group_filter}")
2025-08-05 01:23:07 +03:00
subscription_configs = []
2025-08-08 05:46:36 +03:00
for subscription in user_subscriptions:
group = subscription.subscription_group
logger.info(f"Processing subscription group {group.name} for user {user.username}")
2025-08-05 01:23:07 +03:00
2025-08-08 05:46:36 +03:00
# Get all inbounds from this group
for inbound in group.inbounds.all():
2025-08-05 01:23:07 +03:00
try:
2025-08-08 07:39:01 +03:00
# Find all servers where this inbound is deployed
from .models_xray import ServerInbound
deployed_servers = ServerInbound.objects.filter(
inbound=inbound,
active=True
).select_related('server')
2025-08-08 05:46:36 +03:00
2025-08-08 07:39:01 +03:00
# Generate connection string for each server where inbound is deployed
for server_inbound in deployed_servers:
server = server_inbound.server
# Get server's client_hostname for XrayServerV2
server_hostname = getattr(server.get_real_instance(), 'client_hostname', None)
connection_string = generate_xray_connection_string(user, inbound, server.name, server_hostname)
if connection_string:
subscription_configs.append(connection_string)
logger.info(f"Added {inbound.protocol} config for inbound {inbound.name} on server {server.name}")
2025-08-08 05:46:36 +03:00
2025-08-05 01:23:07 +03:00
except Exception as e:
2025-08-08 05:46:36 +03:00
logger.warning(f"Failed to generate config for inbound {inbound.name}: {e}")
continue
2025-08-05 01:23:07 +03:00
if not subscription_configs:
2025-08-08 05:46:36 +03:00
group_msg = f" for group '{group_filter}'" if group_filter else ""
logger.warning(f"No Xray configurations found for user {user.username}{group_msg}")
2025-08-05 01:23:07 +03:00
AccessLog.objects.create(
2025-08-08 05:46:36 +03:00
user=user.username,
server="Xray-Subscription",
acl_link_id=user_hash,
2025-08-05 01:23:07 +03:00
action="Failed",
2025-08-08 05:46:36 +03:00
data=f"No Xray configurations available{group_msg}"
2025-08-05 01:23:07 +03:00
)
2025-08-08 05:46:36 +03:00
return HttpResponse(f"No configurations available{group_msg}", status=404)
2025-08-05 01:23:07 +03:00
# Join all configs with newlines and encode in base64 for subscription format
subscription_content = '\n'.join(subscription_configs)
2025-08-08 05:46:36 +03:00
logger.info(f"Raw subscription content for {user.username}: {len(subscription_configs)} configs")
2025-08-05 01:23:07 +03:00
subscription_b64 = base64.b64encode(subscription_content.encode('utf-8')).decode('utf-8')
logger.info(f"Base64 subscription length: {len(subscription_b64)}")
# Create access log
2025-08-08 05:46:36 +03:00
group_msg = f" for group '{group_filter}'" if group_filter else ""
2025-08-05 01:23:07 +03:00
AccessLog.objects.create(
2025-08-08 05:46:36 +03:00
user=user.username,
2025-08-05 01:23:07 +03:00
server="Xray-Subscription",
2025-08-08 05:46:36 +03:00
acl_link_id=user_hash,
2025-08-05 01:23:07 +03:00
action="Success",
2025-08-08 05:46:36 +03:00
data=f"Generated subscription with {len(subscription_configs)} configs from {user_subscriptions.count()} groups{group_msg}"
2025-08-05 01:23:07 +03:00
)
2025-08-08 05:46:36 +03:00
logger.info(f"Generated Xray subscription for {user.username} with {len(subscription_configs)} configs{group_msg}")
2025-08-05 01:23:07 +03:00
# Return with proper headers for subscription
response = HttpResponse(subscription_b64, content_type="text/plain; charset=utf-8")
2025-08-08 12:41:33 +03:00
response['Content-Disposition'] = f'attachment; filename="{user.username}"'
2025-08-05 01:23:07 +03:00
response['Cache-Control'] = 'no-cache'
2025-08-08 12:41:33 +03:00
# Add subscription-specific headers like other providers
import base64 as b64
profile_title_b64 = b64.b64encode("OutFleet VPN".encode('utf-8')).decode('utf-8')
response['profile-title'] = f'base64:{profile_title_b64}'
response['profile-update-interval'] = '24' # Update every 24 hours
response['profile-web-page-url'] = f'https://{request.get_host()}/u/{user_hash}'
response['support-url'] = f'https://{request.get_host()}/admin/'
# Add user info without limits (unlimited service)
# Set very high limits to indicate "unlimited"
import time
expire_timestamp = int(time.time()) + (365 * 24 * 60 * 60) # 1 year from now
response['subscription-userinfo'] = f'upload=0; download=0; total=1099511627776; expire={expire_timestamp}'
2025-08-05 01:23:07 +03:00
return response
except Exception as e:
2025-08-08 05:46:36 +03:00
logger.error(f"Failed to generate Xray subscription for {user.username}: {e}")
2025-08-05 01:23:07 +03:00
AccessLog.objects.create(
2025-08-08 05:46:36 +03:00
user=user.username,
2025-08-05 01:23:07 +03:00
server="Xray-Subscription",
2025-08-08 05:46:36 +03:00
acl_link_id=user_hash,
2025-08-05 01:23:07 +03:00
action="Failed",
data=f"Failed to generate subscription: {e}"
)
return HttpResponse(f"Error generating subscription: {e}", status=500)
2025-08-08 05:46:36 +03:00
def xray_subscription_json(request, user, user_hash):
"""Return Xray subscription in JSON format for web display"""
from .models_xray import UserSubscription
import logging
logger = logging.getLogger(__name__)
try:
# Get all active subscription groups for the user
user_subscriptions = UserSubscription.objects.filter(
user=user,
active=True,
subscription_group__is_active=True
).select_related('subscription_group').prefetch_related('subscription_group__inbounds')
groups_data = {}
for subscription in user_subscriptions:
group = subscription.subscription_group
group_configs = []
# Get all inbounds from this group
for inbound in group.inbounds.all():
try:
2025-08-08 07:39:01 +03:00
# Find all servers where this inbound is deployed
from .models_xray import ServerInbound
deployed_servers = ServerInbound.objects.filter(
inbound=inbound,
active=True
).select_related('server')
2025-08-08 05:46:36 +03:00
2025-08-08 07:39:01 +03:00
# Generate connection string for each server where inbound is deployed
for server_inbound in deployed_servers:
server = server_inbound.server
# Get server's client_hostname for XrayServerV2
server_hostname = getattr(server.get_real_instance(), 'client_hostname', None)
connection_string = generate_xray_connection_string(user, inbound, server.name, server_hostname)
if connection_string:
config_name = f"{server.name} {inbound.name}"
group_configs.append({
'name': config_name,
2025-08-08 05:46:36 +03:00
'protocol': inbound.protocol.upper(),
'port': inbound.port,
'network': inbound.network,
'security': inbound.security,
2025-08-08 07:39:01 +03:00
'domain': host,
2025-08-08 05:46:36 +03:00
'connection_string': connection_string
})
except Exception as e:
logger.warning(f"Failed to generate config for inbound {inbound.name}: {e}")
continue
if group_configs:
groups_data[group.name] = {
'group_name': group.name,
'description': group.description,
'configs': group_configs
}
return JsonResponse(groups_data)
except Exception as e:
logger.error(f"Failed to generate Xray JSON for {user.username}: {e}")
return JsonResponse({'error': str(e)}, status=500)
2025-08-08 07:39:01 +03:00
def generate_xray_connection_string(user, inbound, server_name=None, server_hostname=None):
2025-08-08 05:46:36 +03:00
"""Generate Xray connection string for user and inbound"""
import uuid
import base64
import json
from urllib.parse import quote
try:
# Generate user UUID based on user ID and inbound
user_uuid = str(uuid.uuid5(uuid.NAMESPACE_DNS, f"{user.username}-{inbound.name}"))
2025-08-08 10:32:14 +03:00
# Get host (use server's client_hostname if available, fallback to external host)
host = server_hostname if server_hostname else get_external_host()
2025-08-08 05:46:36 +03:00
if inbound.protocol == 'vless':
# VLESS URL format: vless://uuid@host:port?params#name
params = []
2025-08-08 12:41:33 +03:00
# Always add transport type for VLESS
params.append(f"type={inbound.network}")
2025-08-08 05:46:36 +03:00
if inbound.security != 'none':
params.append(f"security={inbound.security}")
2025-08-08 07:39:01 +03:00
if inbound.security == 'tls' and host:
params.append(f"sni={host}")
2025-08-08 05:46:36 +03:00
if inbound.network == 'ws':
params.append(f"path=/{inbound.name}")
elif inbound.network == 'grpc':
params.append(f"serviceName={inbound.name}")
param_string = '&'.join(params)
query_part = f"?{param_string}" if param_string else ""
2025-08-08 07:39:01 +03:00
# Generate config name: ServerName InboundName (e.g., "Israel VLESS-Premium")
config_name = f"{server_name} {inbound.name}" if server_name else inbound.name
connection_string = f"vless://{user_uuid}@{host}:{inbound.port}{query_part}#{quote(config_name)}"
2025-08-08 05:46:36 +03:00
elif inbound.protocol == 'vmess':
# VMess JSON format encoded in base64
2025-08-08 07:39:01 +03:00
# Generate config name: ServerName InboundName (e.g., "Israel VMESS-Premium")
config_name = f"{server_name} {inbound.name}" if server_name else inbound.name
2025-08-08 05:46:36 +03:00
vmess_config = {
"v": "2",
2025-08-08 07:39:01 +03:00
"ps": config_name,
2025-08-08 05:46:36 +03:00
"add": host,
"port": str(inbound.port),
"id": user_uuid,
"aid": "0",
"scy": "auto",
"net": inbound.network,
"type": "none",
2025-08-08 07:39:01 +03:00
"host": host if host else "",
2025-08-08 05:46:36 +03:00
"path": f"/{inbound.name}" if inbound.network == 'ws' else "",
"tls": inbound.security if inbound.security != 'none' else ""
}
vmess_json = json.dumps(vmess_config)
vmess_b64 = base64.b64encode(vmess_json.encode()).decode()
connection_string = f"vmess://{vmess_b64}"
elif inbound.protocol == 'trojan':
# Trojan URL format: trojan://password@host:port?params#name
# Use user UUID as password
params = []
2025-08-08 07:39:01 +03:00
if inbound.security != 'none' and host:
params.append(f"sni={host}")
2025-08-08 05:46:36 +03:00
if inbound.network != 'tcp':
params.append(f"type={inbound.network}")
if inbound.network == 'ws':
params.append(f"path=/{inbound.name}")
elif inbound.network == 'grpc':
params.append(f"serviceName={inbound.name}")
param_string = '&'.join(params)
query_part = f"?{param_string}" if param_string else ""
2025-08-08 07:39:01 +03:00
# Generate config name: ServerName InboundName (e.g., "Israel TROJAN-Premium")
config_name = f"{server_name} {inbound.name}" if server_name else inbound.name
connection_string = f"trojan://{user_uuid}@{host}:{inbound.port}{query_part}#{quote(config_name)}"
2025-08-08 05:46:36 +03:00
else:
# Fallback for unknown protocols
2025-08-08 07:39:01 +03:00
config_name = f"{server_name} {inbound.name}" if server_name else inbound.name
connection_string = f"{inbound.protocol}://{user_uuid}@{host}:{inbound.port}#{quote(config_name)}"
2025-08-08 05:46:36 +03:00
return connection_string
except Exception as e:
import logging
logger = logging.getLogger(__name__)
logger.error(f"Failed to generate connection string for {inbound.name}: {e}")
return None