#!/usr/bin/env bash # Сборка OTA-релиза WESP: код + полный vendor/wheels (offline-установка на клиенте). # # Каждый ZIP — самодостаточный пакет: pip download → verify → zip. # x86 и ARM — разные релизы: собирайте на машине той же ОС/arch, что терминалы # в проде, и публикуйте в отдельный Gitea-репозиторий. # # Полный цикл (пути с пробелами — в кавычках): # bash "./scripts/build_release.sh" 2.0.5 # → dist/wesp-2.0.5-linux-x86_64.zip (или darwin-x86_64, linux-aarch64 …) # Собирайте на той же ОС/Python, что на терминалах в проде (см. WESP_PREPARE_PYTHON). # # Только wheelhouse (без ZIP): # ./scripts/build_release.sh --wheelhouse-only # # ZIP без повторного pip download (wheels уже собраны на этой же машине): # ./scripts/build_release.sh --skip-prepare 2.0.5 set -euo pipefail SKIP_PREPARE=0 WHEELHOUSE_ONLY=0 VERSION="" while [[ $# -gt 0 ]]; do case "$1" in --skip-prepare) SKIP_PREPARE=1; shift ;; --wheelhouse-only) WHEELHOUSE_ONLY=1; shift ;; -h|--help) sed -n '2,16p' "$0" exit 0 ;; -*) echo "Unknown option: $1" >&2; exit 1 ;; *) if [[ -z "$VERSION" ]]; then VERSION="$1" else echo "Unexpected argument: $1" >&2 exit 1 fi shift ;; esac done ROOT="$(cd "$(dirname "$0")/.." && pwd)" PYTHON="${WESP_PREPARE_PYTHON:-python3}" REQ="${ROOT}/requirements-prod.txt" WHEELS="${ROOT}/vendor/wheels" BUILD_ARCH="$(uname -m 2>/dev/null || echo unknown)" BUILD_PLATFORM="$("$PYTHON" -c 'import platform; print(platform.system())' 2>/dev/null || uname -s 2>/dev/null || echo unknown)" BUILD_OS="$(echo "$BUILD_PLATFORM" | tr '[:upper:]' '[:lower:]')" BUILD_TAG="${BUILD_OS}-${BUILD_ARCH}" log_build_target() { echo "==> Build target: ${BUILD_PLATFORM} / ${BUILD_ARCH} (${BUILD_TAG})" echo " ZIP содержит vendor/wheels для этой ОС. Другая arch → другой репозиторий." } wheelhouse_file_count() { find "$WHEELS" -type f \( -name '*.whl' -o -name '*.tar.gz' -o -name '*.zip' \) 2>/dev/null | wc -l | tr -d ' ' } clean_wheelhouse() { echo "==> Clean vendor/wheels (кроме .gitkeep)" mkdir -p "$WHEELS" find "$WHEELS" -mindepth 1 ! -name '.gitkeep' -delete } prepare_wheelhouse() { log_build_target clean_wheelhouse echo "==> Download wheels (PyPI) → vendor/wheels/ [requirements-prod.txt + транзитивные deps]" "$PYTHON" -m pip download --prefer-binary -r "$REQ" -d "$WHEELS" "$PYTHON" -m pip download setuptools wheel packaging -d "$WHEELS" echo "==> Materialize sdists → wheels (zeroconf и др., без Cython с PyPI на терминале)" "$PYTHON" "${ROOT}/scripts/materialize_wheelhouse_sdists.py" \ --wheels "$WHEELS" \ --python "$PYTHON" "$PYTHON" "${ROOT}/scripts/verify_release_deps.py" \ --req "$REQ" \ --python "$PYTHON" \ --verify-closure \ --write-manifest \ --build-arch "$BUILD_ARCH" \ --build-platform "$BUILD_PLATFORM" } verify_wheelhouse() { echo "==> Verify vendor/wheels (обязательно перед упаковкой)" "$PYTHON" "${ROOT}/scripts/verify_release_deps.py" \ --req "$REQ" \ --python "$PYTHON" \ --verify-closure } verify_static_assets() { echo "==> Verify static UI assets (offline, no CDN in HTML/CSS)" "$PYTHON" "${ROOT}/scripts/verify_static_offline_assets.py" } zip_wheelhouse_summary() { local archive="$1" local whl_in_zip whl_bytes_in_zip whl_in_zip="$(unzip -l "$archive" 2>/dev/null | grep -c 'vendor/wheels/.*\.\(whl\|tar\.gz\)$' || echo 0)" whl_bytes_in_zip="$(unzip -l "$archive" 2>/dev/null | grep 'vendor/wheels/.*\.\(whl\|tar\.gz\)$' | awk '{s+=$1} END {print s+0}')" echo " vendor/wheels в ZIP: ${whl_in_zip} файлов, ~$(( whl_bytes_in_zip / 1024 / 1024 )) MB (несжатые .whl)" } build_zip() { if [[ -z "$VERSION" ]]; then echo "Usage: $0 " >&2 echo "Example: $0 2.0.5" >&2 exit 1 fi local out_dir="${ROOT}/dist" local archive="${out_dir}/wesp-${VERSION}-${BUILD_TAG}.zip" local staging="${out_dir}/.staging-${VERSION}-${BUILD_TAG}" local whl_count whl_disk_mb whl_count="$(wheelhouse_file_count)" whl_disk_mb="$(du -sk "$WHEELS" 2>/dev/null | awk '{print int($1/1024)}')" if [[ "$whl_count" -lt 1 ]]; then echo "ERROR: vendor/wheels пуст — нельзя собрать OTA без зависимостей" >&2 exit 1 fi echo "==> Pack $(basename "$archive") (wheelhouse на диске: ${whl_count} pkg, ~${whl_disk_mb} MB)" rm -rf "$staging" mkdir -p "$staging" "$out_dir" rsync -a \ --exclude='.git/' \ --exclude='.venv/' \ --exclude='wes_mac/' \ --exclude='test/' \ --exclude='__pycache__/' \ --exclude='.pytest_cache/' \ --exclude='*.pyc' \ --exclude='.secret/' \ --exclude='backups/' \ --exclude='temp_updates/' \ --exclude='dist/' \ --exclude='data/*.db' \ --exclude='data/*.db-shm' \ --exclude='data/*.db-wal' \ --exclude='data/logs/' \ --exclude='data/update_state.json' \ --exclude='data/.pending_restart.json' \ --exclude='data/wesp_install_state.json' \ --exclude='data/wesp_security_settings.json' \ --exclude='data/wesp_network_settings.json' \ --exclude='data/wesp_network_diagnostics.json' \ --exclude='data/sync_client_state.json' \ --exclude='.DS_Store' \ --exclude='.cursor/' \ "$ROOT/" "$staging/wesp/" if [[ ! -d "$staging/wesp/vendor/wheels" ]] || [[ -z "$(find "$staging/wesp/vendor/wheels" -type f -name '*.whl' 2>/dev/null | head -1)" ]]; then echo "ERROR: vendor/wheels не попал в staging — прервано" >&2 exit 1 fi mkdir -p "$staging/wesp/data/logs" touch "$staging/wesp/data/logs/.gitkeep" echo "{\"version\": \"${VERSION}\"}" > "$staging/wesp/data/config.json" rm -f "$archive" (cd "$staging" && zip -rq "$archive" wesp) rm -rf "$staging" local size files size="$(du -sh "$archive" | awk '{print $1}')" files="$(unzip -l "$archive" | tail -1 | awk '{print $2}')" echo "" echo "════════════════════════════════════════" echo "OTA package: $(basename "$archive")" echo " Архив: ${size}, ${files} files" zip_wheelhouse_summary "$archive" echo " Платформа: ${BUILD_TAG} → свой Gitea-реpo для этих терминалов" echo " Tag: v${VERSION} or ${VERSION}" echo "════════════════════════════════════════" } if [[ "$SKIP_PREPARE" -eq 0 ]]; then prepare_wheelhouse else echo "==> Skip pip download (--skip-prepare; wheels должны быть с этой же машины/arch)" if [[ "$(wheelhouse_file_count)" -lt 1 ]]; then echo "ERROR: vendor/wheels пуст — уберите --skip-prepare" >&2 exit 1 fi fi if [[ "$WHEELHOUSE_ONLY" -eq 1 ]]; then echo "Done (wheelhouse only)." exit 0 fi verify_wheelhouse verify_static_assets build_zip