diff --git a/README.md b/README.md index a5487ea..89026f5 100644 --- a/README.md +++ b/README.md @@ -10,17 +10,23 @@ curl -fsSL https://git.dokops.ru/dokril/vpn-proxy/raw/branch/master/scripts/install-macos-client.sh | bash ``` -После запуска: +После запуска по умолчанию: - UI: `http://127.0.0.1:3456` - HTTP/SOCKS proxy: `127.0.0.1:8080` по умолчанию; в UI можно выбрать порт из Docker-диапазона `8080–8090` -Установщик интерактивно спросит proxy-порт. Для неинтерактивного запуска можно задать его заранее; тогда вопрос не появится: +Установщик интерактивно спросит proxy-порт. Если стандартный UI-порт `3456` занят другим контейнером, установщик попросит выбрать свободный UI-порт. Для неинтерактивного запуска можно задать порты заранее; тогда вопросы не появятся: ```bash curl -fsSL https://git.dokops.ru/dokril/vpn-proxy/raw/branch/master/scripts/install-macos-client.sh | VPN_PROXY_CLIENT_PORT=18080 bash ``` +Если старый gateway/client уже занимает `3456` или выбранный proxy-порт, можно не трогать старый контейнер и поставить новый клиент на другие порты: + +```bash +curl -fsSL https://git.dokops.ru/dokril/vpn-proxy/raw/branch/master/scripts/install-macos-client.sh | VPN_PROXY_CLIENT_UI_PORT=3457 VPN_PROXY_CLIENT_PORT=18080 bash +``` + После запуска скрипт проверяет, что UI реально ответил на `/api/state`. Если контейнер сразу упал или порт занят, он покажет `docker compose ps` и последние логи вместо ложного сообщения о готовности. В Mac UI есть **Домашний режим**. Когда он включён, приложения по-прежнему используют выбранный локальный proxy-порт, но весь proxy-трафик идёт напрямую без VPN. @@ -351,6 +357,8 @@ UI доступен на `http://:3456`. | ------------------- | -------------------- | -------------------------------------- | | `APP_MODE` | `gateway` | `gateway` или `client`; compose клиента задаёт `client` автоматически | | `CLIENT_UI_PORT` | `3456` | Host-порт UI для `docker-compose.client.yml` | +| `VPN_PROXY_CLIENT_UI_PORT` | unset | UI-порт для macOS installer; записывается в `CLIENT_UI_PORT` | +| `VPN_PROXY_CLIENT_PORT` | unset | Proxy-порт для macOS installer; записывает `CLIENT_PROXY_PORT_START/END` | | `CLIENT_PROXY_PORT_START` | `8080` | Первый host/container proxy-порт для `docker-compose.client.yml` | | `CLIENT_PROXY_PORT_END` | `8090` | Последний host/container proxy-порт для `docker-compose.client.yml` | | `PORT` | `3456` | Порт веб-интерфейса | diff --git a/docker-compose.client.yml b/docker-compose.client.yml index a6f9c10..71720b8 100644 --- a/docker-compose.client.yml +++ b/docker-compose.client.yml @@ -19,6 +19,14 @@ services: ROUTING_RU_DIRECT: ${ROUTING_RU_DIRECT:-true} RULE_SET_DOWNLOAD_DETOUR: ${RULE_SET_DOWNLOAD_DETOUR:-vpn} LOG_LEVEL: ${LOG_LEVEL:-info} + HTTP_PROXY: "" + HTTPS_PROXY: "" + ALL_PROXY: "" + http_proxy: "" + https_proxy: "" + all_proxy: "" + NO_PROXY: "localhost,127.0.0.1,host.docker.internal" + no_proxy: "localhost,127.0.0.1,host.docker.internal" ports: - "127.0.0.1:${CLIENT_UI_PORT:-3456}:${PORT:-3456}" - "127.0.0.1:${CLIENT_PROXY_PORT_START:-8080}-${CLIENT_PROXY_PORT_END:-8090}:${CLIENT_PROXY_PORT_START:-8080}-${CLIENT_PROXY_PORT_END:-8090}" diff --git a/scripts/install-macos-client.sh b/scripts/install-macos-client.sh index 69bffa8..c409665 100755 --- a/scripts/install-macos-client.sh +++ b/scripts/install-macos-client.sh @@ -7,6 +7,8 @@ BRANCH="${VPN_PROXY_BRANCH:-master}" COMPOSE_FILE="docker-compose.client.yml" DEFAULT_PROXY_PORT="8080" REQUESTED_PROXY_PORT="${VPN_PROXY_CLIENT_PORT:-}" +REQUESTED_UI_PORT="${VPN_PROXY_CLIENT_UI_PORT:-${CLIENT_UI_PORT:-}}" +CLIENT_CONTAINER_NAME="vpn-proxy-client" log() { printf '[vpn-proxy-client] %s\n' "$*" @@ -57,6 +59,122 @@ ask_proxy_port() { printf '%s\n' "$DEFAULT_PROXY_PORT" } +port_range_end() { + local start="$1" + local end="$((start + 10))" + if [ "$end" -gt 65535 ]; then + end=65535 + fi + printf '%s\n' "$end" +} + +published_port_conflicts() { + local port="$1" + local line + + while IFS= read -r line; do + [ -n "$line" ] || continue + case "$line" in + "${CLIENT_CONTAINER_NAME}"$'\t'*) ;; + *) printf '%s\n' "$line" ;; + esac + done < <(docker ps --filter "publish=${port}" --format '{{.Names}} {{.Ports}}') +} + +proxy_port_conflicts() { + local start="$1" + local end + local port + local conflicts + + end="$(port_range_end "$start")" + for port in $(seq "$start" "$end"); do + conflicts="$(published_port_conflicts "$port")" + if [ -n "$conflicts" ]; then + printf 'port %s: %s\n' "$port" "$conflicts" + fi + done +} + +assert_proxy_port_available() { + local port="$1" + local conflicts + + conflicts="$(proxy_port_conflicts "$port")" + if [ -z "$conflicts" ]; then + return 0 + fi + + printf '[vpn-proxy-client] proxy port range %s-%s is already used:\n%s\n' \ + "$port" "$(port_range_end "$port")" "$conflicts" >&2 + die "choose another proxy port with VPN_PROXY_CLIENT_PORT= or stop the conflicting container" +} + +assert_single_port_available() { + local label="$1" + local port="$2" + local conflicts + + conflicts="$(published_port_conflicts "$port")" + if [ -z "$conflicts" ]; then + return 0 + fi + + printf '[vpn-proxy-client] %s port %s is already used:\n%s\n' \ + "$label" "$port" "$conflicts" >&2 + die "choose another ${label} port or stop the conflicting container" +} + +first_free_port() { + local start="$1" + local port + + for port in $(seq "$start" 65535); do + if [ -z "$(published_port_conflicts "$port")" ]; then + printf '%s\n' "$port" + return 0 + fi + done + return 1 +} + +choose_ui_port() { + local value="$1" + local suggested + + if ! is_valid_port "$value"; then + die "CLIENT_UI_PORT must be a port from 1024 to 65535" + fi + + if [ -z "$(published_port_conflicts "$value")" ]; then + printf '%s\n' "$value" + return 0 + fi + + if [ -n "$REQUESTED_UI_PORT" ] || [ ! -r /dev/tty ]; then + assert_single_port_available "UI" "$value" + fi + + suggested="$(first_free_port "$((value + 1))" || true)" + suggested="${suggested:-3457}" + while true; do + printf 'UI port %s is busy. Choose UI port [%s]: ' "$value" "$suggested" >/dev/tty + IFS= read -r value /dev/tty + done +} + +assert_ui_outside_proxy_range() { + if [ "$UI_PORT" -ge "$PROXY_PORT" ] && [ "$UI_PORT" -le "$PROXY_PORT_END" ]; then + die "UI port ${UI_PORT} overlaps proxy port range ${PROXY_PORT}-${PROXY_PORT_END}" + fi +} + wait_for_client_ui() { local ui_port="${UI_PORT:-3456}" local ui_url="http://127.0.0.1:${ui_port}/api/state" @@ -135,18 +253,21 @@ if [[ ! -f .env && -f .env.example ]]; then fi PROXY_PORT="$(ask_proxy_port)" -PROXY_PORT_END="$((PROXY_PORT + 10))" -if [ "$PROXY_PORT_END" -gt 65535 ]; then - PROXY_PORT_END=65535 -fi -UI_PORT="${CLIENT_UI_PORT:-$(get_env_value CLIENT_UI_PORT)}" +assert_proxy_port_available "$PROXY_PORT" +PROXY_PORT_END="$(port_range_end "$PROXY_PORT")" +UI_PORT="${REQUESTED_UI_PORT:-$(get_env_value CLIENT_UI_PORT)}" UI_PORT="${UI_PORT:-3456}" +UI_PORT="$(choose_ui_port "$UI_PORT")" +assert_ui_outside_proxy_range set_env_value APP_MODE client +set_env_value CLIENT_UI_PORT "$UI_PORT" +set_env_value CLIENT_PROXY_PORT "$PROXY_PORT" set_env_value CLIENT_PROXY_PORT_START "$PROXY_PORT" set_env_value CLIENT_PROXY_PORT_END "$PROXY_PORT_END" set_env_value PROXY_PORT "$PROXY_PORT" +log "UI port: http://127.0.0.1:${UI_PORT}" log "proxy port: 127.0.0.1:${PROXY_PORT} (reserved range ${PROXY_PORT}-${PROXY_PORT_END})" log "building and starting Docker client"