#!/usr/bin/env bash # Скрипт первичной подготовки dev-ВМ для проекта Bridge and Join's # (сервис M2M-перевода ЦБ через НРД). # # Поддерживаемые ОС: # - РЕД ОС 7.x — целевая прод-ОС (вариант с бюджетом на лицензии) # - Ubuntu 22.04+ — бюджетная альтернатива для разработки и для # компаний, у которых ещё нет лицензий на РЕД ОС # (для прод-сертификации в финсекторе всё равно # потребуется РЕД ОС / Astra SE с сертификатом ФСТЭК) # # Что делает: # - определяет ОС и пакетный менеджер; # - устанавливает базовый dev-стек (Git, Make, jq, xmlstarlet, ...); # - устанавливает Go 1.23, Liberica JDK 21, Node.js 20 LTS, Podman; # - устанавливает Claude Code CLI; # - создаёт пользователя dev и каталог /srv/dev; # - готовит безопасный shell-history с timestamp. # # Запуск (под root или через sudo): # sudo bash scripts/setup-dev-vm.sh # # Переменные окружения для тонкой настройки: # GO_VERSION — версия Go (по умолчанию 1.23.4) # JDK_VERSION — версия Liberica JDK (по умолчанию 21) # NODE_VERSION — версия Node (по умолчанию 20) # DEV_USER — имя пользователя для разработки (по умолчанию dev) # DEV_HOME — домашняя директория dev (по умолчанию /home/dev) # WORKSPACE_ROOT — корень рабочих репо (по умолчанию /srv/dev) # SKIP_USER — 1, чтобы не создавать пользователя # SKIP_CLAUDE — 1, чтобы не ставить Claude Code CLI # # Скрипт идемпотентен — повторный запуск не ломает то, что уже сделано. set -euo pipefail # --------------------------------------------------------------------- # # Параметры # --------------------------------------------------------------------- # GO_VERSION="${GO_VERSION:-1.23.4}" JDK_VERSION="${JDK_VERSION:-21}" NODE_VERSION="${NODE_VERSION:-20}" DEV_USER="${DEV_USER:-dev}" DEV_HOME="${DEV_HOME:-/home/${DEV_USER}}" WORKSPACE_ROOT="${WORKSPACE_ROOT:-/srv/dev}" SKIP_USER="${SKIP_USER:-0}" SKIP_CLAUDE="${SKIP_CLAUDE:-0}" REPO_URL="https://git.zetit.ru/zuevav/Bridge-and-Join-s.git" # --------------------------------------------------------------------- # # Хелперы # --------------------------------------------------------------------- # log() { printf '[%s] %s\n' "$(date +'%Y-%m-%d %H:%M:%S')" "$*"; } die() { printf '[ОШИБКА] %s\n' "$*" >&2; exit 1; } need_root() { [[ "$EUID" -eq 0 ]] || die "Нужны права root. Запусти через sudo."; } # --------------------------------------------------------------------- # # 0. Предусловия — определение ОС и менеджера пакетов # --------------------------------------------------------------------- # need_root if [[ ! -f /etc/os-release ]]; then die "Файл /etc/os-release не найден. Проверь, что ОС поддерживает FHS." fi # shellcheck disable=SC1091 . /etc/os-release OS_FAMILY="" # rpm | deb OS_HUMAN="" case "${ID:-}" in redos) OS_FAMILY="rpm" OS_HUMAN="РЕД ОС ${VERSION_ID:-?}" ;; rhel|centos|rocky|almalinux|fedora) OS_FAMILY="rpm" OS_HUMAN="${PRETTY_NAME:-RHEL-совместимая}" log "ВНИМАНИЕ: ОС ${OS_HUMAN} — для разработки подходит, для прод-сертификации в финсекторе нужна РЕД ОС / Astra SE." ;; ubuntu|debian) OS_FAMILY="deb" OS_HUMAN="${PRETTY_NAME:-${ID} ${VERSION_ID:-?}}" log "ОС ${OS_HUMAN} — бюджетный вариант для разработки. Для прода в финсекторе нужна РЕД ОС / Astra SE с сертификатом ФСТЭК." ;; astra) OS_FAMILY="deb" OS_HUMAN="Astra Linux ${VERSION_ID:-?}" ;; *) log "ВНИМАНИЕ: ОС определена как ${ID:-неизвестно} ${VERSION_ID:-?}." if command -v dnf >/dev/null 2>&1 || command -v yum >/dev/null 2>&1; then OS_FAMILY="rpm" elif command -v apt-get >/dev/null 2>&1; then OS_FAMILY="deb" else die "Не удалось определить пакетный менеджер. Поддерживаются dnf/yum (РЕД ОС, RHEL) и apt (Ubuntu, Debian, Astra)." fi log "Пробуем как ${OS_FAMILY}-совместимую." ;; esac log "ОС: ${OS_HUMAN}, семейство: ${OS_FAMILY}" PKG_INSTALL="" PKG_UPDATE="" case "${OS_FAMILY}" in rpm) if command -v dnf >/dev/null 2>&1; then PKG_INSTALL="dnf install -y"; PKG_UPDATE="dnf -y makecache" elif command -v yum >/dev/null 2>&1; then PKG_INSTALL="yum install -y"; PKG_UPDATE="yum -y makecache" else die "Не найден dnf/yum" fi ;; deb) export DEBIAN_FRONTEND=noninteractive PKG_INSTALL="apt-get install -y --no-install-recommends" PKG_UPDATE="apt-get update -y" ;; esac log "Обновление кэша пакетов" ${PKG_UPDATE} # --------------------------------------------------------------------- # # 1. Базовый системный стек # --------------------------------------------------------------------- # log "Шаг 1/8: установка базовых пакетов" case "${OS_FAMILY}" in rpm) ${PKG_INSTALL} \ git make curl wget tar gzip unzip ca-certificates \ jq xmlstarlet libxml2 \ gcc gcc-c++ binutils \ python3 python3-pip \ postgresql-libs \ openssl-libs ;; deb) ${PKG_INSTALL} \ git make curl wget tar gzip unzip ca-certificates \ jq xmlstarlet libxml2-utils \ build-essential \ python3 python3-pip python3-venv \ libpq5 \ libssl3 \ gnupg lsb-release software-properties-common ;; esac # --------------------------------------------------------------------- # # 2. Podman + podman-compose # --------------------------------------------------------------------- # log "Шаг 2/8: установка Podman и podman-compose" case "${OS_FAMILY}" in rpm) ${PKG_INSTALL} podman podman-docker || true ;; deb) ${PKG_INSTALL} podman || true # podman-docker может отсутствовать — не критично apt-get install -y --no-install-recommends podman-docker 2>/dev/null || true ;; esac if ! command -v podman-compose >/dev/null 2>&1; then pip3 install --quiet podman-compose || \ log "ВНИМАНИЕ: не удалось установить podman-compose через pip3. Поставь вручную." fi # Включаем сокет podman, чтобы клиенты, ожидающие docker.sock, работали без правок systemctl enable --now podman.socket 2>/dev/null || true # --------------------------------------------------------------------- # # 3. Go # --------------------------------------------------------------------- # log "Шаг 3/8: установка Go ${GO_VERSION}" GO_TARBALL="go${GO_VERSION}.linux-amd64.tar.gz" GO_URL="https://go.dev/dl/${GO_TARBALL}" if [[ -d /usr/local/go && -x /usr/local/go/bin/go ]]; then CURRENT_GO_VER="$(/usr/local/go/bin/go version | awk '{print $3}' | sed 's/go//')" if [[ "${CURRENT_GO_VER}" == "${GO_VERSION}" ]]; then log "Go ${GO_VERSION} уже установлен" else log "Найден Go ${CURRENT_GO_VER}, переустанавливаем на ${GO_VERSION}" rm -rf /usr/local/go fi fi if [[ ! -d /usr/local/go ]]; then TMP_TAR="/tmp/${GO_TARBALL}" wget -q --show-progress -O "${TMP_TAR}" "${GO_URL}" tar -C /usr/local -xzf "${TMP_TAR}" rm -f "${TMP_TAR}" fi cat > /etc/profile.d/go.sh <<'EOF' export PATH=$PATH:/usr/local/go/bin export GOPATH=$HOME/go export PATH=$PATH:$GOPATH/bin EOF chmod 0644 /etc/profile.d/go.sh # --------------------------------------------------------------------- # # 4. Liberica JDK # --------------------------------------------------------------------- # log "Шаг 4/8: установка Liberica JDK ${JDK_VERSION}" case "${OS_FAMILY}" in rpm) if [[ ! -f /etc/yum.repos.d/bellsoft.repo ]]; then cat > /etc/yum.repos.d/bellsoft.repo <<'EOF' [bellsoft] name=BellSoft Repository baseurl=https://yum.bell-sw.com/ enabled=1 gpgcheck=1 gpgkey=https://download.bell-sw.com/pki/GPG-KEY-bellsoft EOF fi ${PKG_INSTALL} "bellsoft-java${JDK_VERSION}" || \ log "ВНИМАНИЕ: не удалось установить bellsoft-java${JDK_VERSION}. Альтернатива — скачать tar.gz с https://bell-sw.com/pages/downloads/." ;; deb) if [[ ! -f /etc/apt/sources.list.d/bellsoft.list ]]; then curl -fsSL https://download.bell-sw.com/pki/GPG-KEY-bellsoft \ | gpg --dearmor -o /usr/share/keyrings/bellsoft.gpg echo "deb [signed-by=/usr/share/keyrings/bellsoft.gpg] https://apt.bell-sw.com/ stable main" \ > /etc/apt/sources.list.d/bellsoft.list apt-get update -y fi ${PKG_INSTALL} "bellsoft-java${JDK_VERSION}-full" || \ ${PKG_INSTALL} "bellsoft-java${JDK_VERSION}" || \ log "ВНИМАНИЕ: не удалось установить bellsoft-java${JDK_VERSION}. Альтернатива — скачать tar.gz с https://bell-sw.com/pages/downloads/." ;; esac # --------------------------------------------------------------------- # # 5. Node.js LTS (для admin-ui и Claude Code CLI) # --------------------------------------------------------------------- # log "Шаг 5/8: установка Node.js ${NODE_VERSION} LTS" need_install_node=1 if command -v node >/dev/null 2>&1; then CUR_NODE_MAJOR="$(node --version | sed 's/v//;s/\..*//')" if [[ "${CUR_NODE_MAJOR}" == "${NODE_VERSION}" ]]; then log "Node.js ${NODE_VERSION} уже установлен" need_install_node=0 fi fi if [[ "${need_install_node}" == "1" ]]; then case "${OS_FAMILY}" in rpm) curl -fsSL "https://rpm.nodesource.com/setup_${NODE_VERSION}.x" | bash - ${PKG_INSTALL} nodejs ;; deb) curl -fsSL "https://deb.nodesource.com/setup_${NODE_VERSION}.x" | bash - ${PKG_INSTALL} nodejs ;; esac fi # --------------------------------------------------------------------- # # 6. Claude Code CLI # --------------------------------------------------------------------- # if [[ "${SKIP_CLAUDE}" != "1" ]]; then log "Шаг 6/8: установка Claude Code CLI" npm install -g @anthropic-ai/claude-code else log "Шаг 6/8: пропуск установки Claude Code CLI (SKIP_CLAUDE=1)" fi # --------------------------------------------------------------------- # # 7. Пользователь dev и рабочая директория # --------------------------------------------------------------------- # if [[ "${SKIP_USER}" != "1" ]]; then log "Шаг 7/8: подготовка пользователя ${DEV_USER}" if ! id -u "${DEV_USER}" >/dev/null 2>&1; then useradd -m -s /bin/bash -d "${DEV_HOME}" "${DEV_USER}" log "Создан пользователь ${DEV_USER}" else log "Пользователь ${DEV_USER} уже существует" fi mkdir -p "${WORKSPACE_ROOT}" chown "${DEV_USER}:${DEV_USER}" "${WORKSPACE_ROOT}" HIST_SNIPPET="${DEV_HOME}/.bashrc.d-history" cat > "${HIST_SNIPPET}" <<'EOF' # История с timestamp и общая для всех сессий — для аудита. export HISTTIMEFORMAT="%F %T " export HISTSIZE=10000 export HISTFILESIZE=20000 shopt -s histappend PROMPT_COMMAND="history -a; ${PROMPT_COMMAND:-}" EOF chown "${DEV_USER}:${DEV_USER}" "${HIST_SNIPPET}" if ! grep -q "bashrc.d-history" "${DEV_HOME}/.bashrc" 2>/dev/null; then echo "[ -f ${HIST_SNIPPET} ] && . ${HIST_SNIPPET}" >> "${DEV_HOME}/.bashrc" fi if ! grep -q "/usr/local/go/bin" "${DEV_HOME}/.bashrc" 2>/dev/null; then cat >> "${DEV_HOME}/.bashrc" <<'EOF' [ -f /etc/profile.d/go.sh ] && . /etc/profile.d/go.sh EOF fi else log "Шаг 7/8: пропуск подготовки пользователя (SKIP_USER=1)" fi # --------------------------------------------------------------------- # # 8. Финальные проверки # --------------------------------------------------------------------- # log "Шаг 8/8: проверка установленных версий" { printf 'git: '; git --version printf 'make: '; make --version | head -n1 printf 'jq: '; jq --version printf 'xmlstarlet: '; xmlstarlet --version | head -n1 printf 'podman: '; podman --version 2>/dev/null || echo "(не установлен)" printf 'podman-compose:'; podman-compose --version 2>/dev/null || echo "(не установлен)" printf 'go: '; /usr/local/go/bin/go version printf 'java: '; java -version 2>&1 | head -n1 printf 'node: '; node --version 2>/dev/null || echo "(не установлен)" printf 'npm: '; npm --version 2>/dev/null || echo "(не установлен)" if [[ "${SKIP_CLAUDE}" != "1" ]]; then printf 'claude: '; claude --version 2>/dev/null || echo "(не установлен или требует первого запуска)" fi } || true cat <