Add interactive install.sh for server deployment
This commit is contained in:
+5
-2
@@ -18,8 +18,11 @@ CADDY_AUTH_API_TOKEN=
|
||||
# Опционально: Subscription Page (например https://sub.example.com)
|
||||
REMNAWAVE_SUBSCRIPTION_URL=
|
||||
|
||||
# PostgreSQL (docker-compose подставляет URL к сервису db)
|
||||
DATABASE_URL=postgres://tgvpn:tgvpn@db:5432/tgvpn?sslmode=disable
|
||||
# PostgreSQL (должен совпадать с POSTGRES_* ниже; install.sh сгенерирует автоматически)
|
||||
POSTGRES_USER=tgvpn
|
||||
POSTGRES_PASSWORD=change_me_strong_password
|
||||
POSTGRES_DB=tgvpn
|
||||
DATABASE_URL=postgres://tgvpn:change_me_strong_password@db:5432/tgvpn?sslmode=disable
|
||||
|
||||
# Создание пользователей по умолчанию
|
||||
DEFAULT_USER_DAYS=30
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
### Добавлено
|
||||
|
||||
- `install.sh` — интерактивный установщик на Linux-сервер (опрос параметров, `.env`, Docker)
|
||||
- PostgreSQL 16 в Docker Compose (`DATABASE_URL`)
|
||||
- Создание пользователей Remnawave: `/admin user`, `/admin user <логин> [дней]`
|
||||
- Назначение сквадов: external + internal (`/admin assign <логин>`, мастер с кнопками)
|
||||
|
||||
@@ -7,6 +7,7 @@ Telegram-бот на Go для управления VPN через панель
|
||||
## Содержание
|
||||
|
||||
- [Требования](#требования)
|
||||
- [Установщик (рекомендуется)](#установщик-на-сервере)
|
||||
- [Быстрый старт](#быстрый-старт-docker-compose)
|
||||
- [PostgreSQL](#postgresql)
|
||||
- [Развёртывание на VPS](#развёртывание-на-vps-linux)
|
||||
@@ -29,6 +30,63 @@ Telegram-бот на Go для управления VPN через панель
|
||||
|
||||
---
|
||||
|
||||
## Установщик на сервере
|
||||
|
||||
Интерактивный скрипт запросит все параметры, создаст `.env` и запустит Docker.
|
||||
|
||||
### Требования на сервере
|
||||
|
||||
- Linux (Ubuntu 22.04/24.04, Debian 12)
|
||||
- `curl`, `git` (для клонирования)
|
||||
- Права `sudo` (для установки Docker при необходимости)
|
||||
|
||||
### Установка одной командой
|
||||
|
||||
Если репозиторий уже на сервере:
|
||||
|
||||
```bash
|
||||
cd tgvpn
|
||||
chmod +x install.sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
Или скачайте скрипт и укажите каталог `/opt/tgvpn`:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /opt/tgvpn
|
||||
cd /opt/tgvpn
|
||||
git clone <URL-вашего-репозитория> .
|
||||
chmod +x install.sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
### Что спрашивает установщик
|
||||
|
||||
| Блок | Параметры |
|
||||
|------|-----------|
|
||||
| Telegram | `BOT_TOKEN`, `TELEGRAM_ADMIN_ID`, `BOT_DEBUG` |
|
||||
| Remnawave | URL панели, API token, Caddy token, subscription URL |
|
||||
| PostgreSQL | пользователь, база, пароль (можно сгенерировать случайный) |
|
||||
| VPN | срок по умолчанию, UUID сквадов (опционально) |
|
||||
| Система | каталог установки, URL git (если не из текущей папки) |
|
||||
|
||||
После завершения: `docker compose up -d --build`, проверка `docker compose ps`.
|
||||
|
||||
### Переменные окружения для PostgreSQL в compose
|
||||
|
||||
В `.env` должны совпадать `POSTGRES_PASSWORD` и пароль в `DATABASE_URL`:
|
||||
|
||||
```env
|
||||
POSTGRES_USER=tgvpn
|
||||
POSTGRES_PASSWORD=ваш_сильный_пароль
|
||||
POSTGRES_DB=tgvpn
|
||||
DATABASE_URL=postgres://tgvpn:ваш_сильный_пароль@db:5432/tgvpn?sslmode=disable
|
||||
```
|
||||
|
||||
Установщик заполняет это автоматически.
|
||||
|
||||
---
|
||||
|
||||
## Быстрый старт (Docker Compose)
|
||||
|
||||
### 1. Клонирование
|
||||
@@ -645,6 +703,7 @@ tgvpn/
|
||||
│ │ └── migrations/ # SQL-миграции (001_init.sql)
|
||||
│ └── remnawave/ # API панели (users, squads)
|
||||
├── Dockerfile # multi-stage сборка
|
||||
├── install.sh # интерактивный установщик на сервер
|
||||
├── docker-compose.yml # bot + PostgreSQL (volume pgdata)
|
||||
├── .env.example # шаблон переменных
|
||||
├── .dockerignore
|
||||
|
||||
+6
-4
@@ -4,9 +4,11 @@ services:
|
||||
container_name: tgvpn-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: tgvpn
|
||||
POSTGRES_PASSWORD: tgvpn
|
||||
POSTGRES_DB: tgvpn
|
||||
POSTGRES_USER: ${POSTGRES_USER:-tgvpn}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-tgvpn}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-tgvpn}
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
@@ -29,7 +31,7 @@ services:
|
||||
- .env
|
||||
environment:
|
||||
BOT_DEBUG: ${BOT_DEBUG:-false}
|
||||
DATABASE_URL: postgres://tgvpn:tgvpn@db:5432/tgvpn?sslmode=disable
|
||||
DATABASE_URL: ${DATABASE_URL:-postgres://tgvpn:tgvpn@db:5432/tgvpn?sslmode=disable}
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
|
||||
+281
@@ -0,0 +1,281 @@
|
||||
#!/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 "$@"
|
||||
Reference in New Issue
Block a user