282 lines
9.5 KiB
Bash
282 lines
9.5 KiB
Bash
#!/usr/bin/env bash
|
||
# Интерактивная установка tgvpn на Linux-сервер (Docker + PostgreSQL)
|
||
set -euo pipefail
|
||
|
||
INSTALL_DIR="${INSTALL_DIR:-/opt/tgvpn}"
|
||
REPO_URL=""
|
||
USE_CURRENT_DIR=false
|
||
SKIP_DOCKER_INSTALL=false
|
||
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
CYAN='\033[0;36m'
|
||
NC='\033[0m'
|
||
|
||
info() { echo -e "${CYAN}[*]${NC} $*"; }
|
||
ok() { echo -e "${GREEN}[✓]${NC} $*"; }
|
||
warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
||
fail() { echo -e "${RED}[✗]${NC} $*" >&2; exit 1; }
|
||
|
||
prompt() {
|
||
local label="$1"
|
||
local default="${2:-}"
|
||
local val
|
||
if [[ -n "$default" ]]; then
|
||
read -r -p "$label [$default]: " val
|
||
echo "${val:-$default}"
|
||
else
|
||
read -r -p "$label: " val
|
||
echo "$val"
|
||
fi
|
||
}
|
||
|
||
prompt_required() {
|
||
local label="$1"
|
||
local val=""
|
||
while [[ -z "$val" ]]; do
|
||
read -r -p "$label: " val
|
||
[[ -z "$val" ]] && warn "Поле обязательно."
|
||
done
|
||
echo "$val"
|
||
}
|
||
|
||
prompt_secret() {
|
||
local label="$1"
|
||
local val=""
|
||
while [[ -z "$val" ]]; do
|
||
read -r -s -p "$label: " val
|
||
echo ""
|
||
[[ -z "$val" ]] && warn "Поле обязательно."
|
||
done
|
||
echo "$val"
|
||
}
|
||
|
||
prompt_yn() {
|
||
local label="$1"
|
||
local default="${2:-y}"
|
||
local hint="Y/n"
|
||
[[ "$default" == "n" ]] && hint="y/N"
|
||
local ans
|
||
read -r -p "$label [$hint]: " ans
|
||
ans="${ans:-$default}"
|
||
[[ "$ans" =~ ^[Yy] ]]
|
||
}
|
||
|
||
need_cmd() {
|
||
command -v "$1" >/dev/null 2>&1
|
||
}
|
||
|
||
check_docker() {
|
||
need_cmd docker && docker compose version >/dev/null 2>&1
|
||
}
|
||
|
||
install_docker() {
|
||
info "Установка Docker (официальный репозиторий)..."
|
||
if need_cmd apt-get; then
|
||
sudo apt-get update -qq
|
||
sudo apt-get install -y ca-certificates curl
|
||
sudo install -m 0755 -d /etc/apt/keyrings
|
||
if [[ ! -f /etc/apt/keyrings/docker.asc ]]; then
|
||
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
|
||
sudo chmod a+r /etc/apt/keyrings/docker.asc
|
||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
|
||
$(. /etc/os-release && echo "${VERSION_CODENAME}") stable" | \
|
||
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||
fi
|
||
sudo apt-get update -qq
|
||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||
sudo usermod -aG docker "$USER" 2>/dev/null || true
|
||
ok "Docker установлен. Если команда docker требует sudo — перелогиньтесь или выполните: newgrp docker"
|
||
else
|
||
fail "Автоустановка Docker поддерживается только для Debian/Ubuntu (apt). Установите Docker вручную: https://docs.docker.com/engine/install/"
|
||
fi
|
||
}
|
||
|
||
write_env() {
|
||
local env_file="$1"
|
||
cat > "$env_file" <<EOF
|
||
# Сгенерировано install.sh $(date -Iseconds)
|
||
BOT_TOKEN=${BOT_TOKEN}
|
||
BOT_DEBUG=${BOT_DEBUG}
|
||
TELEGRAM_ADMIN_ID=${TELEGRAM_ADMIN_ID}
|
||
|
||
REMNAWAVE_PANEL_NAME=${REMNAWAVE_PANEL_NAME}
|
||
REMNAWAVE_PANEL_URL=${REMNAWAVE_PANEL_URL}
|
||
REMNAWAVE_API_TOKEN=${REMNAWAVE_API_TOKEN}
|
||
CADDY_AUTH_API_TOKEN=${CADDY_AUTH_API_TOKEN}
|
||
REMNAWAVE_SUBSCRIPTION_URL=${REMNAWAVE_SUBSCRIPTION_URL}
|
||
|
||
POSTGRES_USER=${POSTGRES_USER}
|
||
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||
POSTGRES_DB=${POSTGRES_DB}
|
||
DATABASE_URL=${DATABASE_URL}
|
||
|
||
DEFAULT_USER_DAYS=${DEFAULT_USER_DAYS}
|
||
DEFAULT_EXTERNAL_SQUAD_UUID=${DEFAULT_EXTERNAL_SQUAD_UUID}
|
||
DEFAULT_INTERNAL_SQUAD_UUIDS=${DEFAULT_INTERNAL_SQUAD_UUIDS}
|
||
EOF
|
||
chmod 600 "$env_file"
|
||
}
|
||
|
||
banner() {
|
||
echo ""
|
||
echo "========================================"
|
||
echo " tgvpn — установщик Telegram-бота"
|
||
echo " Remnawave + PostgreSQL + Docker"
|
||
echo "========================================"
|
||
echo ""
|
||
}
|
||
|
||
validate_db_password() {
|
||
if [[ "$POSTGRES_PASSWORD" =~ [:@/?#\[\] ] ]]; then
|
||
warn "В пароле PostgreSQL есть спецсимволы. Рекомендуется сгенерировать пароль (буквы/цифры)."
|
||
if ! prompt_yn "Продолжить с этим паролем?" "n"; then
|
||
fail "Укажите другой пароль или сгенерируйте автоматически."
|
||
fi
|
||
fi
|
||
}
|
||
|
||
main() {
|
||
banner
|
||
|
||
local script_dir
|
||
script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||
if [[ -f "$script_dir/docker-compose.yml" ]] && [[ -f "$script_dir/main.go" ]]; then
|
||
if prompt_yn "Запустить установку из текущей папки ($script_dir)?" "y"; then
|
||
USE_CURRENT_DIR=true
|
||
INSTALL_DIR="$script_dir"
|
||
fi
|
||
fi
|
||
|
||
if ! $USE_CURRENT_DIR; then
|
||
INSTALL_DIR="$(prompt "Каталог установки" "$INSTALL_DIR")"
|
||
REPO_URL="$(prompt "URL git-репозитория (пусто — только каталог, код уже должен быть там)" "")"
|
||
fi
|
||
|
||
echo ""
|
||
info "=== Telegram ==="
|
||
BOT_TOKEN="$(prompt_secret "BOT_TOKEN (от @BotFather)")"
|
||
TELEGRAM_ADMIN_ID="$(prompt_required "TELEGRAM_ADMIN_ID (числовой ID от @userinfobot)")"
|
||
if [[ ! "$TELEGRAM_ADMIN_ID" =~ ^[0-9]+$ ]]; then
|
||
fail "TELEGRAM_ADMIN_ID должен быть числом"
|
||
fi
|
||
if prompt_yn "Включить BOT_DEBUG (подробные логи)?" "n"; then
|
||
BOT_DEBUG=true
|
||
else
|
||
BOT_DEBUG=false
|
||
fi
|
||
|
||
echo ""
|
||
info "=== Remnawave ==="
|
||
REMNAWAVE_PANEL_NAME="$(prompt "Название панели в боте" "Панель 1")"
|
||
REMNAWAVE_PANEL_URL="$(prompt_required "REMNAWAVE_PANEL_URL (https://panel.example.com)")"
|
||
if [[ ! "$REMNAWAVE_PANEL_URL" =~ ^https?:// ]]; then
|
||
fail "URL панели должен начинаться с http:// или https://"
|
||
fi
|
||
REMNAWAVE_PANEL_URL="${REMNAWAVE_PANEL_URL%/}"
|
||
REMNAWAVE_API_TOKEN="$(prompt_secret "REMNAWAVE_API_TOKEN (Settings → API Tokens)")"
|
||
CADDY_AUTH_API_TOKEN="$(prompt "CADDY_AUTH_API_TOKEN (опционально, Enter — пропустить)" "")"
|
||
REMNAWAVE_SUBSCRIPTION_URL="$(prompt "REMNAWAVE_SUBSCRIPTION_URL (опционально)" "")"
|
||
if [[ -n "$REMNAWAVE_SUBSCRIPTION_URL" ]]; then
|
||
REMNAWAVE_SUBSCRIPTION_URL="${REMNAWAVE_SUBSCRIPTION_URL%/}"
|
||
fi
|
||
|
||
echo ""
|
||
info "=== PostgreSQL ==="
|
||
POSTGRES_USER="$(prompt "Пользователь БД" "tgvpn")"
|
||
POSTGRES_DB="$(prompt "Имя базы" "tgvpn")"
|
||
if prompt_yn "Сгенерировать случайный пароль PostgreSQL?" "y"; then
|
||
POSTGRES_PASSWORD="$(openssl rand -hex 16 2>/dev/null || head -c 32 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 24)"
|
||
ok "Пароль сгенерирован (сохранён в .env)"
|
||
else
|
||
POSTGRES_PASSWORD="$(prompt_secret "POSTGRES_PASSWORD")"
|
||
fi
|
||
validate_db_password
|
||
DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?sslmode=disable"
|
||
|
||
echo ""
|
||
info "=== Пользователи VPN (по умолчанию) ==="
|
||
DEFAULT_USER_DAYS="$(prompt "Срок подписки по умолчанию (дней)" "30")"
|
||
DEFAULT_EXTERNAL_SQUAD_UUID="$(prompt "DEFAULT_EXTERNAL_SQUAD_UUID (опционально)" "")"
|
||
DEFAULT_INTERNAL_SQUAD_UUIDS="$(prompt "DEFAULT_INTERNAL_SQUAD_UUIDS через запятую (опционально)" "")"
|
||
|
||
echo ""
|
||
if ! check_docker; then
|
||
warn "Docker или Docker Compose не найдены."
|
||
if prompt_yn "Установить Docker автоматически (Ubuntu/Debian)?" "y"; then
|
||
install_docker
|
||
if ! check_docker; then
|
||
warn "После установки может потребоваться перелогин. Запустите скрипт снова или: sudo docker compose ..."
|
||
SKIP_DOCKER_INSTALL=true
|
||
fi
|
||
else
|
||
fail "Нужны Docker и плагин compose. Установите вручную и запустите скрипт снова."
|
||
fi
|
||
else
|
||
ok "Docker и Docker Compose найдены"
|
||
fi
|
||
|
||
echo ""
|
||
info "=== Установка файлов ==="
|
||
|
||
if ! $USE_CURRENT_DIR; then
|
||
sudo mkdir -p "$INSTALL_DIR"
|
||
sudo chown "$USER:$USER" "$INSTALL_DIR"
|
||
if [[ -n "$REPO_URL" ]]; then
|
||
if [[ -d "$INSTALL_DIR/.git" ]]; then
|
||
info "Обновление репозитория..."
|
||
git -C "$INSTALL_DIR" pull --ff-only || warn "git pull не выполнен"
|
||
else
|
||
git clone "$REPO_URL" "$INSTALL_DIR"
|
||
fi
|
||
elif [[ ! -f "$INSTALL_DIR/docker-compose.yml" ]]; then
|
||
fail "В $INSTALL_DIR нет docker-compose.yml. Укажите URL репозитория или запустите скрипт из папки проекта."
|
||
fi
|
||
fi
|
||
|
||
cd "$INSTALL_DIR"
|
||
|
||
if [[ -f .env ]]; then
|
||
if ! prompt_yn ".env уже существует. Перезаписать?" "n"; then
|
||
fail "Установка отменена. Отредактируйте .env вручную."
|
||
fi
|
||
cp .env ".env.bak.$(date +%s)" 2>/dev/null || true
|
||
warn "Старая копия: .env.bak.*"
|
||
fi
|
||
|
||
write_env ".env"
|
||
ok "Создан .env"
|
||
|
||
echo ""
|
||
info "=== Запуск контейнеров ==="
|
||
DC="docker compose"
|
||
if ! docker compose version >/dev/null 2>&1; then
|
||
DC="sudo docker compose"
|
||
fi
|
||
|
||
$DC pull db 2>/dev/null || true
|
||
$DC up -d --build
|
||
|
||
echo ""
|
||
sleep 3
|
||
$DC ps
|
||
|
||
echo ""
|
||
ok "Установка завершена!"
|
||
echo ""
|
||
echo " Каталог: $INSTALL_DIR"
|
||
echo " Логи бота: $DC logs -f bot"
|
||
echo " Логи БД: $DC logs -f db"
|
||
echo " Перезапуск: $DC up -d --build"
|
||
echo ""
|
||
echo " В Telegram (аккаунт админа $TELEGRAM_ADMIN_ID):"
|
||
echo " /start"
|
||
echo " /admin check"
|
||
echo " /admin squads"
|
||
echo " /admin user"
|
||
echo ""
|
||
}
|
||
|
||
main "$@"
|