feat(deploy): one-command online installer for an internet-connected Pi

deploy/setup_online.sh installs system packages (picamera2, hailo-all best-effort),
builds the venv + pip deps, fetches the MoveNet model, writes /etc/move-detect/edge.env
(prompting or from env vars), installs the systemd unit and starts the service — one
command, no offline bundle. Starts on CPU MoveNet; drop in a HEF for Hailo. numpy left
to the system to avoid shadowing picamera2's build. Air-gapped path (deploy/bundle/*)
unchanged.
This commit is contained in:
유창욱 2026-07-13 23:47:20 +09:00
parent c385be017c
commit e8b75d5366
2 changed files with 132 additions and 0 deletions

View file

@ -21,6 +21,24 @@ being offline (events buffer in the local sqlite queue and flush on reconnect).
| `systemd/move-detect-edge.service` | the Pi | the daemon unit (auto-restart, device access, hardening) |
| `systemd/move-detect-edge.env.example` | the Pi | env template → `/etc/move-detect/edge.env` |
## Quick start — Pi with internet (one command)
If the Pi itself has internet, skip the offline bundle entirely. Copy this repo onto
the Pi (git clone or scp), then:
```bash
sudo deploy/setup_online.sh
```
It installs the system packages (`python3-picamera2`, and `hailo-all` if present),
builds the venv, pip-installs the runtime deps, fetches the MoveNet model, writes
`/etc/move-detect/edge.env` (prompting for `DEVICE_ID` / `DEVICE_SECRET` / `CAFE24_URL` /
`CAMERAS`, or take them from env vars), installs the systemd unit, and starts the service.
It begins on CPU MoveNet; drop a `yolov8s_pose.hef` in and pass `HAILO_HEF=...` (or edit
`edge.env`) to switch to Hailo. The rest of this doc is the **air-gapped** path.
---
## 1. Build the bundle (on a machine WITH internet)
**Most reliable: build on an aarch64 host** (a Pi 5, or an arm64 CI runner) so the

114
deploy/setup_online.sh Normal file
View file

