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
125 lines
6.4 KiB
Bash
Executable file
125 lines
6.4 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Offline installer for the move-detect edge daemon. Runs on the air-gapped Pi 5
|
|
# (NO internet). Unpack the bundle produced by build_offline_bundle.sh, cd into it,
|
|
# then: sudo ./install_offline.sh
|
|
#
|
|
# Installs everything from the bundled wheelhouse with --no-index (zero fetches),
|
|
# runs the package straight from source (no build backend needed offline).
|
|
set -euo pipefail
|
|
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
echo "must run as root (sudo ./install_offline.sh)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PREFIX=/opt/move-detect
|
|
STATE=/var/lib/move-detect
|
|
ETC=/etc/move-detect
|
|
SVC_USER=movedetect
|
|
WHEELHOUSE="$HERE/wheelhouse"
|
|
# Mandatory for every backend. onnxruntime is deliberately NOT here — see the optional
|
|
# install below; a Hailo bundle need not carry its wheel.
|
|
RUNTIME_PKGS=(numpy requests opencv-python-headless)
|
|
OPTIONAL_PKGS=(onnxruntime)
|
|
|
|
echo ">> [1/6] service account + groups"
|
|
if ! id -u "$SVC_USER" >/dev/null 2>&1; then
|
|
useradd --system --no-create-home --shell /usr/sbin/nologin "$SVC_USER"
|
|
fi
|
|
# camera (video), Hailo accelerator (render/plugdev vary by image) — ignore missing groups
|
|
for g in video render plugdev hailo; do
|
|
getent group "$g" >/dev/null 2>&1 && usermod -aG "$g" "$SVC_USER" || true
|
|
done
|
|
|
|
echo ">> [2/6] lay down source, model, docs under $PREFIX"
|
|
install -d -m 0755 "$PREFIX" "$PREFIX/src" "$PREFIX/models" "$PREFIX/docs"
|
|
# Fully replace the package tree on re-install/upgrade. Without the rm, `cp -r SRC DEST`
|
|
# nests SRC *inside* an existing DEST (edge/edge/...) and the OLD code keeps running.
|
|
rm -rf "$PREFIX/src/edge"
|
|
cp -r "$HERE/src/edge" "$PREFIX/src/edge"
|
|
cp "$HERE/src/pyproject.toml" "$PREFIX/src/" 2>/dev/null || true
|
|
cp "$HERE/models/"*.onnx "$PREFIX/models/"
|
|
cp "$HERE/docs/"*.md "$PREFIX/docs/" 2>/dev/null || true
|
|
|
|
echo ">> [3/6] python venv + offline wheel install (--no-index)"
|
|
# --system-site-packages so the venv can import the Pi SYSTEM packages that are NOT
|
|
# pip-installable: hailo_platform (HailoRT, apt: hailo-all) for POSE_BACKEND=hailo, and
|
|
# picamera2 (apt: python3-picamera2) for CAMERA_BACKEND=csi. The wheelhouse install
|
|
# below adds our deps into the venv; where a system copy already satisfies a wheel's
|
|
# version constraint pip keeps it (ABI-compatible on the target images), otherwise the
|
|
# bundled wheel installs into the venv and shadows the system copy.
|
|
python3 -m venv --system-site-packages "$PREFIX/venv"
|
|
"$PREFIX/venv/bin/pip" install --no-index --find-links "$WHEELHOUSE" --upgrade pip >/dev/null 2>&1 || true
|
|
"$PREFIX/venv/bin/pip" install --no-index --find-links "$WHEELHOUSE" "${RUNTIME_PKGS[@]}"
|
|
# Optional: only present if the bundle was built for the CPU MoveNet backend. Still --no-index
|
|
# (zero fetches, air-gap intact); absent simply means Hailo-only.
|
|
"$PREFIX/venv/bin/pip" install --no-index --find-links "$WHEELHOUSE" "${OPTIONAL_PKGS[@]}" \
|
|
|| echo " -> onnxruntime not in the wheelhouse; Hailo-only bundle (CPU MoveNet unavailable)."
|
|
|
|
echo ">> [4/6] verify the edge package imports offline"
|
|
# cv2/numpy/requests are mandatory for every backend (snapshot.py imports cv2 at module level
|
|
# on the unconditional startup chain; the daemon's cloud clients import requests at startup).
|
|
PYTHONPATH="$PREFIX/src" "$PREFIX/venv/bin/python" -c "import edge.daemon, cv2, numpy, requests; print('edge import OK')"
|
|
# onnxruntime is the CPU-MoveNet-only dep — the Hailo path never imports it, and onnxruntime
|
|
# ships no sdist and no 32-bit-ARM wheel, so a bundle built for a Hailo Pi legitimately may
|
|
# not carry it. Report, don't fail: a hard `import onnxruntime` here refuses the install over
|
|
# a package the daemon never loads.
|
|
if PYTHONPATH="$PREFIX/src" "$PREFIX/venv/bin/python" -c "import onnxruntime" >/dev/null 2>&1; then
|
|
echo " -> onnxruntime present; CPU MoveNet available"
|
|
else
|
|
echo " -> onnxruntime absent; CPU MoveNet unavailable (set POSE_BACKEND=hailo in $ETC/edge.env)"
|
|
fi
|
|
|
|
echo ">> [5/6] pose backend gate + state dir + env file"
|
|
# Do NOT hand over a Pi whose POSE_BACKEND cannot load. The template ships POSE_BACKEND=movenet,
|
|
# but a Hailo-only bundle carries no onnxruntime/ONNX — installing it verbatim would crash-loop.
|
|
# Probe what can ACTUALLY run (like setup_online's [6/8]) and reconcile, or refuse.
|
|
HEF_DEST="$PREFIX/models/yolov8s_pose.hef"
|
|
ONNX_DEST="$PREFIX/models/movenet_lightning.onnx"
|
|
pyhas() { PYTHONPATH="$PREFIX/src" "$PREFIX/venv/bin/python" -c "import $1" >/dev/null 2>&1; }
|
|
state() { if "$@" >/dev/null 2>&1; then echo present; else echo MISSING; fi; }
|
|
HAILO_OK=0; MOVENET_OK=0
|
|
if [ -f "$HEF_DEST" ] && pyhas hailo_platform; then HAILO_OK=1; fi
|
|
if [ -f "$ONNX_DEST" ] && pyhas onnxruntime; then MOVENET_OK=1; fi
|
|
if [ "$HAILO_OK" = 1 ]; then
|
|
POSE_BACKEND=hailo
|
|
echo " -> Hailo HEF + hailo_platform present; POSE_BACKEND=hailo"
|
|
elif [ "$MOVENET_OK" = 1 ]; then
|
|
POSE_BACKEND=movenet
|
|
echo " -> CPU MoveNet usable; POSE_BACKEND=movenet"
|
|
else
|
|
echo "" >&2
|
|
echo "ERROR: no usable pose backend in this bundle — refusing to install a daemon that" >&2
|
|
echo "cannot start." >&2
|
|
echo " Hailo : HEF=$(state test -f "$HEF_DEST") hailo_platform=$(state "$PREFIX/venv/bin/python" -c 'import hailo_platform')" >&2
|
|
echo " MoveNet: onnx=$(state test -f "$ONNX_DEST") onnxruntime=$(state "$PREFIX/venv/bin/python" -c 'import onnxruntime')" >&2
|
|
echo " Rebuild the bundle for the target backend (deploy/bundle/build_offline_bundle.sh)." >&2
|
|
exit 1
|
|
fi
|
|
|
|
install -d -m 0750 "$STATE"
|
|
chown "$SVC_USER":"$SVC_USER" "$STATE"
|
|
install -d -m 0750 "$ETC"
|
|
if [ ! -f "$ETC/edge.env" ]; then
|
|
install -m 0640 "$HERE/deploy/systemd/move-detect-edge.env.example" "$ETC/edge.env"
|
|
# Reconcile the template's POSE_BACKEND to the one that actually works in this bundle.
|
|
sed -i "s|^POSE_BACKEND=.*|POSE_BACKEND=$POSE_BACKEND|" "$ETC/edge.env"
|
|
if [ "$POSE_BACKEND" = hailo ]; then
|
|
grep -q '^HAILO_HEF=' "$ETC/edge.env" || printf 'HAILO_HEF=%s\n' "$HEF_DEST" >> "$ETC/edge.env"
|
|
fi
|
|
chown root:"$SVC_USER" "$ETC/edge.env"
|
|
echo " -> wrote $ETC/edge.env (POSE_BACKEND=$POSE_BACKEND; EDIT IT: DEVICE_ID, DEVICE_SECRET, CAFE24_URL)"
|
|
else
|
|
echo " -> $ETC/edge.env already exists; left unchanged"
|
|
fi
|
|
|
|
echo ">> [6/6] systemd unit"
|
|
install -m 0644 "$HERE/deploy/systemd/move-detect-edge.service" /etc/systemd/system/
|
|
systemctl daemon-reload
|
|
|
|
echo ""
|
|
echo "Installed. Next:"
|
|
echo " 1. edit $ETC/edge.env (device id/secret, cloud URL; MOVENET_ONNX=$PREFIX/models/movenet_lightning.onnx)"
|
|
echo " 2. sudo systemctl enable --now move-detect-edge"
|
|
echo " 3. journalctl -u move-detect-edge -f"
|