Загрузка данных


#!/usr/bin/env bash
set -uo pipefail

# ОСнова (ОСОн, НППКТ) 2.14 — ФАЙЛ 2/2: сборка Python 3.9.6 и полное локальное окружение.
# Запускать ПОСЛЕ osnova_base_disk.sh (сеть + apt с дисков + python3.9 уже есть).
#
# ПОЧЕМУ ИМЕННО СБОРКА 3.9.6 (а не готовый 3.9.23 с дисков):
#   тесты требуют ровно 3.9.6 (требование Виталия). Собираем через pyenv, как на Астре.
#   Бонусом это решает и проблему venv/pip: pyenv-Python несёт ensurepip/venv в комплекте,
#   а урезанный дисковый python3.9 их не имеет.
#
# ⚠️ BUILD-DEPS И ПАКЕТНАЯ БАЗА (осознанный компромисс):
#   ОСнова 2.14 — «франкендебиан»: libc уровня buster (2.28), libssl уже bullseye (1.1.1w),
#   часть библиотек пересобрана с суффиксом .osnova1. Компилятора и -dev на БАЗОВЫХ дисках
#   (disk1/disk2) НЕТ, поэтому для сборки 3.9.6 их берём из Debian-репозитория (сеть есть).
#   Это СЛЕГКА перетряхивает базу (apt заменит пару osnova-библиотек дебиановскими).
#   Полностью чистый путь — devel-диск НППКТ (там toolchain под osnova-версии): если он
#   появится, задай USE_DEVEL_DISK=yes — тогда Debian-репо не подключаются (см. §3).
#
# Запуск ПОД ОБЫЧНЫМ пользователем:
#     bash osnova_local_setup.sh
#   с git-кредами:  GIT_USER=login GIT_PASS=token bash osnova_local_setup.sh

TESTER="${TESTER:-$(id -un)}"
PYVER="${PYVER:-3.9.6}"                      # ⚠️ ровно 3.9.6 (требование тестов)
PROJECT_GIT="${PROJECT_GIT:-https://scm.aktivco.ru/rutoken/dev/qa-dev/rtlogon_pytests.git}"
PROJECT_DIR="${PROJECT_DIR:-$HOME/rtlogon_pytests}"
PROJECT_REF="${PROJECT_REF:-}"
GIT_USER="${GIT_USER:-}"
GIT_PASS="${GIT_PASS:-}"
PIP_INDEX="${PIP_INDEX:-}"                   # пусто = pypi.org; или внутренний nexus (…/simple)
PRODUCT_URL="${PRODUCT_URL:-https://builds.aktivco.ru/new/unilogon-production/unilogon-production-143a6b8d5cc6b3527afe3ba0fcbeaa464f4e60bf.zip}"
PRODUCT_ZIP="${PRODUCT_ZIP:-}"
PRODUCT_DEB="${PRODUCT_DEB:-}"
ALLURE_VER="${ALLURE_VER:-2.29.0}"
JAVA_PKG="${JAVA_PKG:-default-jre}"
USE_DEVEL_DISK="${USE_DEVEL_DISK:-no}"      # yes = build-deps с devel-диска, Debian-репо НЕ трогаем
NPROC_MIN="${NPROC_MIN:-16384}"
MAKE_OPTS="${MAKE_OPTS:--j4}"; export MAKE_OPTS

