diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5538315 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +node_modules +db +uploads +.env +.env.* +!.env.example +.git +.kilo +.architect +kilo-meta.json +kilo.jsonc +AGENTS.md +corrupt-photo.jpg +wg/config +*.log +__pycache__ +**/__tests__ +**/*.test.js \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 09ccbfb..b6c1b1e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,41 +3,44 @@ FROM node:22-alpine AS builder WORKDIR /app COPY package*.json ./ -RUN npm install && npm cache clean --force -# --- Runtime image --- +RUN apk add --no-cache --virtual .build-deps \ + python3 \ + make \ + g++ \ + gcc \ + linux-headers \ + git \ + py3-setuptools \ + && npm install --omit=dev \ + && apk del .build-deps + +# ============================================================ +# Runtime image +# ============================================================ FROM node:22-alpine -# Install runtime dependencies -RUN apk update && \ - apk add --no-cache --repository https://dl-cdn.alpinelinux.org/alpine/edge/community \ - wireguard-tools \ - && apk add --no-cache \ +RUN apk add --no-cache \ + bash \ + bind-tools \ + curl \ iptables \ iproute2 \ openresolv \ - bash \ - curl \ - && rm -rf /var/cache/apk/* + wireguard-tools WORKDIR /app -# Copy node_modules from builder COPY --from=builder /app/node_modules ./node_modules COPY package*.json ./ -# Copy application source COPY ./src ./src -# Copy startup script COPY ./wg/start.sh /app/start.sh RUN chmod +x /app/start.sh -# Create db directory -RUN mkdir -p /app/db +RUN mkdir -p /app/db /app/uploads -# Health check: bot responds to /health on port 3000 -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ - CMD curl -sf http://localhost:3001/health || exit 1 +EXPOSE 3001 -CMD ["/bin/bash", "/app/start.sh"] \ No newline at end of file +CMD ["/bin/bash", "/app/start.sh"] diff --git a/README.md b/README.md index 3f47724..358b734 100644 --- a/README.md +++ b/README.md @@ -1,175 +1,205 @@ -# Универсальный Телеграмм Магазин +# Telegram Shop Bot -**Описание проекта**: -"Универсальный Телеграмм Магазин" — это телеграмм-бот для организации онлайн-продаж через Telegram. Проект предоставляет полный цикл управления магазином, включая работу с криптовалютами, управление товарами и пользователями, а также интеграцию с VPN через WireGuard для безопасных транзакций. +Телеграм-бот для организации онлайн-продаж через Telegram с поддержкой криптовалют и WireGuard VPN. -**Основные технологии**: -- Node.js + Telegraf для работы с Telegram API -- SQLite для хранения данных -- Docker для контейнеризации -- WireGuard для защищенных соединений -- Поддержка криптокошельков (Bitcoin, Ethereum, Litecoin) +## Возможности -Проект включает несколько ключевых разделов для удобной работы пользователей и администраторов, а также позволяет интегрировать систему криптокошельков для расчетов, управления товарами и отслеживания покупок. +- Каталог товаров с категориями и фильтрацией по локациям +- Покупки с оплатой криптовалютами (BTC, ETH, LTC, USDT, USDC) +- Управление криптокошельками (создание, пополнение, баланс) +- История транзакций и покупок +- SaaS-система с автоматическим расчётом комиссий +- Админ-панель на порту 3001 +- WireGuard VPN для безопасных транзакций -### Основной функционал +## Быстрый старт (одна команда) -#### Для пользователей: -- Просмотр товаров по категориям с фильтрацией по локациям -- Совершение покупок с использованием криптовалют -- Управление криптокошельками (создание, пополнение, просмотр баланса) -- Просмотр истории транзакций и покупок -- Настройка профиля (локация, контактные данные) -- Подключение к защищенному VPN через WireGuard для безопасных транзакций +### Требования -#### Для администраторов: -- Полное управление товарами и категориями -- Управление пользователями (блокировка, редактирование балансов) -- Контроль транзакций и комиссий -- Создание дампов базы данных с автоматическим списанием комиссии (% от балансов кошельков) -- Управление локациями и настройками VPN -- Мониторинг активности пользователей -- SaaS система с автоматическим расчетом комиссий: - - Комиссия за оборот по магазину перед выгрузкой кошельков +- Любое устройство с Docker: x86_64 (PC, сервер) или ARM64 (Orange Pi, Raspberry Pi) +- 512 МБ RAM минимум (Orange Pi Zero 2 поддерживается) ---- +### Установка -### Установка и запуск - -#### Требования: -- Node.js 18+ -- Docker и Docker Compose -- Telegram Bot Token -- SQLite connection string -- WireGuard конфигурация - -#### 1. Установка зависимостей: ```bash -npm install +git clone && cd telegram-shop +bash install.sh ``` -#### 2. Настройка конфигурации: -Создайте файл `.env` в корне проекта со следующим содержимым: -```env -TELEGRAM_BOT_TOKEN=your_bot_token -MONGO_URI=mongodb://localhost:27017/telegram_shop -WIREGUARD_CONFIG_PATH=./wg/config/wg0.conf -``` +Скрипт автоматически: +1. Определит архитектуру (x86_64 / ARM64 / ARMv7) +2. Установит Docker если не установлен +3. Создаст `.env` из шаблона +4. Проверит обязательные переменные +5. Соберёт Docker-образ под текущую архитектуру +6. Запустит контейнер и проверит health-check + +### Ручная установка -#### 3. Запуск через Docker: ```bash -docker-compose up -d +# 1. Клонировать +git clone && cd telegram-shop + +# 2. Создать .env из шаблона +cp .env.example .env +nano .env # заполнить BOT_TOKEN, ADMIN_IDS, ENCRYPTION_KEY + +# 3. Собрать и запустить +docker compose up -d --build + +# 4. Проверить статус +docker compose ps +curl http://localhost:3001/health ``` -#### 4. Настройка WireGuard: -1. Сгенерируйте ключи: +## Настройка .env + +Скопируйте `.env.example` в `.env` и заполните: + +| Переменная | Обязательно | Описание | +|---|---|---| +| `BOT_TOKEN` | ✅ | Токен Telegram бота (@BotFather) | +| `ADMIN_IDS` | ✅ | ID администраторов через запятую | +| `ENCRYPTION_KEY` | ✅ | Ключ шифрования (32 байта hex) | +| `SUPER_ADMIN_IDS` | — | ID супер-админов | +| `SUPPORT_LINK` | — | Ссылка на поддержку | +| `WG_ENABLED` | — | `true` / `false` (по умолчанию `false`) | +| `WG_PRIVATE_KEY` | — | Приватный ключ WireGuard | +| `WG_PUBLIC_KEY` | — | Публичный ключ WireGuard | +| `WG_PRESHARED_KEY` | — | Pre-shared ключ WireGuard | +| `WG_ENDPOINT` | — | Адрес сервера WireGuard | +| `WG_ADDRESS` | — | Адрес интерфейса WireGuard | +| `WG_DNS` | — | DNS для WireGuard | + +Генерация ключа шифрования: ```bash -wg genkey | tee privatekey | wg pubkey > publickey -``` -2. Настройте wg0.conf: -```ini -[Interface] -PrivateKey = -Address = 10.0.0.1/24 -ListenPort = 51820 - -[Peer] -PublicKey = -AllowedIPs = 10.0.0.2/32 +node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" ``` -#### 5. Запуск бота: +## Поддерживаемые устройства + +| Устройство | Архитектура | RAM | Статус | +|---|---|---|---| +| PC / Сервер | x86_64 | ≥ 512 МБ | ✅ | +| Orange Pi Zero 2 | ARM64 (H616) | 512 МБ | ✅ | +| Raspberry Pi 4 | ARM64 | ≥ 1 ГБ | ✅ | +| Raspberry Pi 3 | ARM64 | 1 ГБ | ✅ | +| Raspberry Pi 2 | ARMv7 | 1 ГБ | ✅ | + +Docker автоматически собирает нативные модули (`better-sqlite3`, `tiny-secp256k1`) под архитектуру хоста. + +## Архитектура Docker + +``` +┌─────────────────────────────────────────────┐ +│ telegram_shop_prod (node:22-alpine) │ +│ ┌──────────────┐ ┌─────────────────────┐ │ +│ │ Builder │ │ Runtime │ │ +│ │ python3, g++ │→ │ bash, curl, │ │ +│ │ make, gcc │ │ wireguard-tools, │ │ +│ │ npm install │ │ iptables, bind-tools │ │ +│ └──────────────┘ └─────────────────────┘ │ +│ Port 3001 (localhost) │ +│ Limit: 384MB RAM │ +└─────────────────────────────────────────────┘ + ↑ Volumes: db/, uploads/, .env +``` + +### Файлы + +| Файл | Назначение | +|---|---| +| `Dockerfile` | Multi-stage сборка (builder + runtime) | +| `docker-compose.yml` | Конфигурация контейнера | +| `install.sh` | Автоматический установщик | +| `.dockerignore` | Исключения из Docker-образа | +| `.env.example` | Шаблон переменных окружения | +| `wg/start.sh` | Скрипт запуска (WireGuard + Node.js) | + +## Команды управления + ```bash -npm start +# Запуск +docker compose up -d + +# Пересборка после изменений +docker compose up -d --build + +# Логи +docker compose logs -f + +# Стоп +docker compose down + +# Рестарт +docker compose restart + +# Статус +docker compose ps + +# Health-check +curl http://localhost:3001/health ``` ---- +## WireGuard -### Структура проекта +WireGuard по умолчанию отключен (`WG_ENABLED=false`). Для включения: + +1. Установите `WG_ENABLED=true` в `.env` +2. Заполните ключи WireGuard в `.env` +3. Перезапустите: `docker compose restart` + +Контейнер требует `NET_ADMIN` и `sysctl net.ipv4.conf.all.src_valid_mark=1` для WireGuard. Эти привилегии заданы в `docker-compose.yml`. + +## Безопасность + +- `.env` монтируется только для чтения (`:ro`) +- Порт 3001 привязан к `127.0.0.1` (доступен только с хоста) +- Нативные модули компилируются в builder-стейдже (чистый runtime) +- `devDependencies` не попадают в production-образ +- Тестовые файлы исключены из Docker-образа (`.dockerignore`) +- `node_modules` хоста не попадают в образ (`.dockerignore`) + +## Структура проекта ``` ├── src/ -│ ├── config/ # Конфигурация приложения +│ ├── admin/ # Админ-панель (Express) +│ ├── config/ # Конфигурация (БД, крипто) │ ├── context/ # Контекст и состояния бота │ ├── handlers/ # Обработчики команд -│ ├── models/ # Модели данных +│ ├── middleware/ # Промежуточные обработчики +│ ├── migrations/ # Миграции БД +│ ├── models/ # Модели данных (SQLite) +│ ├── router/ # Роутинг Express │ ├── services/ # Бизнес-логика -│ ├── utils/ # Вспомогательные утилиты +│ ├── utils/ # Утилиты │ └── index.js # Точка входа -├── wg/ # Конфигурация WireGuard -├── docker-compose.yml # Docker конфигурация -└── Dockerfile # Docker образ +├── wg/ # WireGuard конфигурация +│ └── start.sh # Скрипт запуска контейнера +├── db/ # SQLite база данных (volume) +├── uploads/ # Загруженные фото (volume) +├── Dockerfile # Multi-stage сборка +├── docker-compose.yml # Конфигурация контейнера +├── install.sh # Установщик (POSIX sh) +├── .dockerignore # Исключения из образа +├── .env.example # Шаблон переменных +└── package.json ``` ---- +## Разработка -### Разработка - -#### Запуск в режиме разработки: ```bash +# Установка зависимостей +npm install + +# Запуск в режиме разработки npm run dev -``` -#### Линтинг и форматирование: -```bash -npm run lint -npm run format -``` - -#### Тестирование: -```bash +# Тесты npm test ``` ---- +## Лицензия -### Лицензия - -Проект распространяется под лицензией MIT. Подробнее см. в файле LICENSE. - ---- - -### Контакты - -По вопросам сотрудничества и поддержки: -- Email: support@telegram-shop.com -- Telegram: @telegram_shop_support - ---- - -### Требования к системе: -1. **Интерфейс пользователя**: - - Интуитивно понятный и удобный интерфейс для покупок. - - Легкость в управлении профилем и кошельками. - - Информация о товарах и статусах покупок должна быть легко доступна. - -2. **Интерфейс администратора**: - - Возможность редактировать товары, категории и управлять локациями. - - Инструменты для контроля баланса и управления пользователями. - - Функционал для создания и загрузки дампов данных. - -3. **Безопасность**: - - Защищенные транзакции. - - Надежная система для хранения данных пользователей и кошельков. - - Механизмы для предотвращения мошенничества и атак. - -4. **Производительность**: - - Система должна быть способна обрабатывать большое количество пользователей и транзакций одновременно. - - Пагинация данных, чтобы обеспечить быструю загрузку и обработку. - ---- - -### Риски и возможные проблемы: -1. **Зависимость от сторонних сервисов**: - - Интеграция с криптокошельками и сторонними сервисами для проверки баланса может быть подвержена сбоям, если эти сервисы не работают корректно. - -2. **Поддержка разных криптовалют**: - - Необходимо следить за изменениями в протоколах криптовалют и своевременно обновлять систему. - -3. **Безопасность и защита данных**: - - Важно следить за актуальностью средств защиты данных и предотвратить утечку информации о пользователях и их балансе. - ---- - -### Заключение: -**Универсальный Телеграмм Магазин** предоставляет эффективное решение для организации торговых процессов в Telegram, с возможностью работы с криптовалютами и традиционными средствами. Проект ориентирован на пользователей, которые ценят удобство, безопасность и скорость совершения покупок. Для администраторов — это мощный инструмент для управления товаром, пользователями и финансовыми потоками магазина. +MIT \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 523aa8e..402f863 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,30 +1,25 @@ -version: "3.3" services: telegram_shop_prod: build: context: . dockerfile: ./Dockerfile - network: host hostname: telegram_shop_prod container_name: telegram_shop_prod ports: - - "3001:3001" - restart: always - env_file: - - .env + - "127.0.0.1:3001:3001" + restart: unless-stopped volumes: - - ./db:/app/db/ # Синхронизация базы данных (persistence) - - ./uploads:/app/uploads/ # Uploaded product photos - - ./wg/start.sh:/app/start.sh # Монтируем start.sh (генерирует wg0.conf из env) - - ./.env:/app/.env:rw # Settings panel read/write - cap_add: # Минимальные привилегии, необходимые только для WireGuard + - ./db:/app/db/ + - ./uploads:/app/uploads/ + - ./.env:/app/.env:ro + cap_add: - NET_ADMIN sysctls: - - net.ipv4.conf.all.src_valid_mark=1 # Необходимо для маршрутизации + - net.ipv4.conf.all.src_valid_mark=1 dns: - 8.8.8.8 - 1.1.1.1 - mem_limit: 512m + mem_limit: 384m cpus: "1.0" healthcheck: test: ["CMD", "curl", "-sf", "http://localhost:3001/health"] @@ -32,5 +27,3 @@ services: timeout: 10s retries: 3 start_period: 60s - networks: - default: diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..4c8e599 --- /dev/null +++ b/install.sh @@ -0,0 +1,274 @@ +#!/bin/sh +set -euo pipefail + +# ============================================================ +# Telegram Shop — установщик +# Скрипт для развёртывания на x86_64 и ARM64 (Orange Pi, RPi) +# Совместим с POSIX sh (Alpine ash) +# ============================================================ + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' + +print_header() { + printf "${CYAN}${BOLD}\n" + printf "╔═══════════════════════════════════════════════════════════════╗\n" + printf "║ Telegram Shop — Установщик v1.1 ║\n" + printf "║ Поддержка x86_64 и ARM64 (Orange Pi, Raspberry Pi) ║\n" + printf "╚═══════════════════════════════════════════════════════════════╝\n" + printf "${NC}\n" +} + +print_ok() { printf "${GREEN}✓${NC} %s\n" "$1"; } +print_warn() { printf "${YELLOW}⚠${NC} %s\n" "$1"; } +print_err() { printf "${RED}✗${NC} %s\n" "$1"; } +print_info() { printf "${BLUE}ℹ${NC} %s\n" "$1"; } +print_step() { printf "\n${CYAN}${BOLD}▶ %s${NC}\n" "$1"; } + +trap 'print_err "Установка прервана ошибкой (строка $LINENO)"; exit 1' ERR + +# ============================================================ +# 0. Определение окружения +# ============================================================ +print_header + +print_step "Определение окружения" +ARCH=$(uname -m) +case "$ARCH" in + x86_64) ARCH_NAME="x86_64 (Intel/AMD)" ;; + aarch64|arm64) ARCH_NAME="ARM64 (Orange Pi / RPi)" ;; + armv7l) ARCH_NAME="ARMv7 (Raspberry Pi 2)" ;; + *) + print_err "Неподдерживаемая архитектура: $ARCH" + exit 1 + ;; +esac +print_ok "Архитектура: $ARCH_NAME" +print_info "Docker автоматически соберёт образ под текущую архитектуру" + +# ============================================================ +# 1. Проверка / установка Docker +# ============================================================ +print_step "Проверка Docker" + +DOCKER_MISSING=0 +if ! command -v docker >/dev/null 2>&1; then + DOCKER_MISSING=1 +elif ! docker version >/dev/null 2>&1; then + DOCKER_MISSING=1 +fi + +if [ "$DOCKER_MISSING" -eq 1 ]; then + print_warn "Docker не установлен или не запущен" + printf "\n" + printf "Установить Docker сейчас? (y/N): " + read -r INSTALL_DOCKER + case "$INSTALL_DOCKER" in + [Yy]|[Yy][Ee][Ss]) + ;; + *) + print_err "Docker обязателен. Установите вручную: https://docs.docker.com/engine/install/" + exit 1 + ;; + esac + + print_info "Установка Docker..." + if command -v apk >/dev/null 2>&1; then + # Alpine + apk add --no-cache docker docker-cli-compose + rc-service docker start 2>/dev/null || service docker start 2>/dev/null || true + if ! docker version >/dev/null 2>&1; then + print_err "Docker не запустился. Запустите вручную: rc-service docker start" + exit 1 + fi + elif command -v apt-get >/dev/null 2>&1; then + # Debian / Ubuntu / Armbian + apt-get update + apt-get install -y ca-certificates curl gnupg + install -m 0755 -d /etc/apt/keyrings + curl -fsSL "https://download.docker.com/linux/$(. /etc/os-release && echo "$ID")/gpg" | gpg --dearmor -o /etc/apt/keyrings/docker.gpg + chmod a+r /etc/apt/keyrings/docker.gpg + printf "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$(. /etc/os-release && echo "$ID") $(. /etc/os-release && echo "$VERSION_CODENAME") stable\n" \ + > /etc/apt/sources.list.d/docker.list + apt-get update + apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin + service docker start 2>/dev/null || systemctl start docker 2>/dev/null || true + if ! docker version >/dev/null 2>&1; then + print_err "Docker не запустился. Запустите вручную: systemctl start docker" + exit 1 + fi + else + print_err "Неизвестный пакетный менеджер. Установите Docker вручную." + exit 1 + fi + print_ok "Docker установлен" +else + print_ok "Docker установлен: $(docker --version)" +fi + +# ============================================================ +# 2. Проверка docker compose +# ============================================================ +print_step "Проверка docker compose" + +COMPOSE_CMD="" +if docker compose version >/dev/null 2>&1; then + COMPOSE_CMD="docker compose" + COMPOSE_VER=$(docker compose version --short 2>/dev/null || echo "v2") + print_ok "docker compose v2 доступен: $COMPOSE_VER" +elif command -v docker-compose >/dev/null 2>&1; then + COMPOSE_CMD="docker-compose" + print_ok "docker-compose v1 доступен: $(docker-compose --version)" +else + print_err "docker compose не найден. Установите docker-compose-plugin или docker-compose." + exit 1 +fi + +# ============================================================ +# 3. Подготовка файлов окружения +# ============================================================ +print_step "Подготовка .env" + +if [ ! -f ".env" ]; then + if [ -f ".env.example" ]; then + cp .env.example .env + # Убираем CRLF если файл скопирован из Windows + if command -v sed >/dev/null 2>&1; then + sed -i 's/\r$//' .env + fi + print_ok ".env создан из .env.example" + else + print_err ".env.example не найден!" + exit 1 + fi +else + print_ok ".env уже существует — пропускаю создание" + # Убираем CRLF в существующем файле тоже + if command -v sed >/dev/null 2>&1; then + sed -i 's/\r$//' .env + fi +fi + +# ============================================================ +# 4. Проверка обязательных переменных +# ============================================================ +print_step "Проверка переменных окружения" + +MISSING_COUNT=0 +MISSING_LIST="" +for VAR in BOT_TOKEN ADMIN_IDS ENCRYPTION_KEY; do + VAL=$(grep -E "^${VAR}=" .env 2>/dev/null | cut -d'=' -f2- | tr -d '"' | tr -d '\r' || true) + if [ -z "$VAL" ] || [ "$VAL" = "your_bot_token_here" ] || [ "$VAL" = "123456789,987654321" ]; then + MISSING_COUNT=$((MISSING_COUNT + 1)) + MISSING_LIST="$MISSING_LIST $VAR" + fi +done + +if [ "$MISSING_COUNT" -ne 0 ]; then + print_warn "Не заполнены или имеют placeholder значения:$MISSING_LIST" + printf "\n" + printf "${YELLOW}Откройте .env в редакторе и заполните:${NC}\n" + for V in $MISSING_LIST; do + printf " - %s\n" "$V" + done + printf "\n" + printf "Продолжить всё равно? (y/N): " + read -r CONTINUE + case "$CONTINUE" in + [Yy]|[Yy][Ee][Ss]) + ;; + *) + print_info "Откройте .env, заполните значения и запустите install.sh снова." + exit 0 + ;; + esac +else + print_ok "Все обязательные переменные заполнены" +fi + +# ============================================================ +# 5. Создание директорий +# ============================================================ +print_step "Создание директорий" +mkdir -p db uploads +print_ok "db/, uploads/ готовы" + +# ============================================================ +# 6. Сборка образа +# ============================================================ +print_step "Сборка Docker-образа (это может занять несколько минут)" +print_info "Архитектура: $ARCH_NAME" +print_info "Нативные модули: better-sqlite3, tiny-secp256k1 (компилируются в builder)" + +# Docker автоматически собирает под текущую архитектуру хоста +# buildx нужен только для multi-arch push в registry, для локальной сборки — обычный build +docker build -t telegram-shop:latest . +print_ok "Образ telegram-shop:latest собран" + +# ============================================================ +# 7. Запуск контейнеров +# ============================================================ +print_step "Запуск контейнеров" +$COMPOSE_CMD up -d +print_ok "Контейнеры запущены" + +# ============================================================ +# 8. Проверка статуса +# ============================================================ +print_step "Проверка статуса" +sleep 3 +$COMPOSE_CMD ps + +# ============================================================ +# 9. Показ логов +# ============================================================ +print_step "Последние логи" +$COMPOSE_CMD logs --tail=20 + +# ============================================================ +# 10. Health-check с повторными попытками +# ============================================================ +print_step "Проверка работоспособности" +HEALTH_URL="http://localhost:3001/health" +print_info "Запрос: $HEALTH_URL" +HEALTH_OK=0 +for ATTEMPT in 1 2 3 4 5 6; do + sleep 5 + if curl -sf "$HEALTH_URL" >/dev/null 2>&1; then + HEALTH_OK=1 + break + fi + print_info "Попытка $ATTEMPT/6 — сервис ещё запускается..." +done + +if [ "$HEALTH_OK" -eq 1 ]; then + RESPONSE=$(curl -sf "$HEALTH_URL" 2>/dev/null || echo "ok") + print_ok "Сервис отвечает!" + printf " ${GREEN}Ответ: %s${NC}\n" "$RESPONSE" +else + print_warn "Health-check не ответил за 30 секунд. Проверьте логи:" + printf " curl %s\n" "$HEALTH_URL" + printf " %s logs -f\n" "$COMPOSE_CMD" +fi + +# ============================================================ +# Готово +# ============================================================ +printf "\n" +printf "${GREEN}${BOLD}╔═══════════════════════════════════════════════════════════════╗${NC}\n" +printf "${GREEN}${BOLD}║ Установка завершена! ║${NC}\n" +printf "${GREEN}${BOLD}╚═══════════════════════════════════════════════════════════════╝${NC}\n" +printf "\n" +printf "${BOLD}Полезные команды:${NC}\n" +printf " docker compose ps # статус контейнеров\n" +printf " docker compose logs -f # логи в реальном времени\n" +printf " docker compose restart # перезапустить\n" +printf " docker compose down # остановить\n" +printf " docker compose up -d --build # пересобрать и запустить\n" +printf "\n" +printf "${BOLD}Health-check:${NC} %s\n" "$HEALTH_URL" \ No newline at end of file