#!/usr/bin/env bash # Установка API аккаунтов на Debian 12/13 без Docker: python3 из пакетов + служба systemd. # Сначала поставьте сайт: sudo ./deploy/install-debian.sh # # sudo PUBLIC_URL=https://trainer.example.com \ # SMTP_HOST=smtp.example.com SMTP_USER=noreply@example.com SMTP_PASSWORD='...' \ # ./deploy/install-api-debian.sh # # Первый запуск создаёт /etc/split-trainer/api.env из переданных переменных. Повторный запуск обновляет код и # перезапускает службу, а файл настроек не трогает (меняйте его вручную и перезапускайте: systemctl restart split-trainer-api). # Пока SMTP не настроен, а сайт публичный, API не стартует (так задумано): укажите SMTP_HOST или MAIL_BACKEND=console для проверки. set -euo pipefail APP_DIR="/opt/split-trainer/api" ENV_DIR="/etc/split-trainer" ENV_FILE="$ENV_DIR/api.env" UNIT="/etc/systemd/system/split-trainer-api.service" API_PORT="${API_PORT:-8000}" die() { echo "Ошибка: $*" >&2; exit 1; } [ "$(id -u)" -eq 0 ] || die "запустите от root: sudo $0" [[ "$API_PORT" =~ ^[0-9]{1,5}$ ]] || die "некорректный API_PORT: $API_PORT" HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SRC="$HERE/../server/splitapi" [ -f "$SRC/__main__.py" ] || die "не найден $SRC: запускайте скрипт из клонированного репозитория" . /etc/os-release if [ "${ID:-}" != "debian" ] || { [ "${VERSION_ID:-}" != "12" ] && [ "${VERSION_ID:-}" != "13" ]; }; then [ "${FORCE:-0}" = "1" ] || die "скрипт рассчитан на Debian 12/13 (у вас: ${PRETTY_NAME:-неизвестно}). Чтобы всё равно продолжить: FORCE=1" fi echo "==> Проверяю python3" export DEBIAN_FRONTEND=noninteractive command -v python3 >/dev/null 2>&1 || { apt-get update -qq; apt-get install -y -qq --no-install-recommends python3 >/dev/null; } command -v curl >/dev/null 2>&1 || { apt-get update -qq; apt-get install -y -qq --no-install-recommends curl >/dev/null; } # для проверки в конце python3 -c 'import sys; sys.exit(0 if sys.version_info >= (3, 11) else 1)' || die "нужен Python 3.11 или новее" python3 -c 'import hashlib, sqlite3; hashlib.scrypt' >/dev/null 2>&1 || die "в python3 нет sqlite3 или scrypt (установите пакет python3 целиком)" echo "==> Копирую код в $APP_DIR" install -d -m 755 "$APP_DIR" rm -rf "$APP_DIR/splitapi" # наш собственный каталог: заменяем целиком cp -r "$SRC" "$APP_DIR/splitapi" find "$APP_DIR/splitapi" -name '__pycache__' -type d -prune -exec rm -rf {} + chown -R root:root "$APP_DIR" chmod -R go-w "$APP_DIR" # Значение переменной в формате EnvironmentFile systemd: в двойных кавычках, \ и " экранируются, перевод строки недопустим env_line() { local name="$1" value="$2" [[ "$value" != *$'\n'* && "$value" != *$'\r'* ]] || die "в значении $name не должно быть перевода строки" printf '%s="%s"\n' "$name" "$(printf '%s' "$value" | sed -e 's/[\\"]/\\&/g')" } if [ -f "$ENV_FILE" ]; then echo "==> $ENV_FILE уже есть: не меняю" else [ -n "${PUBLIC_URL:-}" ] || die "задайте PUBLIC_URL (внешний адрес сайта), например PUBLIC_URL=https://trainer.example.com" echo "==> Создаю $ENV_FILE (права 0600: в нём может быть пароль почты)" install -d -m 755 "$ENV_DIR" umask 077 { env_line PUBLIC_URL "$PUBLIC_URL" env_line BIND "127.0.0.1" env_line PORT "$API_PORT" env_line TRUST_PROXY "1" env_line DB_PATH "/var/lib/split-trainer/splitapi.sqlite3" for name in MAIL_BACKEND MAIL_FROM SMTP_HOST SMTP_PORT SMTP_SECURITY SMTP_USER SMTP_PASSWORD ALLOW_REGISTRATION SESSION_DAYS; do if [ -n "${!name:-}" ]; then env_line "$name" "${!name}"; fi done } > "$ENV_FILE" chmod 600 "$ENV_FILE" chown root:root "$ENV_FILE" fi echo "==> Настраиваю службу systemd" cat > "$UNIT" <<'UNIT_EOF' [Unit] Description=Сплит-тренер: API аккаунтов After=network.target [Service] Type=simple WorkingDirectory=/opt/split-trainer/api EnvironmentFile=/etc/split-trainer/api.env Environment=PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1 ExecStart=/usr/bin/python3 -m splitapi Restart=on-failure RestartSec=3 # Без постоянного пользователя и почти без прав DynamicUser=yes StateDirectory=split-trainer StateDirectoryMode=0700 NoNewPrivileges=yes CapabilityBoundingSet= ProtectSystem=strict ProtectHome=yes PrivateTmp=yes PrivateDevices=yes ProtectKernelTunables=yes ProtectKernelModules=yes ProtectControlGroups=yes RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX ProtectClock=yes ProtectHostname=yes ProtectProc=invisible ProcSubset=pid RestrictNamespaces=yes RestrictSUIDSGID=yes LockPersonality=yes UMask=0077 SystemCallArchitectures=native SystemCallFilter=@system-service MemoryMax=384M TasksMax=64 [Install] WantedBy=multi-user.target UNIT_EOF chmod 644 "$UNIT" systemctl daemon-reload systemctl enable split-trainer-api >/dev/null 2>&1 systemctl restart split-trainer-api echo "==> Проверяю, что API отвечает" PORT_IN_USE="$(sed -n 's/^PORT="\([0-9]*\)"$/\1/p' "$ENV_FILE" | head -n1)" PORT_IN_USE="${PORT_IN_USE:-$API_PORT}" for _ in $(seq 1 20); do if curl -fsS -m 3 -o /dev/null "http://127.0.0.1:$PORT_IN_USE/api/health" 2>/dev/null; then echo "==> Готово: API слушает 127.0.0.1:$PORT_IN_USE. Сайт обращается к нему через nginx (install-debian.sh, API_UPSTREAM)." exit 0 fi sleep 1 done echo "API не ответил за 20 секунд. Последние строки журнала:" >&2 journalctl -u split-trainer-api -n 20 --no-pager >&2 || true exit 1