feat: multi-architecture Docker setup (x86_64 + ARM64) with one-command install

- Multi-stage Dockerfile: builder compiles native modules (better-sqlite3,
  tiny-secp256k1) under target architecture, runtime is minimal Alpine
- install.sh: POSIX sh installer (Alpine ash compatible) with architecture
  detection, Docker install, .env validation, health-check retry loop
- docker-compose.yml: removed platform locks, .env read-only mount,
  127.0.0.1 port binding, 384m mem limit (Orange Pi Zero 2 safe)
- .dockerignore: excludes node_modules, secrets, tests, .kilo
- README.md: complete rewrite with deployment docs for any device
- Verified: POSIX sh syntax (dash), Dockerfile (docker build --check),
  docker-compose (docker compose config)
This commit is contained in:
NW
2026-06-24 02:06:07 +01:00
parent 293236921c
commit 4aea49811c
5 changed files with 488 additions and 170 deletions

274
install.sh Executable file
View File

@@ -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"