Add interactive install.sh for server deployment

This commit is contained in:
tgvpn
2026-05-21 01:22:54 +03:00
parent fd22714c9b
commit 30866bb244
5 changed files with 352 additions and 6 deletions
+5 -2
View File
@@ -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
+1
View File
@@ -6,6 +6,7 @@
### Добавлено
- `install.sh` — интерактивный установщик на Linux-сервер (опрос параметров, `.env`, Docker)
- PostgreSQL 16 в Docker Compose (`DATABASE_URL`)
- Создание пользователей Remnawave: `/admin user`, `/admin user <логин> [дней]`
- Назначение сквадов: external + internal (`/admin assign <логин>`, мастер с кнопками)
+59
View File
@@ -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
View File
@@ -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
View File
@@ -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 "$@"