Загрузка данных
#!/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"