207 lines
7.2 KiB
Bash
207 lines
7.2 KiB
Bash
#!/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
|