move_detect/deploy/bundle/build_offline_bundle.sh
유창욱 9386dfc580 feat(delivery): install-path fixes + cafe24 package + delivery-audit hardening
Three intertwined bodies of work developed and tested together this session.
Full test suite green: python (edge) + phpunit (cloud).

## Edge/Pi install path
- setup_online.sh: apt-lock retry (apt-daily contention), Type=exec unit +
  StartLimitIntervalSec in [Unit], journalctl-on-failure, crash-loop detection.
- onnxruntime is now best-effort (no sdist / no 32-bit-ARM wheel); [6/8] probes the
  real backend and hard-fails if none works.
- Hailo HEF auto-fetch: edge/tools/fetch_hailo_hef.py (pinned sha256, refuses to guess
  8 vs 8L, HAILO_ARCH override for serial-less boards); hailo_pose.py run() timeout fix.
- install_offline.sh: mirrors the backend gate; onnxruntime optional.

## Cafe24 cloud package (cloud/deploy/)
- build_cafe24_package.sh emits move_detect_app/ (private, one folder) + www/move_detect/
  (docroot subdir); no-dev classmap autoloader; preflight.php; hardened .htaccess.
- Subdirectory mount support: Request::baseFrom/pathFor strip the mount prefix; index.php
  walks up to find the app root; views/JS prefix every URL with APP_BASE; session cookie
  scoped to the mount.

## Delivery audit — silent-failure & correctness (docs/delivery-audit.md)
All 4 blockers + all Phase-1 silent-failure + Phase-2 HIGH correctness:
- Edge cloud clients send a custom User-Agent (cafe24 WAF blocks python-requests) and
  allow_redirects=False.
- Daemon fails loud on blank CAFE24_URL/DEVICE_SECRET; run_loop logs swallowed exceptions;
  heartbeat carries real fps + per-camera health; UvcSource reopens a dead camera;
  offline queue is size-capped and drops poison rows; TARGET_FPS/QUEUE_DB validated.
- Zone coordinates normalized 0..1 on both sides (were canvas-px vs camera-px — every zone
  misplaced); pinch zones emit PINCH/HIGH (were downgraded to WARNING).
- Fall detection uses bbox cues when keypoints are low-confidence (occluded fallen person);
  ghost incident not cancelled by a bystander; displacement normalized per-second (FPS-invariant).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Xmn8KZWqiQUguEMRtn6rW
2026-07-17 09:07:33 +09:00

95 lines
4.3 KiB
Bash
Executable file