SCRIPT_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd || echo "$PWD")"
export DEBIAN_FRONTEND=noninteractive
ok(){ echo "  [OK]   $*"; }
act(){ echo "  [DO]   $*"; }
warn(){ echo "  [WARN] $*"; }
die(){ echo "ОШИБКА: $*" >&2; exit 1; }
have(){ command -v "$1" >/dev/null 2>&1; }
apt_get(){ sudo DEBIAN_FRONTEND=noninteractive apt-get "$@" </dev/null; }
urlenc(){ local s="$1" o="" c i; for ((i=0;i<${#s};i++)); do c="${s:$i:1}"; case "$c" in [a-zA-Z0-9.~_-]) o+="$c";; *) printf -v c '%%%02X' "'$c"; o+="$c";; esac; done; printf '%s' "$o"; }
PIPIDX(){ [ -n "$PIP_INDEX" ] && printf -- '--index-url %s' "$PIP_INDEX"; }

. /etc/os-release 2>/dev/null || true
echo "=== ОСнова local-setup (сборка Python $PYVER + окружение тестов) ==="
echo "  система: ${PRETTY_NAME:-?} (VERSION_ID=${VERSION_ID:-?}), ядро $(uname -r)"
[ "$(id -u)" -ne 0 ] || die "Запусти под обычным пользователем ($TESTER), НЕ root — pyenv/venv в \$HOME"
sudo -v || die "нужен sudo"
have git || die "нет git — сначала отработай osnova_base_disk.sh (§4 ставит его с дисков)"

echo "== 0. sudo NOPASSWD (для тестов) =="
sudo -k 2>/dev/null || true
if sudo -n true 2>/dev/null; then ok "NOPASSWD sudo есть"
else
  act "настраиваю NOPASSWD"
  echo "$TESTER ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/$TESTER >/dev/null
  sudo chmod 0440 /etc/sudoers.d/$TESTER
  sudo -n true 2>/dev/null && ok "NOPASSWD настроен" || warn "NOPASSWD не поднялся — тесты будут спрашивать пароль"
fi

echo "== 0.5 ЗПС =="
if [ -L /etc/ima/policy ] && [ "$(readlink /etc/ima/policy)" = /etc/ima/policy.d/appraise ]; then
  warn "ЗПС включена (policy→appraise) — pyenv-Python/venv не запустятся!"
  warn "Выключи: sudo rm /etc/ima/policy; sudo ln -s /etc/ima/policy.d/empty /etc/ima/policy; sudo update-initramfs -u -k all; sudo reboot"
  die "ЗПС enforce — сборка/тесты невозможны без выключения (или подписи, см. README §ЗПС)"
else
  ok "ЗПС не в enforce (policy→$( [ -L /etc/ima/policy ] && readlink /etc/ima/policy || echo 'нет симлинка' ))"
fi

echo "== 1. лимит nproc =="
[ -f /etc/security/limits.d/90-nproc.conf ] || echo "$TESTER - nproc $NPROC_MIN" | sudo tee /etc/security/limits.d/90-nproc.conf >/dev/null
CUR="$(ulimit -u)"; case "$CUR" in unlimited) CUR=999999999;; esac
[ "$CUR" -ge "$NPROC_MIN" ] && ok "ulimit -u=$CUR" || { ulimit -u "$NPROC_MIN" 2>/dev/null && ok "ulimit -u поднят" || { warn "перелогинься после прогона"; NEED_RELOGIN=1; }; }

echo "== 2. права \$HOME =="
BAD=""
for f in .ICEauthority .Xauthority .config .cache .dbus .local .pyenv; do
  p="$HOME/$f"; [ -e "$p" ] && [ "$(stat -c %U "$p" 2>/dev/null)" != "$TESTER" ] && BAD="$BAD $f"
done
[ -n "$BAD" ] && { warn "не от $TESTER:$BAD — чиню"; sudo chown -R "$TESTER:$(id -gn)" "$HOME"; } || ok "владелец \$HOME ок"

echo "== 3. build-deps для сборки $PYVER =="
# Компилятора и -dev на базовых дисках нет. Тестовые C-зависимости (cffi/cryptography/
# psutil/pydantic_core) приезжают ГОТОВЫМИ wheel'ами с PyPI (§8) — их компилировать НЕ надо.
# Значит нам нужен toolchain только для сборки самого CPython: gcc/make + ssl/zlib/ffi + опц.
if [ "$USE_DEVEL_DISK" = yes ]; then
  act "USE_DEVEL_DISK=yes — ставлю build-deps с дисков (нужен подключённый devel-диск)"
  apt_get update
  apt_get install -y gcc g++ make libc6-dev libssl-dev zlib1g-dev libffi-dev \
    libbz2-dev libreadline-dev libsqlite3-dev liblzma-dev libncurses-dev uuid-dev tk-dev \
    || warn "часть -dev не встала с дисков — на devel-диске проверь наличие пакетов"
else
  # Debian bullseye лучше всего совпадает с osnova (libssl 1.1.1w = bullseye). buster даёт
  # libc-заголовки под osnova libc 2.28. Подключаем оба из архива (Release просрочен → off).
  act "подключаю Debian-репозитории (bullseye+buster) для toolchain"
  { echo "deb http://archive.debian.org/debian bullseye main"
    echo "deb http://security.debian.org/debian-security bullseye-security main"
  } | sudo tee /etc/apt/sources.list.d/zz-debian-bullseye.list >/dev/null
  echo "deb http://archive.debian.org/debian buster main" | sudo tee /etc/apt/sources.list.d/zz-debian-buster.list >/dev/null
  echo 'Acquire::Check-Valid-Until "false";' | sudo tee /etc/apt/apt.conf.d/99-archive-valid-until >/dev/null
  # ОСнова прописывает APT::Default-Release "onyx" — с добавленными репо релиз onyx всё ещё
  # есть на дисках, так что НЕ трогаем. libc-вопрос дебконфа предотвращаем заранее.
  echo 'libc6 libraries/restart-without-asking boolean true' | sudo debconf-set-selections 2>/dev/null || true
  apt_get update || warn "apt update с ошибкой"
  # ставим по одному (сломанная зависимость не топит остальные); критичны ssl/zlib/ffi
  act "ставлю toolchain (gcc/make) и -dev по одному"
  apt_get install -y build-essential || apt_get install -y gcc g++ make libc6-dev \
    || warn "toolchain не встал — если из-за libc6/gegl: sudo apt-get install -y gcc g++ make libc6-dev libgegl-0.4-0"
  MISS=""
  for p in libssl-dev zlib1g-dev libffi-dev libbz2-dev libreadline-dev libsqlite3-dev liblzma-dev uuid-dev tk-dev; do
    dpkg -s "$p" >/dev/null 2>&1 && { ok "$p (стоит)"; continue; }
    apt_get install -y "$p" >/dev/null 2>&1 && ok "$p" || { warn "$p не встал"; MISS="$MISS $p"; }
  done
  dpkg -s libncurses-dev >/dev/null 2>&1 || dpkg -s libncurses5-dev >/dev/null 2>&1 || \
    apt_get install -y libncurses-dev >/dev/null 2>&1 || apt_get install -y libncurses5-dev libncursesw5-dev >/dev/null 2>&1 || MISS="$MISS libncurses-dev"
  [ -n "$MISS" ] && warn "не встали (опциональные модули Python):$MISS"
fi
for t in gcc make; do have "$t" || die "«$t» нет — без него $PYVER не собрать (см. §3, build-deps)"; done
for p in libssl-dev zlib1g-dev libffi-dev; do
  dpkg -s "$p" >/dev/null 2>&1 || die "критичный $p не установлен — без него Python/pip нерабочи (ssl/pip/ctypes)"
done
ok "build-deps готовы"

echo "== 4. pyenv =="
if [ -d "$HOME/.pyenv" ]; then ok "pyenv есть"
else git clone https://github.com/pyenv/pyenv.git "$HOME/.pyenv" && act "pyenv склонирован (github доступен)"; fi
# без 'eval pyenv init' в .bashrc (форк-бомба на закалённых стендах) — только ROOT+PATH шимов
grep -q 'pyenv init' "$HOME/.bashrc" 2>/dev/null && { sed -i '/pyenv init/d' "$HOME/.bashrc"; warn "убрал eval pyenv init из ~/.bashrc"; }
grep -q 'PYENV_ROOT' "$HOME/.bashrc" 2>/dev/null || { echo 'export PYENV_ROOT="$HOME/.pyenv"'; echo 'export PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH"'; } >> "$HOME/.bashrc"
export PYENV_ROOT="$HOME/.pyenv"; export PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH"

echo "== 5. сборка Python $PYVER =="
# osnova несёт OpenSSL 1.1.1 → 3.9.6 собирается штатно (в отличие от OpenSSL 3.x, где нужен 3.9.20).
if pyenv versions --bare 2>/dev/null | grep -qx "$PYVER"; then ok "Python $PYVER уже собран"
else act "сборка $PYVER (MAKE_OPTS=$MAKE_OPTS, ~2–4 мин)"; pyenv install -s "$PYVER" \
  || die "Python $PYVER не собрался — смотри WARNING (нехватка -dev? проверь §3). ssl/zlib/ffi обязательны."; fi
pyenv rehash 2>/dev/null || true

echo "== 6. проект =="
export GIT_TERMINAL_PROMPT=1
if [ ! -d "$PROJECT_DIR/.git" ]; then
  # Креды можно задать заранее (GIT_USER=.. GIT_PASS=..); если не заданы — спросим прямо тут.
  # Читаем с /dev/tty, чтобы работало даже когда stdin скрипта перенаправлен.
  if [ -z "$GIT_USER" ]; then
    printf "  GitLab логин для %s: " "${PROJECT_GIT#https://}" > /dev/tty
    read -r GIT_USER < /dev/tty || true
  fi
  if [ -z "$GIT_PASS" ] && [ -n "$GIT_USER" ]; then
    printf "  Пароль/токен для %s (ввод скрыт): " "$GIT_USER" > /dev/tty
    read -rs GIT_PASS < /dev/tty || true; echo > /dev/tty
  fi
  CLONE_URL="$PROJECT_GIT"
  if [ -n "$GIT_USER" ]; then
    CLONE_URL="https://$(urlenc "$GIT_USER")@${PROJECT_GIT#https://}"
    if [ -n "$GIT_PASS" ]; then AP="$(mktemp)"; printf '#!/bin/sh\nprintf %%s %q\n' "$GIT_PASS" > "$AP"; chmod +x "$AP"; export GIT_ASKPASS="$AP"; fi
  else warn "логин не введён — git спросит креды сам (или перезапусти и введи)"; fi
  git clone "$CLONE_URL" "$PROJECT_DIR" || die "git clone не прошёл (неверные логин/пароль или нет доступа к $PROJECT_GIT)"
  [ -n "${AP:-}" ] && rm -f "$AP"; unset GIT_PASS; act "проект склонирован"
fi
cd "$PROJECT_DIR" || die "нет $PROJECT_DIR"
[ -n "$PROJECT_REF" ] && git checkout "$PROJECT_REF"
ok "$PROJECT_DIR"

echo "== 7. venv ($PYVER) + requirements =="
pyenv local "$PYVER"
[ -d venv ] || python -m venv venv          # pyenv-Python несёт ensurepip → venv штатный
source venv/bin/activate
python -m pip install -q $(PIPIDX) --upgrade pip
python -m pip install $(PIPIDX) -r requirements.txt \
  || warn "pip install -r requirements.txt с ошибкой (сеть до PyPI/nexus? имя пакета?)"
ok "venv готов ($(python --version 2>&1))"

echo "== 8. SDK (download_deps.py, с builds.aktivco.ru) =="
python download_deps.py || warn "download_deps.py не отработал (доступ к builds.aktivco.ru?)"

echo "== 9. продукт rtlogon (.deb) =="
if dpkg -l 2>/dev/null | grep -qiE 'rutokenlogon|unilogon'; then
  ok "уже установлен: $(dpkg -l | grep -iE 'rutokenlogon|unilogon' | awk '{print $2"="$3}' | head -1)"
else
  DEB="$PRODUCT_DEB"; ZIP="$PRODUCT_ZIP"
  [ -z "$ZIP" ] && ZIP="$(ls "$SCRIPT_DIR"/unilogon-production-*.zip "$PWD"/unilogon-production-*.zip 2>/dev/null | head -1)"
  if [ -z "$DEB" ] && [ -z "$ZIP" ]; then
    TMP="$(mktemp -d)"; act "качаю продукт $PRODUCT_URL"
    wget -q "$PRODUCT_URL" -O "$TMP/p.zip" && ZIP="$TMP/p.zip" || warn "zip не скачался — положи рядом или задай PRODUCT_DEB="
  fi
  if [ -z "$DEB" ] && [ -n "$ZIP" ]; then
    act "распаковываю $(basename "$ZIP")"; EX="$(mktemp -d)"; unzip -q "$ZIP" -d "$EX"
    find "$EX" -maxdepth 4 \( -name '*.tar.gz' -o -name '*.tgz' -o -name '*.tar' -o -name '*.zip' \) \
      -exec sh -c 'd=$(dirname "$1"); case "$1" in *.zip) unzip -qo "$1" -d "$d";; *) tar -xf "$1" -C "$d";; esac' _ {} \; 2>/dev/null
    ARCH="$(dpkg --print-architecture 2>/dev/null || echo amd64)"
    DEB="$(find "$EX" -name "*_${ARCH}.deb" | grep -viE 'astra|alt|grdcontrol|guardant' | head -1)"
    [ -z "$DEB" ] && DEB="$(find "$EX" -name '*.deb' | grep -viE 'astra|alt|grdcontrol|guardant|arm64|i386' | head -1)"
  fi
  if [ -n "$DEB" ] && [ -f "$DEB" ]; then
    act "ставлю $(basename "$DEB") (KDE Plasma → активный DM sddm; lightdm приедет зависимостью с дисков)"
    apt_get install -y "$DEB" || { sudo dpkg -i "$DEB"; apt_get -f install -y; } || warn "deb не встал — доустанови зависимости"
    dpkg -s lightdm >/dev/null 2>&1 || warn "lightdm не установлен — тесты --use-system-gui no по /etc/lightdm скипнутся"
  else warn "debian-family .deb не найден — задай PRODUCT_DEB=путь"; fi
fi
CLI="$(ls /opt/aktivco/*/app/*logon-cli 2>/dev/null | head -1)"; [ -n "$CLI" ] && "$CLI" --version 2>/dev/null && ok "cli: $CLI"

echo "== 10. Allure =="
if have allure; then ok "allure: $(allure --version 2>/dev/null)"
else
  TGZ="allure-commandline-${ALLURE_VER}.tgz"
  ( cd /tmp && wget -q "https://repo1.maven.org/maven2/io/qameta/allure/allure-commandline/${ALLURE_VER}/${TGZ}" \
      && sudo tar -zxf "$TGZ" -C /opt/ && sudo ln -sf "/opt/allure-${ALLURE_VER}/bin/allure" /usr/local/bin/allure ) \
    && { act "Allure $ALLURE_VER"; allure --version 2>/dev/null || warn "allure не стартовал (java?)"; } \
    || warn "Allure не скачался — поставь вручную"
fi

echo
echo "=== Готово ==="
[ "${NEED_RELOGIN:-0}" = 1 ] && echo "ПЕРЕЛОГИНЬСЯ (или reboot) — поднимется nproc."
echo "Локальные тесты:  cd $PROJECT_DIR && source venv/bin/activate && python -m pytest -m local"
echo "Доменные:         sudo bash osnova_ad_join.sh | sudo bash osnova_freeipa_join.sh"