@ -0,0 +1,114 @@
#!/usr/bin/env bash
# ONE-COMMAND online installer for an INTERNET-CONNECTED Raspberry Pi 5.
# Installs everything and starts the service. For AIR-GAPPED Pis use deploy/bundle/*.
#
# Run from a clone/copy of this repo, as root:
# sudo deploy/setup_online.sh
#
# Non-interactive: pass values as env vars, e.g.
# sudo DEVICE_ID=pi-01 DEVICE_SECRET=... CAFE24_URL=https://cloud CAMERAS=csi:0:cam-01,uvc:0:cam-02 \
# HAILO_HEF=/home/pi/yolov8s_pose.hef deploy/setup_online.sh
# Interactive (a real terminal): it prompts for anything not passed.
set -euo pipefail
if [ "$(id -u)" -ne 0 ]; then
echo "run as root: sudo $0" >&2
exit 1
fi
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PREFIX=/opt/move-detect
STATE=/var/lib/move-detect
ETC=/etc/move-detect
SVC_USER=movedetect
echo ">> [1/8] system packages (CSI camera, venv; Hailo runtime best-effort)"
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y python3-picamera2 python3-venv python3-pip
# hailo-all is the Hailo driver+runtime; only present in Pi repos with a Hailo HAT.
# Best-effort so a Pi without it still installs and runs on CPU MoveNet.
apt-get install -y hailo-all || echo " WARN: hailo-all not installed — Hailo accel unavailable, CPU MoveNet will be used."
echo ">> [2/8] service account + device groups"
id -u "$SVC_USER" >/dev/null 2>&1 || useradd --system --no-create-home --shell /usr/sbin/nologin "$SVC_USER"
for g in video render plugdev hailo; do
getent group "$g" >/dev/null 2>&1 && usermod -aG "$g" "$SVC_USER" || true
done
echo ">> [3/8] source + dirs under $PREFIX"
install -d -m 0755 "$PREFIX" "$PREFIX/src" "$PREFIX/models" "$PREFIX/docs"
rm -rf "$PREFIX/src/edge" # full replace on re-run (no nested edge/edge)
cp -r "$REPO_ROOT/edge" "$PREFIX/src/edge"
cp "$REPO_ROOT/pyproject.toml" "$PREFIX/src/" 2>/dev/null || true
find "$PREFIX/src" -name __pycache__ -type d -prune -exec rm -rf {} +
cp "$REPO_ROOT/docs/pilot-runbook.md" "$PREFIX/docs/" 2>/dev/null || true
echo ">> [4/8] venv (+ system site-packages) + pip runtime deps"
python3 -m venv --system-site-packages "$PREFIX/venv"
"$PREFIX/venv/bin/pip" install --upgrade pip >/dev/null
# numpy is intentionally NOT pinned here: it comes in transitively via onnxruntime, and
# leaving the system numpy in place (the venv sees it) avoids shadowing the numpy that
# the apt python3-picamera2 stack was compiled against.
"$PREFIX/venv/bin/pip" install requests onnxruntime opencv-python-headless
echo ">> [5/8] MoveNet CPU model"
( cd "$REPO_ROOT" && "$PREFIX/venv/bin/python" -m edge.tools.fetch_movenet )
cp "$REPO_ROOT/models/movenet_lightning.onnx" "$PREFIX/models/" 2>/dev/null || true
echo ">> [6/8] pose backend"
POSE_BACKEND=movenet
HEF_DEST="$PREFIX/models/yolov8s_pose.hef"
if [ -n "${HAILO_HEF:-}" ] && [ -f "${HAILO_HEF:-}" ]; then
cp "$HAILO_HEF" "$HEF_DEST"
POSE_BACKEND=hailo
echo " -> Hailo HEF installed; POSE_BACKEND=hailo"
else
echo " -> no HAILO_HEF given; starting on CPU MoveNet."
echo " For Hailo: copy yolov8s_pose.hef to $HEF_DEST, set POSE_BACKEND=hailo in"
echo " $ETC/edge.env, then: sudo systemctl restart move-detect-edge"
fi
echo ">> [7/8] config -> $ETC/edge.env"
install -d -m 0750 "$STATE"; chown "$SVC_USER":"$SVC_USER" "$STATE"
install -d -m 0750 "$ETC"
if [ -f "$ETC/edge.env" ]; then
echo " -> $ETC/edge.env already exists; left unchanged"
else
: "${DEVICE_ID:=}"; : "${DEVICE_SECRET:=}"; : "${CAFE24_URL:=}"; : "${CAMERAS:=}"
if [ -t 0 ]; then
[ -n "$DEVICE_ID" ] || read -rp "DEVICE_ID [pi-01]: " DEVICE_ID
[ -n "$DEVICE_SECRET" ] || { read -rsp "DEVICE_SECRET (from cloud): " DEVICE_SECRET; echo; }
[ -n "$CAFE24_URL" ] || read -rp "CAFE24_URL (cloud base URL): " CAFE24_URL
[ -n "$CAMERAS" ] || read -rp "CAMERAS [csi:0:cam-01,uvc:0:cam-02]: " CAMERAS
fi
DEVICE_ID="${DEVICE_ID:-pi-01}"
CAMERAS="${CAMERAS:-csi:0:cam-01,uvc:0:cam-02}"
# Every line is a bare KEY=value (systemd EnvironmentFile= keeps inline text literally).
umask 077
cat > "$ETC/edge.env" <<EOF
DEVICE_ID=$DEVICE_ID
DEVICE_SECRET=$DEVICE_SECRET
CAFE24_URL=$CAFE24_URL
CAMERAS=$CAMERAS
POSE_BACKEND=$POSE_BACKEND
HAILO_HEF=$HEF_DEST
MOVENET_ONNX=$PREFIX/models/movenet_lightning.onnx
QUEUE_DB=$STATE/queue.db
TARGET_FPS=12
EOF
chown root:"$SVC_USER" "$ETC/edge.env"
chmod 0640 "$ETC/edge.env"
[ -n "$DEVICE_SECRET" ] || echo " NOTE: DEVICE_SECRET is empty — set it in $ETC/edge.env before the cloud will accept events."
echo " -> wrote $ETC/edge.env"
fi
echo ">> [8/8] systemd service"
install -m 0644 "$REPO_ROOT/deploy/systemd/move-detect-edge.service" /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now move-detect-edge
echo ""
echo "Done. The edge daemon is installed and started."
echo " logs: journalctl -u move-detect-edge -f"
echo " config: sudo nano $ETC/edge.env (then: sudo systemctl restart move-detect-edge)"