move_detect/deploy/README.md
유창욱 9ed759befc docs(deploy): fix MySQL/runbook/secret guidance; offline camera probe
- server-deploy.md: state MySQL 8.0.13+ / MariaDB 10.2+ is required (expression
  defaults), not "5.7+" — an import against 5.7 is a day-1 ERROR 1064 blocker.
- pilot-runbook: add the air-gapped Pi path and mark pip/fetch as build-machine
  only; stop the shipped doc from telling a Pi operator the bundle "does not exist".
- install_offline.sh: instruct the camera probe before enable (mirrors setup_online).
- setup_online.sh: chmod 600 device.env on read; README documents delete + rotate.
- systemd unit: order After/Wants=time-sync.target so occurred_at is real wall time.
2026-07-25 19:21:01 +09:00

171 lines
7.9 KiB
Markdown

# Edge deployment (Raspberry Pi 5, air-gapped / 폐쇄망)
Offline-installable bundle + systemd unit for the `move-detect` edge daemon.
The bundle is fully self-contained (all wheels + pose model + source): the Pi
performs **zero network fetches** at install or run time. The only runtime
network use is the server POST / config-poll / heartbeat, and even that tolerates
being offline (events buffer in the local sqlite queue and flush on reconnect).
> Scope: this covers the **CPU MoveNet + USB (UVC) webcam** path — the one the
> daemon wires today (`edge/daemon.py:main`) and the one verified end-to-end.
> Hailo-8 acceleration (`edge/adapters/hailo_pose.py`) and the CSI camera
> (`edge/adapters/picamera_source.py`) are separate device-validated paths — see
> `docs/hardware-bringup.md`.
## Files
| File | Runs on | Purpose |
|------|---------|---------|
| `bundle/build_offline_bundle.sh` | build machine (has internet) | downloads wheels + model, stages source, emits one tarball |
| `bundle/install_offline.sh` | the Pi (no internet) | venv + `--no-index` install, systemd unit, service account |
| `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 the built delivery
folder onto the Pi — **copy the working tree, not a fresh `git clone`**: the pose model
`models/yolov8s_pose.hef` is git-ignored (a build artifact), so a clone arrives without it.
Use `scp -r`/USB from a build machine that has already staged `models/` (see step 1). 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, **places the pose model from the delivery's
`models/`** (it does NOT download models on the Pi — the HEF/ONNX are fetched on the build
machine per the air-gap rule, `build pulls, target doesn't`), writes `/etc/move-detect/edge.env`
(prompting for `DEVICE_ID` / `DEVICE_SECRET` / `CAFE24_URL` / `CAMERAS`, or take them from env
vars), probes every camera, installs the systemd unit, and starts the service. It uses Hailo
when `models/yolov8s_pose.hef` is present (or pass `HAILO_HEF=/path/to.hef`), else CPU MoveNet
when `models/movenet_lightning.onnx` + onnxruntime are present. The rest of this doc is the
**air-gapped** path.
### Secret handling (`deploy/device.env`)
If you pre-provision with `deploy/device.env`, that file holds this device's **HMAC secret in
plaintext**. It is gitignored (never committed), but it rides onto the Pi with the "copy the
working tree" transfer. After a successful install:
1. **Delete it:** `shred -u deploy/device.env` (the live secret now lives only in
`/etc/move-detect/edge.env`, `0640 root:movedetect`). The installer already `chmod 600`s it
while running, but that is defense-in-depth, not a substitute for removing it.
2. **Rotate if it was ever exposed** (shared over chat, a shell history, a shared drive):
update `move_devices.secret` on the server and the `DEVICE_SECRET` in the Pi's
`/etc/move-detect/edge.env`, then restart the service. A leaked secret lets anyone forge
signed events/heartbeats for this device.
Prefer `device.env` over passing `DEVICE_SECRET=...` on the `sudo` command line, which leaves the
secret in the shell history.
---
## 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
downloaded wheels match the target exactly:
```bash
deploy/bundle/build_offline_bundle.sh # native aarch64 wheels
```
From an x86 box you can attempt a cross build (best-effort — onnxruntime/opencv
wheel tags may not resolve; if it fails, build natively):
```bash
deploy/bundle/build_offline_bundle.sh --cross # target aarch64 / cp311
```
Output: the folder `dist/2_pi_install/` (+ `MANIFEST.txt` listing the bundled
wheels). Copy the WHOLE FOLDER across the air gap as-is — no archive step.
Set `MAKE_TARBALL=1` if you would rather transfer one
`dist/2_pi_install-aarch64-cp311-hailo8.tar.gz` file.
## 2. Transfer + install (on the air-gapped Pi)
Copy `dist/2_pi_install/` across the gap (USB, one-way transfer, etc.), then:
```bash
# tarball variant only (MAKE_TARBALL=1): tar xzf 2_pi_install-aarch64-cp311-hailo8.tar.gz
cd 2_pi_install
sudo ./install_offline.sh
```
The installer: creates the `movedetect` service account (in `video`/`render`/
`plugdev`/`hailo` groups), lays down source + model under `/opt/move-detect`,
builds a venv and installs the bundled wheels with `--no-index`, verifies the
package imports, seeds `/etc/move-detect/edge.env`, and installs the systemd unit.
## 3. Configure + start
```bash
sudo nano /etc/move-detect/edge.env # DEVICE_ID, DEVICE_SECRET, CAFE24_URL, ...
sudo systemctl enable --now move-detect-edge
journalctl -u move-detect-edge -f
```
`DEVICE_ID`/`DEVICE_SECRET` must match a `devices` row in the server (see
`server/docs/server-deploy.md` §6). `MOVENET_ONNX` should point at the bundled
model (`/opt/move-detect/models/movenet_lightning.onnx`).
## 4. Verify the air gap (do this before trusting a build)
- [ ] On a host with **networking disabled**, `install_offline.sh` completes —
the `--no-index` installs prove nothing is fetched. If pip tries to reach
PyPI, a wheel is missing from the bundle: rebuild (step 1) natively.
- [ ] Step [4/6] of the installer prints `edge import OK` (onnxruntime + cv2 +
numpy + requests all import from the venv).
- [ ] `systemctl status move-detect-edge` is `active (running)`; the log shows
frames processed and (if the server is reachable) heartbeats.
- [ ] Pull the network cable → events keep queueing (`QUEUE_DB` grows) → restore
→ the queue flushes. (End-to-end behavior verified in the edge e2e harness.)
## Hailo-8 HAT + CSI camera path (optional, faster)
The default bundle runs CPU MoveNet + USB webcam. To use the Hailo-8 accelerator
and/or a CSI camera, add these SYSTEM packages on the Pi (they are apt packages, not
pip wheels — the venv sees them because it is created with `--system-site-packages`):
```bash
sudo apt install -y hailo-all python3-picamera2 python3-opencv
```
- `hailo-all` — HailoRT runtime + driver + the Python `hailo_platform` bindings.
- `python3-picamera2` — CSI camera stack (libcamera).
Then place the pose model and switch backends in `/etc/move-detect/edge.env`:
```ini
POSE_BACKEND=hailo
HAILO_HEF=/opt/move-detect/models/yolov8s_pose.hef # copy the compiled HEF here
CAMERA_BACKEND=csi
CSI_DEVICE=0
```
Validate on device per `docs/hardware-bringup.md` before trusting output (the Hailo
decode is unit-locked, but the HailoRT inference + picamera2 color order are hardware-
validated). USB webcam stays available anytime via `CAMERA_BACKEND=uvc`.
**Multiple cameras at once.** Set `CAMERAS=backend:device:camera_id,...` to run several
cameras in one daemon — each gets its own detection pipeline and `camera_id`, they share
the Hailo device (round-robin) and the offline queue, and server zones are routed to the
matching camera. Example — CSI + USB together:
```ini
CAMERAS=csi:0:cam-01,uvc:0:cam-02
```
Each `camera_id` must match the `camera_id` on that camera's zones in the server.
> **Air gap:** `hailo-all` / `python3-picamera2` are the ONE piece this bundle can't
> carry (Hailo's own driver + apt tree). Pre-install them on the Pi image, or transfer
> their `.deb`s separately, before running `install_offline.sh`. The pose HEF is
> likewise supplied out-of-band (compiled from the Hailo Model Zoo).
## Not bundled (by design)
- **MySQL / the server app** — a separate host; see `server/docs/server-deploy.md`.
- **HailoRT + `yolov8s_pose.hef` + picamera2** — Hailo's runtime/driver, the compiled
HEF, and the CSI camera stack are Pi system packages installed out-of-band (above);
the default CPU MoveNet + USB path needs none of them.