#!/usr/bin/env bash set -euo pipefail INSTALL_DIR="${VPN_PROXY_INSTALL_DIR:-$HOME/.vpn-proxy-client}" REPO_URL="${VPN_PROXY_REPO_URL:-https://git.dokops.ru/dokril/vpn-proxy.git}" BRANCH="${VPN_PROXY_BRANCH:-master}" COMPOSE_FILE="docker-compose.client.yml" DEFAULT_PROXY_PORT="8080" REQUESTED_PROXY_PORT="${VPN_PROXY_CLIENT_PORT:-}" log() { printf '[vpn-proxy-client] %s\n' "$*" } die() { printf '[vpn-proxy-client] error: %s\n' "$*" >&2 exit 1 } need() { command -v "$1" >/dev/null 2>&1 || die "$1 is required" } is_valid_port() { case "$1" in ''|*[!0-9]*) return 1 ;; esac [ "$1" -ge 1024 ] && [ "$1" -le 65535 ] } ask_proxy_port() { local value="" if [ -n "$REQUESTED_PROXY_PORT" ]; then if ! is_valid_port "$REQUESTED_PROXY_PORT"; then die "VPN_PROXY_CLIENT_PORT must be a port from 1024 to 65535" fi printf '%s\n' "$REQUESTED_PROXY_PORT" return 0 fi if [ -r /dev/tty ]; then while true; do printf 'Proxy port for local apps [%s]: ' "$DEFAULT_PROXY_PORT" >/dev/tty IFS= read -r value /dev/tty done fi if ! is_valid_port "$DEFAULT_PROXY_PORT"; then die "VPN_PROXY_CLIENT_PORT must be a port from 1024 to 65535" fi printf '%s\n' "$DEFAULT_PROXY_PORT" } wait_for_client_ui() { local ui_port="${UI_PORT:-3456}" local ui_url="http://127.0.0.1:${ui_port}/api/state" local attempt for attempt in $(seq 1 30); do if curl -fsS "$ui_url" >/dev/null 2>&1; then return 0 fi sleep 1 done printf '\n[vpn-proxy-client] client did not become ready at %s\n' "$ui_url" >&2 printf '[vpn-proxy-client] docker compose status:\n' >&2 docker compose -f "$COMPOSE_FILE" ps >&2 || true printf '\n[vpn-proxy-client] recent service logs:\n' >&2 docker compose -f "$COMPOSE_FILE" logs --tail=120 vpn-proxy-client >&2 || true die "client UI is not ready; see Docker status and logs above" } set_env_value() { local key="$1" local value="$2" local tmp tmp="$(mktemp)" if [ -f .env ] && grep -q "^${key}=" .env; then awk -v key="$key" -v value="$value" ' BEGIN { prefix = key "=" } index($0, prefix) == 1 { print key "=" value; next } { print } ' .env > "$tmp" else [ -f .env ] && cat .env > "$tmp" printf '%s=%s\n' "$key" "$value" >> "$tmp" fi mv "$tmp" .env } get_env_value() { local key="$1" [ -f .env ] || return 0 awk -v key="$key" ' BEGIN { prefix = key "=" } index($0, prefix) == 1 { print substr($0, length(prefix) + 1); exit } ' .env } if [[ "$(uname -s)" != "Darwin" ]]; then die "this installer is intended for macOS" fi need git need docker need curl docker compose version >/dev/null 2>&1 || die "Docker Compose plugin is required" docker info >/dev/null 2>&1 || die "Docker Desktop is not running" if [[ -d "$INSTALL_DIR/.git" ]]; then log "updating $INSTALL_DIR" git -C "$INSTALL_DIR" fetch origin "$BRANCH" git -C "$INSTALL_DIR" checkout "$BRANCH" git -C "$INSTALL_DIR" pull --ff-only origin "$BRANCH" else log "cloning $REPO_URL#$BRANCH to $INSTALL_DIR" mkdir -p "$(dirname "$INSTALL_DIR")" git clone --branch "$BRANCH" "$REPO_URL" "$INSTALL_DIR" fi cd "$INSTALL_DIR" if [[ ! -f .env && -f .env.example ]]; then cp .env.example .env 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)}" UI_PORT="${UI_PORT:-3456}" set_env_value APP_MODE client 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 "proxy port: 127.0.0.1:${PROXY_PORT} (reserved range ${PROXY_PORT}-${PROXY_PORT_END})" log "building and starting Docker client" docker compose -f "$COMPOSE_FILE" up -d --build wait_for_client_ui cat <