feat: auto-add SOCKS5 proxy alongside existing xray panel

When a server has an existing xray panel (3x-ui, Marzban), paqctl now
adds a SOCKS5 inbound on a free port (10443+) instead of skipping xray
setup entirely. This gives users two connections automatically:
- Panel mapping (e.g. 14000:443) for server-to-server panel traffic
- SOCKS5 mapping (e.g. 14001:10443) for direct proxy from Windows/Mac

Updates README with Setup A (server-to-server) and Setup B (direct
client) guides in both English and Farsi.
This commit is contained in:
SamNet-dev
2026-02-06 21:24:21 -06:00
parent 60b613afc9
commit fc8102c585
2 changed files with 253 additions and 53 deletions

190
paqctl.sh
View File

@@ -1407,6 +1407,40 @@ EOF
log_success "Xray configured (SOCKS5 on 127.0.0.1:$listen_port)"
}
# Add a SOCKS5 inbound to an existing xray config (panel) without touching other inbounds
_add_xray_gfk_socks() {
local port="$1"
python3 -c "
import json, sys
port = int(sys.argv[1])
config_path = sys.argv[2]
try:
with open(config_path, 'r') as f:
cfg = json.load(f)
except:
cfg = {'inbounds': [], 'outbounds': [{'tag': 'direct', 'protocol': 'freedom', 'settings': {}}]}
cfg.setdefault('inbounds', [])
cfg['inbounds'] = [i for i in cfg['inbounds'] if i.get('tag') != 'gfk-socks']
cfg['inbounds'].append({
'tag': 'gfk-socks',
'port': port,
'listen': '127.0.0.1',
'protocol': 'socks',
'settings': {'auth': 'noauth', 'udp': True},
'sniffing': {'enabled': True, 'destOverride': ['http', 'tls']}
})
if not any(o.get('protocol') == 'freedom' for o in cfg.get('outbounds', [])):
cfg.setdefault('outbounds', []).append({'tag': 'direct', 'protocol': 'freedom', 'settings': {}})
with open(config_path, 'w') as f:
json.dump(cfg, f, indent=2)
" "$port" "$XRAY_CONFIG_FILE" 2>/dev/null
if [ $? -ne 0 ]; then
log_error "Failed to add SOCKS5 inbound to existing Xray config"
return 1
fi
log_success "Added GFK SOCKS5 inbound on 127.0.0.1:$port"
}
start_xray() {
log_info "Starting Xray service..."
@@ -1456,22 +1490,18 @@ stop_xray() {
}
setup_xray_for_gfk() {
# Get the first target port from GFK_PORT_MAPPINGS (e.g., "14000:443" -> 443)
local target_port
target_port=$(echo "${GFK_PORT_MAPPINGS:-14000:443}" | cut -d: -f2 | cut -d, -f1)
# If xray is already running (e.g. user has a panel), skip SOCKS5 setup.
# GFK is a raw TCP forwarder — traffic goes to whatever is on the target port.
# The user's panel/services handle the actual proxying.
if pgrep -x xray &>/dev/null; then
XRAY_PANEL_DETECTED=true
log_info "Existing Xray detected — skipping SOCKS5 setup (panel will handle traffic)"
log_info "Existing Xray detected — adding SOCKS5 alongside panel..."
# Clean up any leftover standalone GFK xray from prior installs
pkill -f "xray run -c.*gfk-socks.json" 2>/dev/null || true
rm -f "${XRAY_CONFIG_DIR}/gfk-socks.json" 2>/dev/null
# Check all target ports from mappings
# Check all existing target ports from mappings
local mapping pairs
IFS=',' read -ra pairs <<< "${GFK_PORT_MAPPINGS:-14000:443}"
for mapping in "${pairs[@]}"; do
@@ -1480,17 +1510,54 @@ setup_xray_for_gfk() {
if ss -tln 2>/dev/null | grep -q ":${tp} "; then
log_success "Port $tp is listening — GFK will forward VIO port $vio_port to this port"
else
log_warn "Port $tp is NOT listening on this server — make sure your xray panel inbound is configured on port $tp"
log_warn "Port $tp is NOT listening — make sure your panel inbound is on port $tp"
fi
done
# Find free port for SOCKS5 (starting at 10443)
local socks_port=10443
while ss -tln 2>/dev/null | grep -q ":${socks_port} "; do
socks_port=$((socks_port + 1))
if [ "$socks_port" -gt 65000 ]; then
log_warn "Could not find free port for SOCKS5 — panel-only mode"
echo ""
local first_vio
first_vio=$(echo "${GFK_PORT_MAPPINGS:-14000:443}" | cut -d: -f1 | cut -d, -f1)
log_warn "For panel-to-panel: configure Iran panel outbound to 127.0.0.1:${first_vio}"
return 0
fi
done
# Add SOCKS5 inbound to existing xray config
_add_xray_gfk_socks "$socks_port" || {
log_warn "Could not add SOCKS5 to panel config — panel-only mode"
return 0
}
# Restart xray to load new config
systemctl restart xray 2>/dev/null || pkill -SIGHUP xray 2>/dev/null || true
sleep 2
# Find next VIO port (highest existing + 1) and append SOCKS5 mapping
local max_vio=0
for mapping in "${pairs[@]}"; do
local v="${mapping%%:*}"
[ "$v" -gt "$max_vio" ] && max_vio="$v"
done
local socks_vio=$((max_vio + 1))
GFK_PORT_MAPPINGS="${GFK_PORT_MAPPINGS},${socks_vio}:${socks_port}"
GFK_SOCKS_PORT="$socks_port"
GFK_SOCKS_VIO_PORT="$socks_vio"
log_success "SOCKS5 proxy added on port $socks_port (VIO port $socks_vio)"
echo ""
log_info "Port mappings updated: ${GFK_PORT_MAPPINGS}"
log_warn "Use these SAME mappings on the client side"
echo ""
log_warn "ACTION REQUIRED: On your Iran server, configure the panel outbound:"
log_warn " → Set address to: 127.0.0.1"
local first_vio
first_vio=$(echo "${GFK_PORT_MAPPINGS:-14000:443}" | cut -d: -f1 | cut -d, -f1)
log_warn " → Set port to: ${first_vio} (VIO port, NOT the original server port)"
log_warn " See README for detailed panel configuration guide"
log_warn "For panel-to-panel: configure Iran panel outbound to 127.0.0.1:${first_vio}"
log_warn "For direct SOCKS5: use 127.0.0.1:${socks_vio} as your proxy on client"
return 0
fi
@@ -5713,6 +5780,36 @@ EOF
log_success "Xray configured (SOCKS5 on 127.0.0.1:$listen_port)"
}
_add_xray_gfk_socks() {
local port="$1"
python3 -c "
import json, sys
port = int(sys.argv[1])
config_path = sys.argv[2]
try:
with open(config_path, 'r') as f:
cfg = json.load(f)
except:
cfg = {'inbounds': [], 'outbounds': [{'tag': 'direct', 'protocol': 'freedom', 'settings': {}}]}
cfg.setdefault('inbounds', [])
cfg['inbounds'] = [i for i in cfg['inbounds'] if i.get('tag') != 'gfk-socks']
cfg['inbounds'].append({
'tag': 'gfk-socks', 'port': port, 'listen': '127.0.0.1', 'protocol': 'socks',
'settings': {'auth': 'noauth', 'udp': True},
'sniffing': {'enabled': True, 'destOverride': ['http', 'tls']}
})
if not any(o.get('protocol') == 'freedom' for o in cfg.get('outbounds', [])):
cfg.setdefault('outbounds', []).append({'tag': 'direct', 'protocol': 'freedom', 'settings': {}})
with open(config_path, 'w') as f:
json.dump(cfg, f, indent=2)
" "$port" "$XRAY_CONFIG_FILE" 2>/dev/null
if [ $? -ne 0 ]; then
log_error "Failed to add SOCKS5 inbound to existing Xray config"
return 1
fi
log_success "Added GFK SOCKS5 inbound on 127.0.0.1:$port"
}
start_xray() {
log_info "Starting Xray service..."
if command -v systemctl &>/dev/null && [ -d /run/systemd/system ]; then
@@ -5751,12 +5848,16 @@ start_xray() {
setup_xray_for_gfk() {
local target_port
target_port=$(echo "${GFK_PORT_MAPPINGS:-14000:443}" | cut -d: -f2 | cut -d, -f1)
if pgrep -x xray &>/dev/null; then
XRAY_PANEL_DETECTED=true
log_info "Existing Xray detected — skipping SOCKS5 setup (panel will handle traffic)"
log_info "Existing Xray detected — adding SOCKS5 alongside panel..."
# Clean up any leftover standalone GFK xray from prior installs
pkill -f "xray run -c.*gfk-socks.json" 2>/dev/null || true
rm -f "${XRAY_CONFIG_DIR}/gfk-socks.json" 2>/dev/null
# Check all target ports from mappings
# Check all existing target ports from mappings
local mapping pairs
IFS=',' read -ra pairs <<< "${GFK_PORT_MAPPINGS:-14000:443}"
for mapping in "${pairs[@]}"; do
@@ -5765,18 +5866,57 @@ setup_xray_for_gfk() {
if ss -tln 2>/dev/null | grep -q ":${tp} "; then
log_success "Port $tp is listening — GFK will forward VIO port $vio_port to this port"
else
log_warn "Port $tp is NOT listening on this server — make sure your xray panel inbound is configured on port $tp"
log_warn "Port $tp is NOT listening — make sure your panel inbound is on port $tp"
fi
done
# Find free port for SOCKS5 (starting at 10443)
local socks_port=10443
while ss -tln 2>/dev/null | grep -q ":${socks_port} "; do
socks_port=$((socks_port + 1))
if [ "$socks_port" -gt 65000 ]; then
log_warn "Could not find free port for SOCKS5 — panel-only mode"
echo ""
local first_vio
first_vio=$(echo "${GFK_PORT_MAPPINGS:-14000:443}" | cut -d: -f1 | cut -d, -f1)
log_warn "For panel-to-panel: configure Iran panel outbound to 127.0.0.1:${first_vio}"
return 0
fi
done
# Add SOCKS5 inbound to existing xray config
_add_xray_gfk_socks "$socks_port" || {
log_warn "Could not add SOCKS5 to panel config — panel-only mode"
return 0
}
# Restart xray to load new config
systemctl restart xray 2>/dev/null || pkill -SIGHUP xray 2>/dev/null || true
sleep 2
# Find next VIO port (highest existing + 1) and append SOCKS5 mapping
local max_vio=0
for mapping in "${pairs[@]}"; do
local v="${mapping%%:*}"
[ "$v" -gt "$max_vio" ] && max_vio="$v"
done
local socks_vio=$((max_vio + 1))
GFK_PORT_MAPPINGS="${GFK_PORT_MAPPINGS},${socks_vio}:${socks_port}"
GFK_SOCKS_PORT="$socks_port"
GFK_SOCKS_VIO_PORT="$socks_vio"
log_success "SOCKS5 proxy added on port $socks_port (VIO port $socks_vio)"
echo ""
log_info "Port mappings updated: ${GFK_PORT_MAPPINGS}"
log_warn "Use these SAME mappings on the client side"
echo ""
log_warn "ACTION REQUIRED: On your Iran server, configure the panel outbound:"
log_warn " → Set address to: 127.0.0.1"
local first_vio
first_vio=$(echo "${GFK_PORT_MAPPINGS:-14000:443}" | cut -d: -f1 | cut -d, -f1)
log_warn " → Set port to: ${first_vio} (VIO port, NOT the original server port)"
log_warn " See README for detailed panel configuration guide"
log_warn "For panel-to-panel: configure Iran panel outbound to 127.0.0.1:${first_vio}"
log_warn "For direct SOCKS5: use 127.0.0.1:${socks_vio} as your proxy on client"
return 0
fi
install_xray || return 1
configure_xray_socks "$target_port" || return 1
start_xray || return 1
@@ -5816,7 +5956,7 @@ _install_gfk_components() {
# Generate TLS certificates for QUIC
generate_gfk_certs || return 1
# Setup Xray (server only — skipped if panel detected)
# Setup Xray (server only — adds SOCKS5 alongside panel if detected)
if [ "$ROLE" = "server" ]; then
setup_xray_for_gfk || return 1
fi
@@ -6772,8 +6912,12 @@ main() {
download_gfk || { log_error "Failed to download GFK"; exit 1; }
generate_gfk_certs || { log_error "Failed to generate certificates"; exit 1; }
if [ "$ROLE" = "server" ]; then
# Install Xray to provide SOCKS5 proxy on the target port (skipped if panel detected)
# Install Xray SOCKS5 proxy (adds alongside panel if detected)
setup_xray_for_gfk || { log_error "Failed to setup Xray"; exit 1; }
# Regenerate config if mappings changed (panel detected → SOCKS5 added)
if [ "${XRAY_PANEL_DETECTED:-false}" = "true" ]; then
generate_gfk_config || { log_error "Failed to regenerate GFK config"; exit 1; }
fi
elif [ "$ROLE" = "client" ]; then
install_microsocks || { log_error "Failed to install microsocks"; exit 1; }
create_gfk_client_wrapper || { log_error "Failed to create client wrapper"; exit 1; }
@@ -6868,11 +7012,13 @@ main() {
echo -e " QUIC port: ${BOLD}${GFK_QUIC_PORT}${NC}"
if [ "${XRAY_PANEL_DETECTED:-false}" = "true" ]; then
echo -e " Xray: ${BOLD}Existing panel detected (forwarding to port ${_xray_port})${NC}"
echo -e " SOCKS5: ${BOLD}127.0.0.1:${GFK_SOCKS_PORT:-N/A} (auto-added, VIO port ${GFK_SOCKS_VIO_PORT:-N/A})${NC}"
echo ""
echo -e " ${GREEN}✓ GFK will forward traffic to your existing panel${NC}"
echo -e " ${GREEN}✓ GFK forwards to panel + SOCKS5 proxy added${NC}"
local _first_vio
_first_vio=$(echo "${GFK_PORT_MAPPINGS:-14000:443}" | cut -d: -f1 | cut -d, -f1)
echo -e " ${YELLOW}! ACTION: Configure Iran panel outbound → 127.0.0.1:${_first_vio}${NC}"
echo -e " ${YELLOW}! Panel users: configure Iran outbound → 127.0.0.1:${_first_vio}${NC}"
echo -e " ${YELLOW}! Direct SOCKS5: use 127.0.0.1:${GFK_SOCKS_VIO_PORT:-} on client${NC}"
else
echo -e " Xray: ${BOLD}127.0.0.1:${_xray_port} (SOCKS5)${NC}"
echo ""