Files
telegram-shop/install.sh
NW 94300c7d35 fix: photo URLs use ADMIN_URL prefix for Telegram API + resilience improvements
- productHandler, purchaseHandler, viewHandler: prefix relative photo_url
  with ADMIN_URL so Telegram can fetch images via public URL
- bot.js: 5 retries with 5s delay on init, graceful fallback to null
- errorHandler.js: 5 retries on 404 (invalid token), stops polling
  but keeps process alive for admin panel
- config.js: BOT_TOKEN missing logs warning instead of process.exit
- index.js: bot handlers only registered when bot is available,
  admin panel always starts regardless of bot status
- adminWalletsHandler.js: replace throw with logger.warn for missing
  commission wallets (prevents container crash on startup)
- docker-compose.yml: bind admin port to all interfaces (0.0.0.0)
- README.md: updated with Tor proxy architecture, resilience docs
- install.sh: added Tor proxy status check and onion address display
2026-06-24 19:32:49 +01:00

313 lines
13 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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"