Загрузка данных
#!/usr/bin/env bash
set -uo pipefail
# ОСнова (ОСОн, АО «НППКТ») 2.14 / 3.1 — local-setup «всё в одном», идемпотентно.
#
# ⚠️ ГЛАВНЫЙ ПРИНЦИП: пакеты берём ТОЛЬКО из РОДНЫХ установочных дисков ОСновы
# (disk1/disk2/devel — те, что выдал вендор и что примонтированы в Proxmox как
# CD/DVD). НИКАКИХ Debian-репозиториев: они тянут чужие libc/libssl/zlib других
# версий → ломают пакетную базу ОСновы и не имеют подписей ЗПС. Всё, что нужно
# для стенда, есть на дисках; если пакета на смонтированных дисках нет — это
# сигнал подключить нужный диск (обычно «Комплект разработчика»), а НЕ тянуть
# из Debian.
#
# Запуск ПОД ОБЫЧНЫМ пользователем (НЕ root — pyenv/venv ставятся в $HOME):
# bash osnova_local_setup.sh
#
# ЧТО ТАКОЕ ОСНОВА (важно для скрипта):
# - дебиан-подобная: apt/dpkg, обычные *_amd64.deb; 2.14 — база Debian 10 (onyx,
# OpenSSL 1.1.1 → Python 3.9.6 собирается), 3.1 — база Debian 12 (onyx3, OpenSSL 3.x
# → §6 сам выберет PYVER=3.9.20);
# - рабочий стол KDE Plasma → активный DM = sddm; продукту lightdm нужен только
# УСТАНОВЛЕННЫМ (приедет зависимостью .deb) — §10;
# - ⚠️ ЗПС на базе IMA подписывает бинарники И скрипты: при enforce pyenv-Python/venv
# и наши скрипты не запустятся (§0.5 проверяет, на стенде ЗПС выключить);
# - репозиторий = установочные диски (§3). Диски вендор отдал как ISO — в Proxmox
# они подключены как CD/DVD (ide0/ide2 → /dev/sr0,/dev/sr1 в госте).
TESTER="${TESTER:-$(id -un)}"
PYVER="${PYVER:-}" # пусто = автовыбор по OpenSSL (§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:-}"
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}"
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 под sudo: sudo сбрасывает окружение (env_reset), поэтому DEBIAN_FRONTEND передаём
# ЯВНО в каждом вызове, а stdin подрезаем (</dev/null) — чтобы дебконф-вопрос не повесил прогон.
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"; }
. /etc/os-release 2>/dev/null || true
echo "=== ОСнова local-setup (репозиторий = установочные диски, без Debian-репо) ==="
echo " система: ${PRETTY_NAME:-?} (VERSION_ID=${VERSION_ID:-?}), ядро $(uname -r)"
case "${VERSION_ID:-}" in
2*) ok "линейка 2.x (2.14, onyx: база Debian 10, OpenSSL 1.1.1)";;
3*) ok "линейка 3.x (3.1, onyx3: база Debian 12, OpenSSL 3.x)";;
*) warn "VERSION_ID='${VERSION_ID:-}' не 2.x/3.x — продолжаю, различия решает OpenSSL (§6)";;
esac
[ "$(id -u)" -ne 0 ] || die "Запусти под обычным пользователем ($TESTER), НЕ root — pyenv/venv ставятся в \$HOME"
echo "== 0. sudo / привилегии =="
sudo -k 2>/dev/null || true
if sudo -n true 2>/dev/null; then
ok "NOPASSWD sudo уже есть"
else
act "NOPASSWD sudo не настроен — поднимаю права"
BOOT="$(mktemp)"
cat > "$BOOT" <<EOF
usermod -aG sudo $TESTER
[ -d /etc/sudoers.d ] || install -d -m 0755 /etc/sudoers.d
grep -qE '^[@#]includedir /etc/sudoers.d' /etc/sudoers || echo '@includedir /etc/sudoers.d' >> /etc/sudoers
echo '$TESTER ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/$TESTER
chmod 0440 /etc/sudoers.d/$TESTER
echo '$TESTER - nproc $NPROC_MIN' > /etc/security/limits.d/90-nproc.conf
EOF
chmod 0644 "$BOOT"
if command -v sudo >/dev/null 2>&1 && sudo -v 2>/dev/null && sudo bash "$BOOT"; then
ok "права подняты через sudo (пароль $TESTER)"
elif su - root -c "bash '$BOOT'"; then
ok "права подняты через su root (пароль root)"
else
rm -f "$BOOT"; die "не поднять права: ни sudo, ни su root не сработали. Добавь $TESTER в sudo вручную (от root: usermod -aG sudo $TESTER)"
fi
rm -f "$BOOT"
sudo -n true 2>/dev/null || die "sudo NOPASSWD всё ещё не работает — открой НОВЫЙ терминал (группа sudo подхватится при логине) и проверь: sudo -n true"
ok "sudo NOPASSWD настроен"
fi
echo "== 0.5 ЗПС/IMA и NESS (специфика ОСновы) =="
if grep -qE 'ima_appraise=(enforce|fix)' /proc/cmdline 2>/dev/null; then
warn "в cmdline ядра ima_appraise=$(grep -oE 'ima_appraise=[^ ]*' /proc/cmdline) — ЗПС активна!"
warn "pyenv-Python/venv и тестовые скрипты НЕ ЗАПУСТЯТСЯ. Выключи ЗПС на время тестов"
warn "(документация НППКТ, раздел ЗПС) — иначе прогон не поедет."
elif sudo test -s /sys/kernel/security/ima/policy 2>/dev/null; then
warn "IMA-policy непустая — проверь режим ЗПС (audit ок, enforce — нет)"
else
ok "ЗПС/IMA не активна (appraise не форсится)"
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
if [ "$CUR" -lt "$NPROC_MIN" ]; then
if ulimit -u "$NPROC_MIN" 2>/dev/null; then ok "ulimit -u поднят до $(ulimit -u) на сессию"
else warn "ulimit -u=$CUR, не поднять live — перелогинься после прогона"; NEED_RELOGIN=1; fi
else ok "ulimit -u=$CUR"; fi
ok "cgroup pids.max=$(cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/pids.max 2>/dev/null || echo '?')"
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
if [ -n "$BAD" ]; then
warn "не от $TESTER:$BAD — чиню владельца"; sudo chown -R "$TESTER:$(id -gn)" "$HOME"; act "владелец \$HOME возвращён"
else ok "владелец \$HOME в порядке"; fi
echo "== 3. репозитории = установочные диски ОСновы =="
# Ищем примонтированные (или подключённые) CD/DVD с файловой системой ISO9660 — это диски
# ОСновы (в Proxmox ide0/ide2 → /dev/sr0,/dev/sr1). Каждый монтируем ro и регистрируем как
# apt-источник file:. Подписи ЗПС/вендора уже в связке ключей (система ставилась с этих
# дисков) → сначала пробуем БЕЗ trusted; если apt ругнётся на подпись — trusted=yes
# (диск физически доверенный, read-only, ничего чужого не тянет).
sudo install -d -m 0755 /media/osnova
DISK_SRCS=0
# кандидаты: реальные optical-устройства + любые block-устройства с iso9660
CANDS="$(ls /dev/sr[0-9] /dev/scd[0-9] 2>/dev/null)"
for dev in $CANDS; do
[ -b "$dev" ] || continue
blkid -o value -s TYPE "$dev" 2>/dev/null | grep -q iso9660 || {
# некоторые приводы не отдают TYPE до монтирования — пробуем всё равно
:
}
n="$(basename "$dev")"
mp="/media/osnova/$n"
sudo install -d -m 0755 "$mp"
if ! mountpoint -q "$mp"; then
sudo mount -o ro "$dev" "$mp" 2>/dev/null || { warn "$dev не смонтировался (пусто/нет диска) — пропускаю"; continue; }
fi
# apt-структура диска: dists/<suite>/... — suite обычно 'onyx' (или 'onyx3')
if [ ! -d "$mp/dists" ]; then
warn "$dev ($mp) без dists/ — это не apt-репозиторий ОСновы, пропускаю"
continue
fi
SUITE="$(ls "$mp/dists" 2>/dev/null | grep -viE 'stable|testing' | head -1)"
[ -n "$SUITE" ] || SUITE="$(ls "$mp/dists" 2>/dev/null | head -1)"
LABEL="$(blkid -o value -s LABEL "$dev" 2>/dev/null)"
echo "deb file:${mp} ${SUITE} main contrib non-free" | sudo tee "/etc/apt/sources.list.d/osnova-${n}.list" >/dev/null
# fstab (nofail,ro) — чтобы диск был доступен и после ребута (для join-скриптов apt)
if ! grep -q " ${mp} " /etc/fstab 2>/dev/null; then
echo "${dev} ${mp} iso9660 ro,nofail,x-systemd.automount 0 0" | sudo tee -a /etc/fstab >/dev/null
fi
act "диск ${n}${LABEL:+ [$LABEL]} → file:${mp} (suite=${SUITE})"
DISK_SRCS=$((DISK_SRCS+1))
done
# Родные cdrom:-строки заменяем нашими file: (cdrom:-метод apt интерактивен — просит
# «вставьте диск», что и вешало прогон). Комментируем с бэкапом.
if grep -q '^deb cdrom:' /etc/apt/sources.list 2>/dev/null; then
sudo sed -i.bak 's/^deb cdrom:/# deb cdrom:/' /etc/apt/sources.list
act "cdrom:-строки закомментированы (бэкап /etc/apt/sources.list.bak) — вместо них file:"
fi
[ "$DISK_SRCS" -gt 0 ] || die "не найдено ни одного установочного диска ОСновы (примонтированного ISO9660 с dists/).
Подключи диски в Proxmox: Hardware → CD/DVD Drive → образ onyx-2.14-disk1/disk2 (и devel, если нужен),
затем повтори скрипт. Проверить вручную: ls /dev/sr* ; sudo mount -o ro /dev/sr0 /mnt ; ls /mnt/dists"
# добить возможные недонастроенные пакеты с прошлых прерванных прогонов
sudo DEBIAN_FRONTEND=noninteractive dpkg --configure -a </dev/null 2>/dev/null || true
apt_get update || warn "apt-get update с ошибкой — смотри вывод (подпись диска? см. ниже)"
# если update упал на подписи (NO_PUBKEY / not signed) — перевести file:-источники на trusted=yes
if apt_get update 2>&1 | grep -qiE 'NO_PUBKEY|is not signed|GPG error'; then
warn "диск без валидной подписи в связке ключей — перевожу file: на [trusted=yes]"
for lst in /etc/apt/sources.list.d/osnova-*.list; do
[ -f "$lst" ] && sudo sed -i 's#^deb file:#deb [trusted=yes] file:#' "$lst"
done
apt_get update || warn "update всё ещё с ошибкой"
fi
ok "репозитории с дисков подключены ($DISK_SRCS шт.)"
echo "== 4. build-deps (ТОЛЬКО с дисков; чего нет — нужен диск «Комплект разработчика») =="
# Заранее отвечаем на дебконф-вопрос libc (на всякий; с родными дисками libc не обновляется).
echo 'libc6 libraries/restart-without-asking boolean true' | sudo debconf-set-selections 2>/dev/null || true
# Фильтруем список по тому, что реально ЕСТЬ на дисках: apt-get install падает целиком,
# если хоть один пакет не найден, — поэтому ставим только доступные, а недостающие копим.
WANT="make gcc g++ patch build-essential \
libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \
libncurses-dev libncurses5-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev tk-dev \
libgdbm-dev libgdbm-compat-dev uuid-dev \
wget curl git zip unzip xz-utils $JAVA_PKG"
AVAIL=""; MISS=""
for p in $WANT; do
if dpkg -s "$p" >/dev/null 2>&1; then continue; fi # уже стоит
if apt-cache show "$p" >/dev/null 2>&1; then AVAIL="$AVAIL $p"; else MISS="$MISS $p"; fi
done
if [ -n "$AVAIL" ]; then
act "ставлю с дисков:$AVAIL"
# shellcheck disable=SC2086
apt_get install -y $AVAIL || warn "часть пакетов не встала — смотри вывод"
else
ok "всё нужное уже установлено"
fi
if [ -n "$MISS" ]; then
warn "НЕТ на подключённых дисках:$MISS"
echo " Это пакеты с диска «Комплект разработчика» (devel). Подключи его ISO в Proxmox"
echo " (Hardware → CD/DVD → образ onyx-*-devel*.iso) и повтори скрипт — они встанут в"
echo " родных osnova-версиях. Debian-репозитории НЕ подключаем (сломают пакетную базу)."
fi
# гейт: без инструментов и критичного минимума -dev Python/pip не собрать
for t in git gcc make wget unzip; do
have "$t" || die "«$t» нет — на подключённых дисках его не оказалось. Нужен devel-диск (см. выше)."
done
for p in libssl-dev zlib1g-dev libffi-dev; do
dpkg -s "$p" >/dev/null 2>&1 || die "критичный $p не установлен — нужен devel-диск ОСновы (см. выше). Без него Python/pip не соберутся."
done
ok "build-deps обработаны (с дисков)"
echo "== 5. pyenv =="
if [ -d "$HOME/.pyenv" ]; then ok "pyenv есть"
else git clone https://github.com/pyenv/pyenv.git "$HOME/.pyenv" && act "pyenv склонирован"; fi
if grep -q 'pyenv init' "$HOME/.bashrc" 2>/dev/null; then
sed -i '/pyenv init/d' "$HOME/.bashrc"; warn "убрал 'eval pyenv init' из ~/.bashrc (рекурсивный форк)"
fi
if ! grep -q 'PYENV_ROOT' "$HOME/.bashrc" 2>/dev/null; then
{ echo 'export PYENV_ROOT="$HOME/.pyenv"'
echo 'export PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH"'; } >> "$HOME/.bashrc"
act "PYENV_ROOT/PATH добавлены в ~/.bashrc (без eval init)"
fi
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH"
echo "== 6. Python =="
if [ -z "$PYVER" ]; then
case "$(openssl version 2>/dev/null | awk '{print $2}')" in
3.*) PYVER="3.9.20"; act "OpenSSL 3.x (ОСнова 3.1) → PYVER=3.9.20";;
*) PYVER="3.9.6"; act "OpenSSL 1.x (ОСнова 2.14) → PYVER=3.9.6";;
esac
fi
if pyenv versions --bare 2>/dev/null | grep -qx "$PYVER"; then ok "Python $PYVER уже собран"
else act "сборка Python $PYVER (MAKE_OPTS=$MAKE_OPTS)"; pyenv install -s "$PYVER" \
|| die "Python $PYVER не собрался — если ошибка в _ssl, перезапусти PYVER=3.9.20 (OpenSSL 3); иначе не хватает -dev (нужен devel-диск)"; fi
pyenv rehash 2>/dev/null || true
echo "== 7. проект =="
export GIT_TERMINAL_PROMPT=1
if [ ! -d "$PROJECT_DIR/.git" ]; then
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"
act "клон под $GIT_USER (пароль через askpass)"
fi
else warn "git спросит логин/пароль GitLab (или задай GIT_USER=.. GIT_PASS=..)"; fi
git clone "$CLONE_URL" "$PROJECT_DIR" || die "git clone не прошёл (логин/пароль или сеть)"
[ -n "${AP:-}" ] && rm -f "$AP"
act "проект склонирован"
fi
cd "$PROJECT_DIR" || die "нет $PROJECT_DIR"
[ -n "$PROJECT_REF" ] && git checkout "$PROJECT_REF"
ok "$PROJECT_DIR"
echo "== 8. venv + requirements =="
pyenv local "$PYVER"
[ -d venv ] || python -m venv venv
source venv/bin/activate
python -m pip install -q --upgrade pip
python -m pip install -q -r requirements.txt || warn "pip install -r requirements.txt с ошибкой"
ok "venv готов"
echo "== 9. SDK =="
python download_deps.py || warn "download_deps.py не отработал (доступ к SDK?)"
echo "== 10. продукт rtlogon =="
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 "локального zip нет, качаю $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)"
# ОСнова = обычный debian-family пакет; берём _${ARCH}.deb БЕЗ astra/alt-тега и БЕЗ grdcontrol
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)"
if [ -z "$DEB" ]; then
warn "debian-family .deb под ${ARCH} в архиве не найден. Все .deb внутри:"; find "$EX" -name '*.deb' | sed 's/^/ /'
warn "задай нужный явно: PRODUCT_DEB=путь"
fi
fi
if [ -n "$DEB" ] && [ -f "$DEB" ]; then
act "ставлю $(basename "$DEB")"
# KDE Plasma → активный DM = sddm. Продукту lightdm нужен УСТАНОВЛЕННЫМ (приедет
# зависимостью .deb), активным быть не обязан → DM по умолчанию НЕ трогаем.
# ⚠️ Зависимости продукта (lightdm, pcsc, Qt) apt возьмёт С ДИСКОВ — они там есть.
case "${DISPLAY_MANAGER:-keep}" in
lightdm|sddm)
sudo debconf-set-selections <<< "sddm shared/default-x-display-manager select ${DISPLAY_MANAGER}" 2>/dev/null || true
sudo debconf-set-selections <<< "lightdm shared/default-x-display-manager select ${DISPLAY_MANAGER}" 2>/dev/null || true
echo "/usr/sbin/${DISPLAY_MANAGER}" | sudo tee /etc/X11/default-display-manager >/dev/null
act "DM принудительно выставлен: ${DISPLAY_MANAGER} (нужен reboot)"
;;
esac
apt_get install -y "$DEB" && act "установлен $(basename "$DEB")" \
|| { sudo dpkg -i "$DEB"; apt_get -f install -y; } \
|| warn "deb не встал — доустанови зависимости (если пакета нет на дисках — нужен devel/extras-диск)"
ACT_DM="$(basename "$(cat /etc/X11/default-display-manager 2>/dev/null)" 2>/dev/null)"
echo " активный DM: ${ACT_DM:-?}; lightdm установлен: $(dpkg -s lightdm >/dev/null 2>&1 && echo да || echo НЕТ)"
dpkg -s lightdm >/dev/null 2>&1 || warn "lightdm НЕ установлен — тесты с --use-system-gui no по /etc/lightdm будут скипаться"
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 "== 11. Allure =="
if have allure; then ok "allure стоит: $(allure --version 2>/dev/null)"
else
TGZ="allure-commandline-${ALLURE_VER}.tgz"
if ( 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 ); then
act "Allure $ALLURE_VER установлен"; allure --version 2>/dev/null || warn "allure не стартовал — проверь java -version"
else warn "Allure не скачался (Maven Central недоступен?) — поставь вручную"; fi
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"