@@ -0,0 +1,206 @@
|
||||
#!/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 <version>" >&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
|
||||
Reference in New Issue
Block a user