- Созданы директории: docker/, scripts/, config/ - Перемещены файлы Docker (Dockerfile, entrypoint.sh) в docker/ - Перемещены утилитарные скрипты в scripts/ - Шаблон конфигурации перенесен в config/ - Веб-сервер перемещен в web/ и переименован в server.py - Обновлены пути в docker-compose.yml, Dockerfile и entrypoint.sh
150 lines
4.0 KiB
Bash
Executable File
150 lines
4.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# Usage: ./gen-client-from-url.sh "vless://uuid@host:443?type=tcp&security=reality&pbk=PUBLIC_KEY&fp=random&sni=yahoo.com&sid=SHORTID&spx=%2F&flow=xtls-rprx-vision#tag" [output.json]
|
|
# If output not set, defaults to client.json
|
|
|
|
URL_INPUT=${1:-}
|
|
OUT_FILE=${2:-client.json}
|
|
TEMPLATE_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
TEMPLATE_FILE="$TEMPLATE_DIR/client.template.json"
|
|
|
|
if [[ -z "$URL_INPUT" ]]; then
|
|
echo "Error: provide VLESS reality URL or Subscription URL" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -f "$TEMPLATE_FILE" ]]; then
|
|
echo "Template not found: $TEMPLATE_FILE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Detect if input is a subscription link (HTTP/HTTPS)
|
|
if [[ "$URL_INPUT" =~ ^http ]]; then
|
|
echo "Detecting subscription link..."
|
|
|
|
# Build URL with client parameter for APIs that require it
|
|
SUB_URL="$URL_INPUT"
|
|
|
|
# Try fetching as-is first
|
|
SUB_CONTENT=$(curl -sSL "$SUB_URL")
|
|
|
|
# If empty, try adding client parameter (some APIs require this)
|
|
if [[ -z "$SUB_CONTENT" ]]; then
|
|
echo "Empty response, trying with client=v2rayng parameter..."
|
|
if [[ "$SUB_URL" == *"?"* ]]; then
|
|
SUB_URL="${URL_INPUT}&client=v2rayng"
|
|
else
|
|
SUB_URL="${URL_INPUT}?client=v2rayng"
|
|
fi
|
|
SUB_CONTENT=$(curl -sSL "$SUB_URL")
|
|
fi
|
|
|
|
if [[ -z "$SUB_CONTENT" ]]; then
|
|
echo "Error: Failed to download subscription from $SUB_URL" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Check if base64 encoded (simple check: no spaces, looks like b64)
|
|
# Trying to decode. If fails, assume it's plain text lists
|
|
if DECODED=$(echo "$SUB_CONTENT" | base64 -d 2>/dev/null); then
|
|
echo "Decoded base64 subscription."
|
|
RAW_CONFIGS="$DECODED"
|
|
else
|
|
echo "Using plain text subscription."
|
|
RAW_CONFIGS="$SUB_CONTENT"
|
|
fi
|
|
|
|
# Find first vless reality link (vless://... + security=reality or just vless://)
|
|
# We try to find one that explicitly has reality, if not, pick ANY vless
|
|
TARGET_URL=$(echo "$RAW_CONFIGS" | grep -o 'vless://[^[:space:]]*' | grep 'security=reality' | head -n 1)
|
|
|
|
if [[ -z "$TARGET_URL" ]]; then
|
|
echo "No VLESS Reality link found, trying any VLESS..."
|
|
TARGET_URL=$(echo "$RAW_CONFIGS" | grep -o 'vless://[^[:space:]]*' | head -n 1)
|
|
fi
|
|
|
|
if [[ -z "$TARGET_URL" ]]; then
|
|
echo "Error: No VLESS URL found in subscription." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Selected URL from subscription: ${TARGET_URL:0:30}..."
|
|
URL_INPUT="$TARGET_URL"
|
|
fi
|
|
|
|
# Strip scheme
|
|
URL_NOSCHEME=${URL_INPUT#vless://}
|
|
|
|
UUID_HOST_PORT=${URL_NOSCHEME%%\?*}
|
|
QUERY_AND_TAG=${URL_NOSCHEME#*?}
|
|
QUERY=${QUERY_AND_TAG%%#*}
|
|
TAG_RAW=${URL_INPUT#*#}
|
|
TAG=${TAG_RAW:-reality}
|
|
|
|
UUID=${UUID_HOST_PORT%%@*}
|
|
HOST_PORT=${UUID_HOST_PORT#*@}
|
|
HOST=${HOST_PORT%%:*}
|
|
PORT=${HOST_PORT##*:}
|
|
|
|
# Parse query params (portable, no associative arrays)
|
|
PBK=""; FINGERPRINT="chrome"; SNI=""; SHORT_ID=""; SPX=""; FLOW=""
|
|
OLD_IFS=$IFS
|
|
IFS='&'
|
|
set +u
|
|
for kv in $QUERY; do
|
|
key=${kv%%=*}
|
|
val=${kv#*=}
|
|
case "$key" in
|
|
pbk) PBK=$val ;;
|
|
fp) FINGERPRINT=$val ;;
|
|
sni) SNI=$val ;;
|
|
sid) SHORT_ID=$val ;;
|
|
spx) SPX=$val ;;
|
|
flow) FLOW=$val ;;
|
|
esac
|
|
done
|
|
set -u
|
|
IFS=$OLD_IFS
|
|
SNI=${SNI:-$HOST}
|
|
# SPX currently not used
|
|
|
|
if [[ -z "$UUID" || -z "$HOST" || -z "$PORT" || -z "$PBK" || -z "$SHORT_ID" ]]; then
|
|
echo "Missing required fields (uuid/host/port/pbk/sid)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
TMP=$(mktemp)
|
|
cp "$TEMPLATE_FILE" "$TMP"
|
|
|
|
# Perform replacements safely using jq
|
|
# Replace simple placeholders
|
|
jq \
|
|
--arg uuid "$UUID" \
|
|
--arg server "$HOST" \
|
|
--argjson port "$PORT" \
|
|
--arg tag "$TAG" \
|
|
--arg sni "$SNI" \
|
|
--arg fp "$FINGERPRINT" \
|
|
--arg pk "$PBK" \
|
|
--arg sid "$SHORT_ID" \
|
|
--arg flow "$FLOW" '
|
|
(.outbounds[] | select(.type=="vless")) as $v | (
|
|
.outbounds |= map(if .type=="vless" then (
|
|
.uuid=$uuid
|
|
| .server=$server
|
|
| .server_port=$port
|
|
| .tag=$tag
|
|
| .tls.server_name=$sni
|
|
| .tls.utls.fingerprint=$fp
|
|
| .tls.reality.public_key=$pk
|
|
| .tls.reality.short_id=$sid
|
|
| .flow=$flow
|
|
) else . end)
|
|
| .route.final=$tag
|
|
)' "$TMP" > "$OUT_FILE"
|
|
|
|
rm "$TMP"
|
|
|
|
echo "Generated $OUT_FILE from URL (tag=$TAG)"
|