#!/usr/bin/env bash
# Build a fully self-contained, offline-installable bundle for the move-detect
# edge daemon (air-gapped / 폐쇄망 target — see rules/common/build-deployment.md).
#
# Run this on a BUILD machine that HAS internet. The resulting tarball carries
# every runtime wheel + the pose model + the edge source + the systemd unit, so
# the air-gapped Pi installs with ZERO network fetches (see install_offline.sh).
#
# RELIABILITY NOTE: onnxruntime and opencv publish per-architecture wheels. The
# most reliable build is a NATIVE one — run this ON an aarch64 host (a Pi 5, or
# an arm64 CI runner) so pip downloads wheels for the exact target. Cross-downloading
# aarch64 wheels from an x86 box (--cross) is best-effort and can miss a wheel whose
# tags don't match; if it fails, build natively.
#
# Usage:
# deploy/bundle/build_offline_bundle.sh # native (host arch)
# deploy/bundle/build_offline_bundle.sh --cross # cross to aarch64/cp311
# TARGET_ARCH=aarch64 PYVER=3.11 deploy/bundle/build_offline_bundle.sh --cross
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
OUT_DIR="${OUT_DIR:-$REPO_ROOT/dist}"
STAGE="$OUT_DIR/move-detect-edge-bundle"
WHEELHOUSE="$STAGE/wheelhouse"
PYVER="${PYVER:-3.11}" # Pi OS Bookworm ships CPython 3.11
PYABI="cp${PYVER//./}" # cp311
TARGET_ARCH="${TARGET_ARCH:-aarch64}"
RUNTIME_PKGS=(numpy requests onnxruntime opencv-python-headless)
MODE="native"
[ "${1:-}" = "--cross" ] && MODE="cross"
echo ">> build mode : $MODE"
echo ">> python target : $PYVER ($PYABI)"
echo ">> target arch : $TARGET_ARCH"
echo ">> output : $OUT_DIR"
rm -rf "$STAGE"
mkdir -p "$WHEELHOUSE" "$STAGE/src" "$STAGE/models" "$STAGE/deploy/systemd" "$STAGE/docs"
echo ">> [1/5] downloading runtime wheels -> wheelhouse/"
if [ "$MODE" = "cross" ]; then
# Best-effort cross download. Try the common manylinux aarch64 tags.
python3 -m pip download \
--only-binary=:all: \
--python-version "$PYVER" --implementation cp --abi "$PYABI" \
--platform "manylinux2014_${TARGET_ARCH}" \
--platform "manylinux_2_28_${TARGET_ARCH}" \
-d "$WHEELHOUSE" "${RUNTIME_PKGS[@]}"
else
# Native: wheels for THIS host's interpreter/arch (run on the Pi / arm64 CI).
# Guard the ABI: numpy/onnxruntime ship version-specific cpXY wheels, so a build
# host whose python3 != target PYVER produces a wheelhouse the Pi's cp311 venv
# cannot install offline. Fail loudly instead of shipping a broken bundle.
HOST_PYVER="$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')"
if [ "$HOST_PYVER" != "$PYVER" ]; then
echo "ERROR: native build host python3 is $HOST_PYVER but target PYVER is $PYVER." >&2
echo " Run with python$PYVER (e.g. 'PYVER=$HOST_PYVER ...' only if the Pi runs $HOST_PYVER)," >&2
echo " or use --cross to pin aarch64/$PYABI wheels explicitly." >&2
exit 1
fi
python3 -m pip download -d "$WHEELHOUSE" "${RUNTIME_PKGS[@]}"
fi
echo ">> [2/5] fetching MoveNet ONNX model (build-machine only)"
( cd "$REPO_ROOT" && python3 -m edge.tools.fetch_movenet )
cp "$REPO_ROOT/models/movenet_lightning.onnx" "$STAGE/models/"
echo ">> [3/5] staging edge source (package + pyproject)"
cp -r "$REPO_ROOT/edge" "$STAGE/src/edge"
cp "$REPO_ROOT/pyproject.toml" "$STAGE/src/"
# do not ship compiled caches
find "$STAGE/src" -name '__pycache__' -type d -prune -exec rm -rf {} +
echo ">> [4/5] staging deploy assets + install script"
cp "$REPO_ROOT/deploy/systemd/move-detect-edge.service" "$STAGE/deploy/systemd/"
cp "$REPO_ROOT/deploy/systemd/move-detect-edge.env.example" "$STAGE/deploy/systemd/"
cp "$REPO_ROOT/deploy/bundle/install_offline.sh" "$STAGE/"
cp "$REPO_ROOT/docs/pilot-runbook.md" "$STAGE/docs/" 2>/dev/null || true
chmod +x "$STAGE/install_offline.sh"
{
echo "bundle: move-detect-edge"
echo "built_mode: $MODE"
echo "python: $PYVER ($PYABI)"
echo "target_arch: $TARGET_ARCH"
echo "wheels:"
ls -1 "$WHEELHOUSE"
} > "$STAGE/MANIFEST.txt"
echo ">> [5/5] packaging tarball"
TARBALL="$OUT_DIR/move-detect-edge-bundle-${TARGET_ARCH}-${PYABI}.tar.gz"
tar -C "$OUT_DIR" -czf "$TARBALL" "move-detect-edge-bundle"
echo ">> DONE: $TARBALL"
echo ">> transfer this single file across the air gap, then run install_offline.sh on the Pi."