Ground-Zerro / Phobos Public
Code Issues Pull requests Actions Releases View on GitHub ↗
11.1 KB bash
#!/bin/sh
set -e

RCI_URL="http://localhost:79/rci/"
MAX_INTERFACE_NUM=9

check_dependencies() {
    local missing=""

    for cmd in curl jq date; do
        if ! command -v "$cmd" >/dev/null 2>&1; then
            missing="$missing $cmd"
        fi
    done

    if [ -n "$missing" ]; then
        echo "ERROR: Missing required utilities:$missing" >&2
        echo "Please install them using: opkg update && opkg install$missing" >&2
        return 1
    fi

    return 0
}

CLIENT_NAME=""
UPSTREAM_HOST=""
UPSTREAM_PORT=""
SOCKS5_LOGIN=""
SOCKS5_PASSWORD=""
FALLBACK_CONFIG=""

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}

error() {
    echo "[ERROR] $*" >&2
    log "ERROR: $*"
}

usage() {
    cat <<EOF
Usage: $0 [OPTIONS]

Options:
  --client-name NAME     Client name (required)
  --upstream-host HOST   Upstream SOCKS5 host, reachable from NDM (required)
  --upstream-port PORT   Upstream SOCKS5 port (required)
  --login LOGIN          SOCKS5 auth login (required)
  --password PASSWORD    SOCKS5 auth password (required)
  --fallback-config PATH Path to the obfuscator conf shown in manual instructions
  --help                 Show this help

Example:
  $0 --client-name Pegacomp \\
     --upstream-host 127.0.0.1 \\
     --upstream-port 13255 \\
     --login abcde \\
     --password fghij

EOF
    exit 1
}

parse_args() {
    while [ $# -gt 0 ]; do
        case "$1" in
            --client-name)
                CLIENT_NAME="$2"
                shift 2
                ;;
            --upstream-host)
                UPSTREAM_HOST="$2"
                shift 2
                ;;
            --upstream-port)
                UPSTREAM_PORT="$2"
                shift 2
                ;;
            --login)
                SOCKS5_LOGIN="$2"
                shift 2
                ;;
            --password)
                SOCKS5_PASSWORD="$2"
                shift 2
                ;;
            --fallback-config)
                FALLBACK_CONFIG="$2"
                shift 2
                ;;
            --help)
                usage
                ;;
            *)
                error "Unknown option: $1"
                usage
                ;;
        esac
    done

    if [ -z "${CLIENT_NAME}" ] || [ -z "${UPSTREAM_HOST}" ] || \
       [ -z "${UPSTREAM_PORT}" ] || [ -z "${SOCKS5_LOGIN}" ] || \
       [ -z "${SOCKS5_PASSWORD}" ]; then
        error "Missing required parameters"
        usage
    fi
}

find_phobos_proxy_interface() {
    local target_desc="Phobos-${CLIENT_NAME}"

    log "Поиск существующего интерфейса Proxy для клиента: ${CLIENT_NAME}..." >&2

    local i
    for i in 0 1 2 3 4 5 6 7 8 9; do
        local interface_json=$(curl -s "${RCI_URL}show/rc/interface/Proxy${i}" 2>/dev/null)

        if [ -n "${interface_json}" ] && echo "${interface_json}" | jq -e . >/dev/null 2>&1; then
            local desc=$(echo "${interface_json}" | jq -r '.description // empty' 2>/dev/null)
            if [ "${desc}" = "${target_desc}" ]; then
                log "Найден существующий интерфейс: Proxy${i}" >&2
                echo "Proxy${i}"
                return 0
            fi
        fi
    done

    log "Существующий интерфейс Proxy не найден" >&2
    echo ""
    return 0
}

find_free_proxy_interface() {
    log "Поиск свободного интерфейса Proxy..." >&2

    local i
    for i in 0 1 2 3 4 5 6 7 8 9; do
        local interface_json=$(curl -s "${RCI_URL}show/interface/Proxy${i}" 2>/dev/null)

        if [ -z "${interface_json}" ] || ! echo "${interface_json}" | jq -e '.id' >/dev/null 2>&1; then
            log "Найден свободный интерфейс: Proxy${i}" >&2
            echo "Proxy${i}"
            return 0
        fi
    done

    error "Нет свободных интерфейсов Proxy (0-${MAX_INTERFACE_NUM})"
    return 1
}

remove_proxy_interface() {
    local interface_name="$1"

    log "Удаление существующего интерфейса: ${interface_name}..."

    if command -v ndmc >/dev/null 2>&1; then
        if ndmc -c "no interface ${interface_name}" >/dev/null 2>&1; then
            log "Интерфейс ${interface_name} успешно удален ✓"
            return 0
        else
            log "Предупреждение: не удалось удалить интерфейс через ndmc"
            return 1
        fi
    else
        log "Предупреждение: команда ndmc не найдена"
        return 1
    fi
}

