diff --git a/docs/deploy.md b/docs/deploy.md index 865abdc..153f2f0 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -9,6 +9,8 @@ > curl -fsSL https://raw.githubusercontent.com/samosvalishe/free-turn-proxy/master/scripts/install-server.sh -o install-server.sh > sudo bash install-server.sh > ``` +> Скрипт идемпотентен: **повторный запуск** обнаружит существующую установку и предложит меню — *обновить версию* (конфиг и ключ сохраняются), *переконфигурировать* или *удалить*. При установке можно выбрать конкретную версию релиза или `latest`. +> > Если вы хотите всё настроить руками — следуйте инструкциям ниже. --- diff --git a/scripts/install-server.sh b/scripts/install-server.sh index ca71864..e67d6f5 100644 --- a/scripts/install-server.sh +++ b/scripts/install-server.sh @@ -1,8 +1,21 @@ #!/usr/bin/env bash -# Free Turn Proxy Server Installation Script +# Free Turn Proxy Server — интерактивный установщик. +# +# Идемпотентен: повторный запуск обнаруживает существующую установку и +# предлагает обновить версию (сохраняя конфиг), переконфигурировать или удалить. +# Конфигурация установки хранится в $CONF_FILE и переиспользуется при обновлении. set -e +# --- Константы --- +REPO="samosvalishe/free-turn-proxy" +IMAGE="ghcr.io/${REPO}" +APP_DIR="/opt/free-turn-proxy" +CONF_FILE="${APP_DIR}/install.conf" +SERVICE="free-turn-proxy.service" +UNIT_FILE="/etc/systemd/system/${SERVICE}" +COMPOSE_FILE="${APP_DIR}/docker-compose.yml" + # --- Цвета --- RED='\033[0;31m' GREEN='\033[0;32m' @@ -10,251 +23,336 @@ YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' -# --- Функции логирования --- +# --- Логирование --- info() { echo -e "${CYAN}[*]${NC} $1"; } success() { echo -e "${GREEN}[+]${NC} $1"; } warn() { echo -e "${YELLOW}[!]${NC} $1"; } error() { echo -e "${RED}[x]${NC} $1"; exit 1; } -# --- Проверки --- -if [ "$EUID" -ne 0 ]; then - error "Пожалуйста, запустите скрипт от имени root (sudo bash install-server.sh)" -fi - -echo -e "${CYAN}" -cat << "EOF" - ______ _____ ____ - / ____/_______ ___ /_ __/_ ___________ / __ \_________ _ ____ __ - / /_ / ___/ _ \/ _ \ / / / / / / ___/ __ \/ /_/ / ___/ __ \| |/_/ / / / - / __/ / / / __/ __/ / / / /_/ / / / / / / ____/ / / /_/ /> /dev/null; then - apt-get update -y -qq >/dev/null - apt-get install -y -qq curl jq openssl >/dev/null -elif command -v yum >/dev/null; then - yum install -y -q curl jq openssl >/dev/null -else - warn "Не удалось определить пакетный менеджер (apt/yum). Убедитесь, что curl и jq установлены." -fi -success "Утилиты проверены." - -# Определение архитектуры -ARCH=$(uname -m) -case "$ARCH" in - x86_64|amd64) GOARCH="amd64" ;; - aarch64|arm64) GOARCH="arm64" ;; - *) error "Неподдерживаемая архитектура: $ARCH" ;; -esac - -# Шаг 1. Метод установки (Docker vs Systemd) -echo "" -info "Выберите метод установки сервера:" -echo -e " ${GREEN}1) Docker Compose (Рекомендуется)${NC}" -echo -e " 2) Systemd (Прямой запуск бинарника)" -read -p "Ваш выбор [1/2] (по умолчанию 1): " INSTALL_METHOD -INSTALL_METHOD=${INSTALL_METHOD:-1} - -if [ "$INSTALL_METHOD" == "1" ]; then - if ! command -v docker >/dev/null; then - warn "Docker не найден." - read -p "Установить Docker сейчас? [Y/n]: " INSTALL_DOCKER - INSTALL_DOCKER=${INSTALL_DOCKER:-Y} - if [[ "$INSTALL_DOCKER" =~ ^[Yy]$ ]]; then - info "Установка Docker..." - curl -fsSL https://get.docker.com -o get-docker.sh - sh get-docker.sh >/dev/null 2>&1 - rm -f get-docker.sh - success "Docker успешно установлен." - else - error "Для этого метода требуется Docker. Отмена." - fi - fi -fi - -# Шаг 2. WireGuard -echo "" -info "Проверка WireGuard..." -WG_PORT="" -if ! command -v wg >/dev/null; then - warn "WireGuard не найден." - read -p "Установить пакет WireGuard? (Скрипт установит только пакет, настраивать нужно будет вручную) [Y/n]: " INSTALL_WG - INSTALL_WG=${INSTALL_WG:-Y} - if [[ "$INSTALL_WG" =~ ^[Yy]$ ]]; then - info "Установка WireGuard..." - if command -v apt-get >/dev/null; then - apt-get install -y -qq wireguard >/dev/null - elif command -v yum >/dev/null; then - yum install -y -q epel-release >/dev/null - yum install -y -q wireguard-tools >/dev/null - fi - success "WireGuard установлен." - fi -else - success "WireGuard уже установлен." - # Пытаемся найти порт - if wg show all listen-port >/dev/null 2>&1; then - WG_PORT=$(wg show all listen-port | head -n 1 | awk '{print $2}') - fi - if [ -z "$WG_PORT" ]; then - if [ -n "$(ls -A /etc/wireguard/*.conf 2>/dev/null)" ]; then - WG_PORT=$(grep -i ListenPort /etc/wireguard/*.conf | head -n 1 | awk -F '=' '{print $2}' | tr -d ' ') - fi - fi -fi - -# Шаг 3. Конфигурация прокси -echo "" -info "Конфигурация Free Turn Proxy" - -# Режим -read -p "Режим работы туннеля (udp - для WireGuard/AmneziaWG, tcp - для Xray) [udp]: " PROXY_MODE -PROXY_MODE=${PROXY_MODE:-udp} - -# Порт бэкенда -if [ -n "$WG_PORT" ]; then - read -p "Порт вашего VPN / бэкенда (найден WireGuard на порту $WG_PORT) [$WG_PORT]: " BACKEND_PORT - BACKEND_PORT=${BACKEND_PORT:-$WG_PORT} -else - if [ "$PROXY_MODE" == "udp" ]; then - DEF_B="51820" - else - DEF_B="443" - fi - read -p "Порт вашего VPN / бэкенда (например, $DEF_B) [$DEF_B]: " BACKEND_PORT - BACKEND_PORT=${BACKEND_PORT:-$DEF_B} -fi -CONNECT_ADDR="127.0.0.1:$BACKEND_PORT" - -# Внешний порт -read -p "Внешний порт (на котором Free Turn Proxy будет принимать соединения) [56000]: " LISTEN_PORT -LISTEN_PORT=${LISTEN_PORT:-56000} - -# Обфускация -read -p "Включить маскировку (rtpopus)? [Y/n]: " USE_OBF -USE_OBF=${USE_OBF:-Y} -OBF_PROFILE="none" +# --- Переменные конфигурации (дефолты; перекрываются load_config / мастером) --- +INSTALL_METHOD="1" # 1=docker, 2=systemd +VERSION="latest" +PROXY_MODE="udp" +BACKEND_PORT="" +LISTEN_PORT="56000" +OBF_PROFILE="rtpopus" OBF_KEY="" +CLIENTS_FILE_CONF="" -if [[ "$USE_OBF" =~ ^[Yy]$ ]]; then - OBF_PROFILE="rtpopus" - read -p "Сгенерировать случайный ключ обфускации? [Y/n]: " GEN_KEY - GEN_KEY=${GEN_KEY:-Y} - if [[ "$GEN_KEY" =~ ^[Yy]$ ]]; then - OBF_KEY=$(openssl rand -hex 32) - success "Ключ сгенерирован: $OBF_KEY" +# ============================================================================ +# Хелперы +# ============================================================================ + +# ask VAR "Текст вопроса" "дефолт" — читает ответ в переменную VAR. +ask() { + local __var="$1" __prompt="$2" __def="$3" __ans + if [ -n "$__def" ]; then + read -p "$__prompt [$__def]: " __ans else - read -p "Введите ваш 64-символьный hex ключ: " OBF_KEY - if [ -z "$OBF_KEY" ]; then - error "Ключ не может быть пустым." + read -p "$__prompt: " __ans + fi + printf -v "$__var" '%s' "${__ans:-$__def}" +} + +# yesno "Текст" "Y|N" — true если пользователь согласился. +yesno() { + local __ans + read -p "$1 [$([ "$2" = "Y" ] && echo "Y/n" || echo "y/N")]: " __ans + __ans=${__ans:-$2} + [[ "$__ans" =~ ^[Yy]$ ]] +} + +detect_arch() { + local arch + arch=$(uname -m) + case "$arch" in + x86_64|amd64) GOARCH="amd64" ;; + aarch64|arm64) GOARCH="arm64" ;; + *) error "Неподдерживаемая архитектура: $arch" ;; + esac +} + +ensure_base_deps() { + info "Проверка базовых утилит (curl, jq, openssl)..." + if command -v apt-get >/dev/null; then + apt-get update -y -qq >/dev/null + apt-get install -y -qq curl jq openssl >/dev/null + elif command -v yum >/dev/null; then + yum install -y -q curl jq openssl >/dev/null + else + warn "Пакетный менеджер не определён (apt/yum). Убедитесь, что curl, jq, openssl установлены." + fi +} + +# --- GitHub API --- +gh_latest_version() { + curl -s "https://api.github.com/repos/${REPO}/releases/latest" | jq -r '.tag_name // empty' +} + +gh_recent_versions() { + curl -s "https://api.github.com/repos/${REPO}/releases?per_page=6" | jq -r '.[].tag_name' +} + +# resolve_asset_url — URL ассета релиза (latest или vX.Y.Z). +resolve_asset_url() { + local ver="$1" asset="$2" api + if [ "$ver" = "latest" ]; then + api="https://api.github.com/repos/${REPO}/releases/latest" + else + api="https://api.github.com/repos/${REPO}/releases/tags/${ver}" + fi + curl -s "$api" | jq -r --arg n "$asset" '.assets[] | select(.name==$n) | .browser_download_url' +} + +# Тег Docker-образа: latest -> latest, иначе сам тег. +image_tag() { + [ "$VERSION" = "latest" ] && echo "latest" || echo "$VERSION" +} + +# Интерактивный выбор версии в переменную VERSION. +choose_version() { + info "Получение списка версий с GitHub..." + local latest others def="${VERSION:-latest}" + latest=$(gh_latest_version || true) + echo "" + if [ -n "$latest" ]; then + echo -e " ${GREEN}latest${NC} (= $latest)" + others=$(gh_recent_versions 2>/dev/null | grep -vx "$latest" | head -4 || true) + if [ -n "$others" ]; then + while IFS= read -r t; do echo " $t"; done <<< "$others" + fi + else + warn "Не удалось получить список релизов. Введите тег вручную (или latest)." + fi + echo "" + ask VERSION "Версия (latest или vX.Y.Z)" "$def" +} + +# --- Конфигурация установки --- +is_installed() { + [ -f "$CONF_FILE" ] || [ -f "$COMPOSE_FILE" ] || [ -f "$UNIT_FILE" ] +} + +load_config() { + [ -f "$CONF_FILE" ] && . "$CONF_FILE" +} + +save_config() { + mkdir -p "$APP_DIR" + cat > "$CONF_FILE" </dev/null; then + warn "WireGuard не найден." + if yesno "Установить пакет WireGuard? (только пакет, настройка — вручную)" "Y"; then + if command -v apt-get >/dev/null; then + apt-get install -y -qq wireguard >/dev/null + elif command -v yum >/dev/null; then + yum install -y -q epel-release >/dev/null + yum install -y -q wireguard-tools >/dev/null + fi + success "WireGuard установлен." + fi + else + success "WireGuard уже установлен." + if wg show all listen-port >/dev/null 2>&1; then + wg_port=$(wg show all listen-port | head -n 1 | awk '{print $2}') + fi + if [ -z "$wg_port" ] && [ -n "$(ls -A /etc/wireguard/*.conf 2>/dev/null)" ]; then + wg_port=$(grep -i ListenPort /etc/wireguard/*.conf | head -n 1 | awk -F '=' '{print $2}' | tr -d ' ') fi fi -fi -# Авторизация по Client ID -read -p "Включить авторизацию по Client ID? (Потребуется создать клиентов) [y/N]: " USE_AUTH -USE_AUTH=${USE_AUTH:-N} -CLIENTS_FILE_CONF="" -if [[ "$USE_AUTH" =~ ^[Yy]$ ]]; then - CLIENTS_FILE_CONF="/opt/free-turn-proxy/clients.json" -fi + echo "" + info "Конфигурация Free Turn Proxy" -# Шаг 4. Настройка Firewall (UFW / iptables) -echo "" -info "Настройка брандмауэра..." -read -p "Открыть порт $LISTEN_PORT/udp в UFW/iptables? [Y/n]: " CONF_FW -CONF_FW=${CONF_FW:-Y} -if [[ "$CONF_FW" =~ ^[Yy]$ ]]; then + ask PROXY_MODE "Режим туннеля (udp — WireGuard/AmneziaWG, tcp — Xray)" "${PROXY_MODE:-udp}" + + # Порт бэкенда: приоритет — текущий конфиг, затем найденный WG, затем дефолт по режиму. + local def_backend="$BACKEND_PORT" + if [ -z "$def_backend" ]; then + if [ -n "$wg_port" ]; then + def_backend="$wg_port" + elif [ "$PROXY_MODE" = "udp" ]; then + def_backend="51820" + else + def_backend="443" + fi + fi + ask BACKEND_PORT "Порт вашего VPN / бэкенда" "$def_backend" + + ask LISTEN_PORT "Внешний порт (приём соединений Free Turn Proxy)" "${LISTEN_PORT:-56000}" + + # Обфускация (дефолт ответа = текущее состояние) + local obf_def="Y" + [ "$OBF_PROFILE" = "none" ] && obf_def="N" + if yesno "Включить маскировку (rtpopus)?" "$obf_def"; then + OBF_PROFILE="rtpopus" + if [ -n "$OBF_KEY" ]; then + info "Текущий ключ обфускации сохранён (оставьте пустым, чтобы не менять)." + ask OBF_KEY "Новый 64-hex ключ (Enter — оставить текущий)" "$OBF_KEY" + elif yesno "Сгенерировать случайный ключ обфускации?" "Y"; then + OBF_KEY=$(openssl rand -hex 32) + success "Ключ сгенерирован: $OBF_KEY" + else + ask OBF_KEY "Введите ваш 64-символьный hex ключ" "" + [ -z "$OBF_KEY" ] && error "Ключ не может быть пустым." + fi + else + OBF_PROFILE="none" + OBF_KEY="" + fi + + # Авторизация по Client ID + local auth_def="N" + [ -n "$CLIENTS_FILE_CONF" ] && auth_def="Y" + if yesno "Включить авторизацию по Client ID?" "$auth_def"; then + CLIENTS_FILE_CONF="${APP_DIR}/clients.json" + else + CLIENTS_FILE_CONF="" + fi + + # Версия + echo "" + choose_version + + # Firewall + echo "" + info "Настройка брандмауэра..." + if yesno "Открыть порт ${LISTEN_PORT}/udp в UFW/iptables?" "Y"; then + firewall_open + fi +} + +firewall_open() { if command -v ufw >/dev/null && ufw status | grep -q "Status: active"; then - ufw allow $LISTEN_PORT/udp >/dev/null + ufw allow "${LISTEN_PORT}/udp" >/dev/null # ufw сам дедуплицирует правила success "Порт открыт через UFW." elif command -v iptables >/dev/null; then - iptables -I INPUT -p udp --dport $LISTEN_PORT -j ACCEPT - if command -v netfilter-persistent >/dev/null; then - netfilter-persistent save >/dev/null 2>&1 + # -C проверяет наличие правила → не плодим дубли при повторном запуске. + if ! iptables -C INPUT -p udp --dport "$LISTEN_PORT" -j ACCEPT 2>/dev/null; then + iptables -I INPUT -p udp --dport "$LISTEN_PORT" -j ACCEPT + command -v netfilter-persistent >/dev/null && netfilter-persistent save >/dev/null 2>&1 || true fi success "Порт открыт через iptables." else - warn "Брандмауэр (ufw/iptables) не найден, пожалуйста, откройте порт $LISTEN_PORT/udp вручную." + warn "Брандмауэр (ufw/iptables) не найден — откройте порт ${LISTEN_PORT}/udp вручную." fi -fi +} -# Шаг 5. Установка -echo "" -info "Начинаем установку..." - -mkdir -p /opt/free-turn-proxy - -# Инициализация clients.json если нужно -if [ -n "$CLIENTS_FILE_CONF" ]; then - if [ ! -f "$CLIENTS_FILE_CONF" ]; then +# ============================================================================ +# Применение конфигурации +# ============================================================================ +init_clients_file() { + if [ -n "$CLIENTS_FILE_CONF" ] && [ ! -f "$CLIENTS_FILE_CONF" ]; then echo "[]" > "$CLIENTS_FILE_CONF" success "Создан пустой список клиентов $CLIENTS_FILE_CONF" fi -fi +} -if [ "$INSTALL_METHOD" == "1" ]; then - # DOCKER INSTALL - info "Генерация docker-compose.yml..." - cat > /opt/free-turn-proxy/docker-compose.yml <> /opt/free-turn-proxy/docker-compose.yml - echo " volumes:" >> /opt/free-turn-proxy/docker-compose.yml - echo " - $CLIENTS_FILE_CONF:$CLIENTS_FILE_CONF" >> /opt/free-turn-proxy/docker-compose.yml +ensure_docker() { + if command -v docker >/dev/null; then + return fi + warn "Docker не найден." + if yesno "Установить Docker сейчас?" "Y"; then + info "Установка Docker..." + curl -fsSL https://get.docker.com -o get-docker.sh + sh get-docker.sh >/dev/null 2>&1 + rm -f get-docker.sh + success "Docker успешно установлен." + else + error "Для этого метода требуется Docker. Отмена." + fi +} - cd /opt/free-turn-proxy +apply_docker() { + ensure_docker + info "Генерация docker-compose.yml..." + { + echo "services:" + echo " free-turn-proxy:" + echo " image: ${IMAGE}:$(image_tag)" + echo " container_name: free-turn-proxy" + echo " network_mode: \"host\"" + echo " restart: unless-stopped" + echo " environment:" + echo " - CONNECT_ADDR=$(connect_addr)" + echo " - LISTEN_ADDR=0.0.0.0:${LISTEN_PORT}" + echo " - MODE=${PROXY_MODE}" + echo " - OBF_PROFILE=${OBF_PROFILE}" + echo " - OBF_KEY=${OBF_KEY}" + if [ -n "$CLIENTS_FILE_CONF" ]; then + echo " - CLIENTS_FILE=${CLIENTS_FILE_CONF}" + echo " volumes:" + echo " - ${CLIENTS_FILE_CONF}:${CLIENTS_FILE_CONF}" + fi + } > "$COMPOSE_FILE" + + cd "$APP_DIR" + info "Загрузка образа ${IMAGE}:$(image_tag)..." + docker compose pull info "Запуск контейнера..." docker compose up -d -else - # SYSTEMD INSTALL - info "Скачивание последнего релиза (server-linux-$GOARCH)..." - LATEST_URL=$(curl -s https://api.github.com/repos/samosvalishe/free-turn-proxy/releases/latest | jq -r ".assets[] | select(.name == \"server-linux-$GOARCH\") | .browser_download_url") - if [ -z "$LATEST_URL" ]; then - error "Не удалось найти бинарник server-linux-$GOARCH в последнем релизе." - fi - if ! curl -fL -o /opt/free-turn-proxy/server "$LATEST_URL" >/dev/null 2>&1; then - error "Не удалось скачать бинарник по адресу $LATEST_URL" - fi - chmod +x /opt/free-turn-proxy/server +} - info "Генерация systemd сервиса..." - CMD_ARGS="-listen 0.0.0.0:$LISTEN_PORT -connect $CONNECT_ADDR -mode $PROXY_MODE" - if [ "$OBF_PROFILE" != "none" ]; then - CMD_ARGS="$CMD_ARGS -obf-profile $OBF_PROFILE -obf-key $OBF_KEY" - fi - if [ -n "$CLIENTS_FILE_CONF" ]; then - CMD_ARGS="$CMD_ARGS -clients-file $CLIENTS_FILE_CONF" +download_binary() { + info "Поиск бинарника server-linux-${GOARCH} (версия: ${VERSION})..." + local url + url=$(resolve_asset_url "$VERSION" "server-linux-${GOARCH}") + [ -z "$url" ] && error "Не удалось найти server-linux-${GOARCH} в релизе ${VERSION}." + info "Скачивание..." + # Скачиваем во временный файл и атомарно подменяем — рабочий процесс не падает. + if ! curl -fL -o "${APP_DIR}/server.new" "$url" >/dev/null 2>&1; then + error "Не удалось скачать бинарник по адресу $url" fi + chmod +x "${APP_DIR}/server.new" + mv -f "${APP_DIR}/server.new" "${APP_DIR}/server" +} - cat > /etc/systemd/system/free-turn-proxy.service < "$UNIT_FILE" </dev/null 2>&1 -fi + systemctl enable "$SERVICE" >/dev/null 2>&1 || true + systemctl restart "$SERVICE" # restart идемпотентен: стартует если стоит, перезапускает если работает +} -echo "" -success "Установка успешно завершена!" -echo "--------------------------------------------------------" -echo -e "${CYAN}Конфигурация для подключения (Client):${NC}" - -EXT_IP=$(curl -s ifconfig.me || echo "IP_ВАШЕГО_СЕРВЕРА") -echo " Сервер (peer): $EXT_IP:$LISTEN_PORT" -if [ "$OBF_PROFILE" != "none" ]; then - echo " Профиль обфускации: $OBF_PROFILE" - echo " Ключ обфускации: $OBF_KEY" -fi -echo " Режим: $PROXY_MODE" - -if [ -n "$CLIENTS_FILE_CONF" ]; then - echo "" - echo -e "${YELLOW}Внимание: Включена авторизация по Client ID.${NC}" - echo "Чтобы клиент мог подключиться, вам нужно добавить его на сервере." - if [ "$INSTALL_METHOD" == "1" ]; then - echo "Управление клиентами (через Docker):" - echo " Добавить: docker exec -it free-turn-proxy /app/server clients add <ваш_id_клиента>" - echo " Список: docker exec -it free-turn-proxy /app/server clients list" +apply() { + mkdir -p "$APP_DIR" + init_clients_file + if [ "$INSTALL_METHOD" = "1" ]; then + apply_docker else - echo "Управление клиентами (systemd):" - echo " Добавить: /opt/free-turn-proxy/server -clients-file $CLIENTS_FILE_CONF clients add <ваш_id_клиента>" - echo " Список: /opt/free-turn-proxy/server -clients-file $CLIENTS_FILE_CONF clients list" + apply_systemd fi -fi -echo "--------------------------------------------------------" -echo -e "Подробная документация: https://github.com/samosvalishe/free-turn-proxy" + save_config +} + +# ============================================================================ +# Итоговая сводка +# ============================================================================ +print_summary() { + echo "" + success "Готово!" + echo "--------------------------------------------------------" + echo -e "${CYAN}Конфигурация для подключения (Client):${NC}" + local ext_ip + ext_ip=$(curl -s ifconfig.me || echo "IP_ВАШЕГО_СЕРВЕРА") + echo " Сервер (peer): ${ext_ip}:${LISTEN_PORT}" + if [ "$OBF_PROFILE" != "none" ]; then + echo " Профиль обфускации: $OBF_PROFILE" + echo " Ключ обфускации: $OBF_KEY" + fi + echo " Режим: $PROXY_MODE" + echo " Версия: $VERSION" + + if [ -n "$CLIENTS_FILE_CONF" ]; then + echo "" + echo -e "${YELLOW}Внимание: включена авторизация по Client ID.${NC}" + echo "Добавьте клиента на сервере, иначе он не подключится:" + if [ "$INSTALL_METHOD" = "1" ]; then + echo " Добавить: docker exec -it free-turn-proxy /app/server clients add " + echo " Список: docker exec -it free-turn-proxy /app/server clients list" + else + echo " Добавить: ${APP_DIR}/server -clients-file ${CLIENTS_FILE_CONF} clients add " + echo " Список: ${APP_DIR}/server -clients-file ${CLIENTS_FILE_CONF} clients list" + fi + fi + echo "--------------------------------------------------------" + echo "Повторный запуск скрипта: обновление версии / переконфигурация / удаление." + echo -e "Документация: https://github.com/${REPO}" +} + +# ============================================================================ +# Сценарии +# ============================================================================ +flow_install() { + info "Новая установка Free Turn Proxy Server." + wizard + echo "" + info "Начинаем установку..." + apply + print_summary +} + +flow_update() { + if [ ! -f "$CONF_FILE" ]; then + warn "Файл конфигурации не найден ($CONF_FILE) — нужна переконфигурация." + flow_reconfigure + return + fi + info "Обновление. Текущая версия: ${VERSION}, метод: $([ "$INSTALL_METHOD" = "1" ] && echo docker || echo systemd)." + choose_version + echo "" + info "Применяем версию ${VERSION}..." + apply + print_summary +} + +flow_reconfigure() { + info "Переконфигурация (текущие значения подставлены как дефолты)." + wizard + echo "" + info "Применяем изменения..." + apply + print_summary +} + +flow_uninstall() { + echo "" + warn "Удаление Free Turn Proxy Server." + if ! yesno "Продолжить удаление?" "N"; then + info "Отменено." + return + fi + if [ "$INSTALL_METHOD" = "1" ]; then + if [ -f "$COMPOSE_FILE" ]; then + ( cd "$APP_DIR" && docker compose down ) || warn "docker compose down завершился с ошибкой." + fi + else + systemctl disable --now "$SERVICE" >/dev/null 2>&1 || true + rm -f "$UNIT_FILE" + systemctl daemon-reload + fi + success "Служба остановлена и удалена." + if yesno "Удалить каталог ${APP_DIR} (ключи, clients.json, конфиг)?" "N"; then + rm -rf "$APP_DIR" + success "Каталог удалён." + else + info "Каталог сохранён: $APP_DIR" + fi +} + +menu_existing() { + local method_label + method_label=$([ "$INSTALL_METHOD" = "1" ] && echo docker || echo systemd) + echo "" + info "Обнаружена установка (метод: ${method_label}, версия: ${VERSION:-?})." + echo " 1) Обновить версию (конфиг сохранён)" + echo " 2) Переконфигурировать" + echo " 3) Удалить" + echo " 4) Выход" + local choice + ask choice "Ваш выбор [1-4]" "1" + case "$choice" in + 1) flow_update ;; + 2) flow_reconfigure ;; + 3) flow_uninstall ;; + 4) info "Выход."; exit 0 ;; + *) error "Неизвестный выбор: $choice" ;; + esac +} + +# ============================================================================ +# main +# ============================================================================ +main() { + [ "$EUID" -ne 0 ] && error "Запустите скрипт от root (sudo bash install-server.sh)" + + echo -e "${CYAN}" + cat << "EOF" + ______ _____ ____ + / ____/_______ ___ /_ __/_ ___________ / __ \_________ _ ____ __ + / /_ / ___/ _ \/ _ \ / / / / / / ___/ __ \/ /_/ / ___/ __ \| |/_/ / / / + / __/ / / / __/ __/ / / / /_/ / / / / / / ____/ / / /_/ />