mirror of
https://github.com/hackdiaz-dev/free-turn-proxy.git
synced 2026-10-12 09:41:53 +02:00
feat: идемпотентный установщик с выбором версии и обновлением
Повторный запуск install-server.sh обнаруживает установку и предлагает меню: обновить версию (конфиг сохраняется), переконфигурировать, удалить. - конфиг установки в /opt/free-turn-proxy/install.conf (chmod 600) - выбор версии релиза при установке (список с GitHub API, дефолт latest) - iptables -C перед -I: нет дублей правил при повторе - атомарная подмена бинарника systemd + restart - docker compose pull для подтягивания нового образа
This commit is contained in:
@@ -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`.
|
||||
>
|
||||
> Если вы хотите всё настроить руками — следуйте инструкциям ниже.
|
||||
|
||||
---
|
||||
|
||||
+463
-238
@@ -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"
|
||||
______ _____ ____
|
||||
/ ____/_______ ___ /_ __/_ ___________ / __ \_________ _ ____ __
|
||||
/ /_ / ___/ _ \/ _ \ / / / / / / ___/ __ \/ /_/ / ___/ __ \| |/_/ / / /
|
||||
/ __/ / / / __/ __/ / / / /_/ / / / / / / ____/ / / /_/ /> </ /_/ /
|
||||
/_/ /_/ \___/\___/ /_/ \__,_/_/ /_/ /_/_/ /_/ \____/_/|_|\__, /
|
||||
/____/
|
||||
EOF
|
||||
echo -e "${NC}"
|
||||
info "Вас приветствует интерактивный установщик Free Turn Proxy Server!"
|
||||
echo ""
|
||||
|
||||
# Установка базовых утилит
|
||||
info "Проверка базовых утилит..."
|
||||
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 установлены."
|
||||
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 <version> <asset-name> — 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" <<EOF
|
||||
# Сгенерировано install-server.sh — переиспользуется при обновлении. Не редактируйте вручную.
|
||||
INSTALL_METHOD="$INSTALL_METHOD"
|
||||
VERSION="$VERSION"
|
||||
PROXY_MODE="$PROXY_MODE"
|
||||
BACKEND_PORT="$BACKEND_PORT"
|
||||
LISTEN_PORT="$LISTEN_PORT"
|
||||
OBF_PROFILE="$OBF_PROFILE"
|
||||
OBF_KEY="$OBF_KEY"
|
||||
CLIENTS_FILE_CONF="$CLIENTS_FILE_CONF"
|
||||
EOF
|
||||
chmod 600 "$CONF_FILE" # содержит OBF_KEY
|
||||
}
|
||||
|
||||
connect_addr() { echo "127.0.0.1:${BACKEND_PORT}"; }
|
||||
|
||||
# ============================================================================
|
||||
# Мастер настройки (install / reconfigure)
|
||||
# ============================================================================
|
||||
wizard() {
|
||||
echo ""
|
||||
info "Выберите метод установки сервера:"
|
||||
echo -e " ${GREEN}1) Docker Compose (Рекомендуется)${NC}"
|
||||
echo -e " 2) Systemd (Прямой запуск бинарника)"
|
||||
ask INSTALL_METHOD "Ваш выбор [1/2]" "${INSTALL_METHOD:-1}"
|
||||
|
||||
# WireGuard (только подсказка дефолтного порта бэкенда)
|
||||
echo ""
|
||||
info "Проверка WireGuard..."
|
||||
local wg_port=""
|
||||
if ! command -v wg >/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 <<EOF
|
||||
services:
|
||||
free-turn-proxy:
|
||||
image: ghcr.io/samosvalishe/free-turn-proxy:latest
|
||||
container_name: free-turn-proxy
|
||||
network_mode: "host"
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- CONNECT_ADDR=$CONNECT_ADDR
|
||||
- LISTEN_ADDR=0.0.0.0:$LISTEN_PORT
|
||||
- MODE=$PROXY_MODE
|
||||
- OBF_PROFILE=$OBF_PROFILE
|
||||
- OBF_KEY=$OBF_KEY
|
||||
EOF
|
||||
if [ -n "$CLIENTS_FILE_CONF" ]; then
|
||||
echo " - CLIENTS_FILE=$CLIENTS_FILE_CONF" >> /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 <<EOF
|
||||
apply_systemd() {
|
||||
download_binary
|
||||
|
||||
info "Генерация systemd-сервиса..."
|
||||
local args="-listen 0.0.0.0:${LISTEN_PORT} -connect $(connect_addr) -mode ${PROXY_MODE}"
|
||||
[ "$OBF_PROFILE" != "none" ] && args="$args -obf-profile ${OBF_PROFILE} -obf-key ${OBF_KEY}"
|
||||
[ -n "$CLIENTS_FILE_CONF" ] && args="$args -clients-file ${CLIENTS_FILE_CONF}"
|
||||
|
||||
cat > "$UNIT_FILE" <<EOF
|
||||
[Unit]
|
||||
Description=Free TURN Proxy Server
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/opt/free-turn-proxy/server $CMD_ARGS
|
||||
ExecStart=${APP_DIR}/server ${args}
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
User=nobody
|
||||
@@ -265,35 +363,162 @@ WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now free-turn-proxy.service >/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 <id_клиента>"
|
||||
echo " Список: docker exec -it free-turn-proxy /app/server clients list"
|
||||
else
|
||||
echo " Добавить: ${APP_DIR}/server -clients-file ${CLIENTS_FILE_CONF} clients add <id_клиента>"
|
||||
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"
|
||||
______ _____ ____
|
||||
/ ____/_______ ___ /_ __/_ ___________ / __ \_________ _ ____ __
|
||||
/ /_ / ___/ _ \/ _ \ / / / / / / ___/ __ \/ /_/ / ___/ __ \| |/_/ / / /
|
||||
/ __/ / / / __/ __/ / / / /_/ / / / / / / ____/ / / /_/ /> </ /_/ /
|
||||
/_/ /_/ \___/\___/ /_/ \__,_/_/ /_/ /_/_/ /_/ \____/_/|_|\__, /
|
||||
/____/
|
||||
EOF
|
||||
echo -e "${NC}"
|
||||
|
||||
ensure_base_deps
|
||||
detect_arch
|
||||
|
||||
if is_installed; then
|
||||
load_config
|
||||
menu_existing
|
||||
else
|
||||
flow_install
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
Reference in New Issue
Block a user