#!/bin/bash GREEN='\033[92m' YELLOW='\033[93m' RED='\033[91m' BLUE='\033[94m' CYAN='\033[96m' RESET='\033[0m' BOLD='\033[1m' print_status() { local message="$1" local status="${2:-info}" local prefix="" case "$status" in success) prefix="${GREEN}[OK]" ;; error) prefix="${RED}[ERR]" ;; warning) prefix="${YELLOW}[WARN]" ;; info) prefix="${BLUE}[INFO]" ;; debug) prefix="${CYAN}[DBG]" ;; esac echo -e "${prefix} ${message}${RESET}" } check_dependencies() { local missing=() for cmd in jq sqlite3; do if ! command -v "$cmd" >/dev/null 2>&1; then missing+=("$cmd") fi done if [[ ${#missing[@]} -gt 0 ]]; then print_status "Installing missing dependencies: ${missing[*]}" "info" if command -v apt-get >/dev/null 2>&1; then apt-get update -qq && apt-get install -y -qq "${missing[@]}" >/dev/null 2>&1 elif command -v yum >/dev/null 2>&1; then yum install -y -q "${missing[@]/sqlite3/sqlite}" >/dev/null 2>&1 elif command -v dnf >/dev/null 2>&1; then dnf install -y -q "${missing[@]/sqlite3/sqlite}" >/dev/null 2>&1 elif command -v apk >/dev/null 2>&1; then apk add --quiet "${missing[@]/sqlite3/sqlite}" >/dev/null 2>&1 fi for cmd in "${missing[@]}"; do if ! command -v "$cmd" >/dev/null 2>&1; then print_status "Failed to install $cmd" "error" exit 1 fi done print_status "Dependencies installed successfully" "success" fi } parse_wireguard_conf() { local config_path="$1" local section="" if [[ ! -f "$config_path" ]]; then print_status "File $config_path not found" "error" exit 1 fi INTERFACE_PRIVATEKEY="" INTERFACE_ADDRESS="" INTERFACE_MTU="1420" PEER_PUBLICKEY="" PEER_ENDPOINT="" PEER_ALLOWEDIPS="" PEER_PRESHAREDKEY="" PEER_KEEPALIVE="0" while IFS= read -r line || [[ -n "$line" ]]; do line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') [[ -z "$line" || "$line" == \#* ]] && continue if [[ "$line" == "["*"]" ]]; then section=$(echo "$line" | tr -d '[]' | tr '[:upper:]' '[:lower:]') continue fi if [[ "$line" == *"="* ]]; then local key=$(echo "$line" | cut -d'=' -f1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') local value=$(echo "$line" | cut -d'=' -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') if [[ "$section" == "interface" ]]; then case "$key" in PrivateKey) INTERFACE_PRIVATEKEY="$value" ;; Address) INTERFACE_ADDRESS="$value" ;; MTU) INTERFACE_MTU="$value" ;; esac elif [[ "$section" == "peer" ]]; then case "$key" in PublicKey) PEER_PUBLICKEY="$value" ;; Endpoint) PEER_ENDPOINT="$value" ;; AllowedIPs) PEER_ALLOWEDIPS="$value" ;; PresharedKey|PreSharedKey) PEER_PRESHAREDKEY="$value" ;; PersistentKeepalive) PEER_KEEPALIVE="$value" ;; esac fi fi done < "$config_path" } extract_endpoint() { local endpoint="$1" if [[ "$endpoint" == "["*"]:"* ]]; then SERVER_ADDRESS=$(echo "$endpoint" | sed 's/^\[\([^]]*\)\]:.*/\1/') SERVER_PORT=$(echo "$endpoint" | sed 's/.*\]:\([0-9]*\).*/\1/') else SERVER_ADDRESS=$(echo "$endpoint" | rev | cut -d':' -f2- | rev) SERVER_PORT=$(echo "$endpoint" | rev | cut -d':' -f1 | rev) fi [[ -z "$SERVER_PORT" ]] && SERVER_PORT="51820" } build_local_addresses() { local addresses="$1" local result="[]" IFS=',' read -ra ADDR_ARRAY <<< "$addresses" for addr in "${ADDR_ARRAY[@]}"; do addr=$(echo "$addr" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') [[ -z "$addr" ]] && continue local ip_part="$addr" if [[ "$addr" == *"/"* ]]; then ip_part=$(echo "$addr" | cut -d'/' -f1) fi if [[ "$ip_part" == *":"* ]]; then result=$(echo "$result" | jq --arg ip "${ip_part}/128" '. + [$ip]') else result=$(echo "$result" | jq --arg ip "${ip_part}/32" '. + [$ip]') fi done echo "$result" } build_allowed_ips() { local allowed="$1" local result="[]" if [[ -z "$allowed" ]]; then echo '["0.0.0.0/0", "::/0"]' return fi IFS=',' read -ra IP_ARRAY <<< "$allowed" for ip in "${IP_ARRAY[@]}"; do ip=$(echo "$ip" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') [[ -z "$ip" ]] && continue result=$(echo "$result" | jq --arg ip "$ip" '. + [$ip]') done if [[ "$result" == "[]" ]]; then echo '["0.0.0.0/0", "::/0"]' else echo "$result" fi } convert_to_xray_outbound() { local tag="${1:-Phobos}" extract_endpoint "$PEER_ENDPOINT" local local_addresses=$(build_local_addresses "$INTERFACE_ADDRESS") local allowed_ips=$(build_allowed_ips "$PEER_ALLOWEDIPS") local peer_config peer_config=$(jq -n \ --arg pubkey "$PEER_PUBLICKEY" \ --argjson allowed "$allowed_ips" \ --arg endpoint "${SERVER_ADDRESS}:${SERVER_PORT}" \ '{ "publicKey": $pubkey, "allowedIPs": $allowed, "endpoint": $endpoint }') if [[ -n "$PEER_KEEPALIVE" && "$PEER_KEEPALIVE" != "0" ]]; then peer_config=$(echo "$peer_config" | jq --argjson ka "$PEER_KEEPALIVE" '. + {"keepAlive": $ka}') fi if [[ -n "$PEER_PRESHAREDKEY" ]]; then peer_config=$(echo "$peer_config" | jq --arg psk "$PEER_PRESHAREDKEY" '. + {"preSharedKey": $psk}') fi local mtu="${INTERFACE_MTU:-1420}" OUTBOUND_JSON=$(jq -n \ --arg tag "$tag" \ --argjson mtu "$mtu" \ --arg secret "$INTERFACE_PRIVATEKEY" \ --argjson addresses "$local_addresses" \ --argjson peer "$peer_config" \ '{ "protocol": "wireguard", "settings": { "mtu": $mtu, "secretKey": $secret, "address": $addresses, "workers": 2, "peers": [$peer], "noKernelTun": true }, "tag": $tag }') } get_default_xray_config() { cat <<'XRAY_CONFIG' { "api": { "services": ["HandlerService", "StatsService", "LoggerService"], "tag": "api" }, "inbounds": [ { "listen": "127.0.0.1", "port": 62789, "protocol": "dokodemo-door", "settings": {"address": "127.0.0.1"}, "tag": "api" } ], "outbounds": [ {"protocol": "freedom", "settings": {}, "tag": "direct"}, {"protocol": "blackhole", "settings": {}, "tag": "block"} ], "policy": { "levels": {"0": {"statsUserDownlink": true, "statsUserUplink": true}}, "system": { "statsInboundDownlink": true, "statsInboundUplink": true, "statsOutboundDownlink": true, "statsOutboundUplink": true } }, "routing": { "domainStrategy": "AsIs", "rules": [ {"inboundTag": ["api"], "outboundTag": "api", "type": "field"}, {"ip": ["geoip:private"], "outboundTag": "block", "type": "field"} ] }, "stats": {} } XRAY_CONFIG } find_database() { local paths=( "/etc/x-ui/x-ui.db" "/usr/local/x-ui/x-ui.db" "/opt/x-ui/x-ui.db" "./x-ui.db" ) for path in "${paths[@]}"; do if [[ -f "$path" ]]; then print_status "Database found: $path" "success" >&2 echo "$path" return 0 fi done return 1 } diagnose_database() { local db_path="$1" local tables=$(sqlite3 "$db_path" "SELECT name FROM sqlite_master WHERE type='table';" 2>/dev/null) print_status "Found tables: $tables" "debug" >&2 if echo "$tables" | grep -q "settings"; then local keys=$(sqlite3 "$db_path" "SELECT key FROM settings;" 2>/dev/null) print_status "Keys in settings: $keys" "debug" >&2 for key in xrayTemplateConfig xrayConfig xray_config config; do local result=$(sqlite3 "$db_path" "SELECT value FROM settings WHERE key = '$key';" 2>/dev/null) if [[ -n "$result" ]]; then print_status "Found config key: $key" "success" >&2 echo "$key" return 0 fi done fi return 1 } write_setting_to_db() { local db_path="$1" local config_key="$2" local config_value="$3" local tmp tmp=$(mktemp) printf '%s' "$config_value" > "$tmp" sqlite3 "$db_path" "INSERT OR REPLACE INTO settings (key, value) VALUES ('$config_key', readfile('$tmp'));" local ret=$? rm -f "$tmp" return $ret } import_via_database() { local db_path="$1" print_status "Importing to database..." "info" if [[ ! -f "$db_path" ]]; then print_status "Database not found: $db_path" "error" return 1 fi local config_key=$(diagnose_database "$db_path") if [[ -z "$config_key" ]]; then print_status "Xray config not found in database, creating new..." "warning" local base_config=$(get_default_xray_config) local new_config=$(echo "$base_config" | jq --argjson ob "$OUTBOUND_JSON" '.outbounds = [$ob] + .outbounds') if write_setting_to_db "$db_path" "xrayTemplateConfig" "$new_config"; then print_status "Base Xray config created with outbound" "success" return 0 else print_status "Failed to create config" "error" return 1 fi fi local current_config=$(sqlite3 "$db_path" "SELECT value FROM settings WHERE key = '$config_key';" 2>/dev/null) if [[ -z "$current_config" ]]; then print_status "Xray config not found" "error" return 1 fi if ! echo "$current_config" | jq . >/dev/null 2>&1; then print_status "Invalid JSON in config" "error" return 1 fi local tag=$(echo "$OUTBOUND_JSON" | jq -r '.tag') local existing_tag=$(echo "$current_config" | jq -r --arg t "$tag" '.outbounds[]? | select(.tag == $t) | .tag' 2>/dev/null) if [[ -n "$existing_tag" ]]; then print_status "Outbound '$tag' exists, replacing..." "warning" current_config=$(echo "$current_config" | jq --arg t "$tag" '.outbounds = [.outbounds[] | select(.tag != $t)]') fi local new_config=$(echo "$current_config" | jq --argjson ob "$OUTBOUND_JSON" '.outbounds = [$ob] + .outbounds') if write_setting_to_db "$db_path" "$config_key" "$new_config"; then print_status "Outbound added to database" "success" return 0 else print_status "Failed to update database" "error" return 1 fi } restart_xui_service() { print_status "Restarting x-ui service..." "info" if systemctl restart x-ui 2>/dev/null; then sleep 1 if systemctl is-active --quiet x-ui; then print_status "x-ui service restarted and active" "success" return 0 fi fi for cmd in "service x-ui restart" "/etc/init.d/x-ui restart" "x-ui restart"; do if $cmd 2>/dev/null; then print_status "Service restarted via: $cmd" "success" return 0 fi done print_status "Failed to restart service automatically" "error" print_status "Restart manually: systemctl restart x-ui" "info" return 1 } main() { echo -e "\n${BOLD}=== WireGuard to 3x-ui Auto-Import Tool ===${RESET}\n" check_dependencies local config_path="$1" if [[ -z "$config_path" ]]; then echo -n "Path to wireguard.conf: " read config_path fi print_status "Parsing $config_path..." "info" parse_wireguard_conf "$config_path" print_status "Converting to Xray format..." "info" convert_to_xray_outbound "Phobos" echo -e "\n${BOLD}Outbound configuration:${RESET}" echo "$OUTBOUND_JSON" | jq . echo local db_path="/etc/x-ui/x-ui.db" if [[ ! -f "$db_path" ]]; then print_status "Database not found at: $db_path" "warning" db_path=$(find_database) if [[ -z "$db_path" ]]; then print_status "Database not found in standard locations" "error" print_status "Make sure 3x-ui is installed" "info" exit 1 fi fi systemctl stop x-ui 2>/dev/null if ! import_via_database "$db_path"; then systemctl start x-ui 2>/dev/null print_status "Database import failed" "error" exit 1 fi local output_path="${config_path%.*}_3xui_outbound.json" echo "$OUTBOUND_JSON" | jq . > "$output_path" 2>/dev/null if [[ $? -eq 0 ]]; then print_status "JSON saved to: $output_path" "success" fi echo local restart_ok=0 restart_xui_service && restart_ok=1 echo print_status "Import completed successfully!" "success" echo print_status "${BOLD}Info:${RESET}" "info" print_status " Outbound 'Phobos' added to config" "info" if [[ $restart_ok -eq 1 ]]; then print_status " x-ui service restarted" "info" else print_status " x-ui service needs manual restart" "warning" fi print_status " JSON config saved to $output_path" "info" echo print_status "${BOLD}Verification:${RESET}" "info" print_status " Open 3x-ui web panel" "info" print_status " Go to Outbounds section" "info" print_status " Verify 'Phobos' outbound is present" "info" echo } main "$@"