#!/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 # ============================================================ # 9. Tor Proxy — onion-адреса # ============================================================ printf "\n" print_step "Проверка Tor-прокси" TOR_RUNNING=0 for ATTEMPT in 1 2 3 4 5 6; do sleep 5 if docker exec tor-proxy test -s /var/lib/tor/ssh/hostname 2>/dev/null && \ docker exec tor-proxy test -s /var/lib/tor/admin/hostname 2>/dev/null; then TOR_RUNNING=1 break fi print_info "Попытка $ATTEMPT/6 — Tor генерирует onion-адреса..." done if [ "$TOR_RUNNING" -eq 1 ]; then SSH_ONION=$(docker exec tor-proxy cat /var/lib/tor/ssh/hostname 2>/dev/null || echo "") ADMIN_ONION=$(docker exec tor-proxy cat /var/lib/tor/admin/hostname 2>/dev/null || echo "") if [ -n "$SSH_ONION" ] && [ -n "$ADMIN_ONION" ]; then print_ok "Tor-прокси работает!" printf "\n ${CYAN}${BOLD}SSH onion:${NC} %s\n" "$SSH_ONION" printf " ${CYAN}${BOLD}Admin onion:${NC} %s\n" "$ADMIN_ONION" printf "\n ${BOLD}Подключение:${NC}\n" printf " SSH: torify ssh root@%s\n" "$SSH_ONION" printf " Admin: откройте http://%s в Tor Browser\n" "$ADMIN_ONION" if [ -f "./tor-proxy/get-onions.sh" ]; then sh ./tor-proxy/get-onions.sh 2>/dev/null || true fi else print_warn "Tor запущен, но onion-адреса не найдены" fi else print_warn "Tor-прокси не стартовал за 30 секунд. Проверьте логи:" printf " docker logs tor-proxy\n" 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"