configure_proxy_interface() {
    local interface_name="$1"
    local description="Phobos-${CLIENT_NAME}"

    log "Настройка интерфейса ${interface_name}..."

    local commands=$(cat <<EOF
[
  {"parse": "interface ${interface_name}"},
  {"parse": "interface ${interface_name} description ${description}"},
  {"parse": "interface ${interface_name} proxy protocol socks5"},
  {"parse": "interface ${interface_name} proxy socks5-udp"},
  {"parse": "interface ${interface_name} authentication identity ${SOCKS5_LOGIN}"},
  {"parse": "interface ${interface_name} authentication password ${SOCKS5_PASSWORD}"},
  {"parse": "interface ${interface_name} proxy upstream ${UPSTREAM_HOST} ${UPSTREAM_PORT}"},
  {"parse": "interface ${interface_name} no ip name-servers"},
  {"parse": "interface ${interface_name} no ipv6 name-servers"},
  {"parse": "interface ${interface_name} up"},
  {"parse": "interface ${interface_name} ip global 1"}
]
EOF
)

    local result=$(echo "${commands}" | curl -s -X POST \
        -H "Content-Type: application/json" \
        -d @- \
        "${RCI_URL}" 2>/dev/null)

    if echo "${result}" | jq -e '.[] | select(.status == "error")' >/dev/null 2>&1; then
        local error_msg=$(echo "${result}" | jq -r '.[] | select(.status == "error") | .message // "Unknown error"' 2>/dev/null | head -1)
        error "RCI API отклонил конфигурацию: ${error_msg}"
        return 1
    fi

    log "Интерфейс ${interface_name} создан ✓"

    return 0
}

save_configuration() {
    log "Сохранение конфигурации..."

    local result=$(curl -s -X POST \
        -H "Content-Type: application/json" \
        -d '{"system":{"configuration":{"save":{}}}}' \
        "${RCI_URL}" 2>/dev/null)

    if echo "${result}" | grep -q '"status"[[:space:]]*:[[:space:]]*"message"'; then
        log "Конфигурация сохранена ✓"
        return 0
    else
        error "Ошибка сохранения конфигурации"
        return 1
    fi
}

verify_interface_created() {
    local interface_description="Phobos-${CLIENT_NAME}"

    log "Проверка создания интерфейса Proxy..."

    local interfaces=$(curl -s "http://127.0.0.1:79/rci/show/interface" 2>/dev/null || echo "")

    if [ -z "$interfaces" ]; then
        error "Не удалось получить список интерфейсов через RCI API"
        return 1
    fi

    if ! echo "$interfaces" | jq -e . >/dev/null 2>&1; then
        error "Некорректный JSON ответ от RCI API"
        return 1
    fi

    local found=$(echo "$interfaces" | jq -r "to_entries[] | select(.value.description == \"$interface_description\") | .key" 2>/dev/null)

    if [ -n "$found" ]; then
        log "✓ Интерфейс $found (Phobos-${CLIENT_NAME}) успешно создан"
        return 0
    else
        error "Интерфейс с description '$interface_description' не найден"
        return 1
    fi
}

show_fallback_instructions() {
    cat <<EOF

╔════════════════════════════════════════════════════════════╗
║  RCI API недоступен - требуется ручная настройка          ║
╚════════════════════════════════════════════════════════════╝

Локальный SOCKS5-обфускатор запущен и настроен в: ${FALLBACK_CONFIG}

Инструкция по ручному созданию интерфейса Proxy:
1. Откройте веб-панель Keenetic (http://192.168.1.1 или http://my.keenetic.net)
2. Перейдите: Интернет → Другие подключения → Добавить подключение
3. Выберите тип: Proxy, протокол SOCKS5
4. Upstream: ${UPSTREAM_HOST}:${UPSTREAM_PORT}
5. Логин/пароль: ${SOCKS5_LOGIN} / ${SOCKS5_PASSWORD}
6. Активируйте подключение

EOF
}

main() {
    parse_args "$@"

    if ! check_dependencies; then
        exit 1
    fi

    log "=== Phobos SOCKS5 Proxy RCI Configuration ==="
    log "Клиент: ${CLIENT_NAME}"

    EXISTING_INTERFACE=$(find_phobos_proxy_interface) || true

    if [ -n "${EXISTING_INTERFACE}" ]; then
        log "Обнаружен существующий интерфейс: ${EXISTING_INTERFACE}"
        if remove_proxy_interface "${EXISTING_INTERFACE}"; then
            log "Интерфейс ${EXISTING_INTERFACE} удален, будет создан заново"
        else
            log "Не удалось удалить интерфейс ${EXISTING_INTERFACE}, попытка пересоздать"
        fi
    fi

    INTERFACE_NAME=$(find_free_proxy_interface) || true
    if [ -z "${INTERFACE_NAME}" ]; then
        show_fallback_instructions
        exit 1
    fi
    log "Создание нового интерфейса: ${INTERFACE_NAME}"

    if ! configure_proxy_interface "${INTERFACE_NAME}"; then
        error "Не удалось настроить Proxy через RCI API"
        show_fallback_instructions
        exit 1
    fi

    if ! save_configuration; then
        error "Не удалось сохранить конфигурацию"
        exit 1
    fi

    log "Настройка SOCKS5 Proxy завершена успешно! ✓"
    log "Интерфейс: ${INTERFACE_NAME}"
    log "Description: Phobos-${CLIENT_NAME}"

    log ""
    log "Ожидание применения конфигурации..."
    sleep 3

    if verify_interface_created; then
        log ""
        log "╔════════════════════════════════════════════════════════════╗"
        log "║  SOCKS5 Proxy успешно настроен!                            ║"
        log "╚════════════════════════════════════════════════════════════╝"
        log ""
        exit 0
    else
        log ""
        log "⚠ Не удалось подтвердить создание интерфейса Proxy"
        log ""
        log "Проверьте вручную в веб-панели Keenetic:"
        log "  Интернет → Другие подключения"
        log ""
        exit 1
    fi
}

main "$@"