Jetson edge-compute spec
The Jetson Orin Nano Super Dev Kit (8GB, 67 TOPS, 1024 CUDA cores) is the inference + recording + dashboard box for Phase I. It must boot headless, auto-recover from process crashes and power events, expose itself only through Tailscale, decode 2 concurrent H.264 sub-streams via NVDEC, run YOLOv8n in TensorRT (person class only), write events to SQLite, serve a Flask/FastAPI dashboard on :5000, and segment-record video to a 4TB NVMe for ~120 days of rolling history.
Both Jetsons (LBZF + ITBA twin) are pre-flashed in California per ADR-006 before the 2026-05-15 BA flight — once the LBZF unit lands in Pereira, the only inbound channel is Tailscale, so every “what happens when X breaks” path must be in place before takeoff.
Hardware (locked 2026-05-11)
Section titled “Hardware (locked 2026-05-11)”| Item | Choice | Notes |
|---|---|---|
| SBC | NVIDIA Jetson Orin Nano Super Developer Kit (8GB) | MAXN power profile for super-mode 67 TOPS |
| Carrier | Bundled with dev kit | The bare module variant is harder to deploy — verify dev-kit SKU on box |
| Enclosure | Waveshare aluminum Case A (Amazon B0CG38BS5S) | Passive — fan NOT in the box. Add Waveshare PWM fan B0C1TYVDTF + low-profile M.2 NVMe heatsink + thermal pad |
| Primary storage | Samsung 990 EVO Plus 4TB NVMe (M.2 2280, PCIe Gen4/5) | Trains down to Gen3 x4 on the Orin carrier — fine |
| Boot media | SanDisk 64GB Extreme microSD | Initial JetPack boot only; migrate root to NVMe via jetsonhacks/bootFromExternalStorage after first boot |
| Power | Included 19V barrel-jack supply | Do NOT power via USB-C — community reports of brownout / recovery-mode failures over USB-C |
| Input | Logitech MK120 wired keyboard/mouse | CA bring-up only; runs headless after deploy |
OS and base image
Section titled “OS and base image”| Item | Choice | Why |
|---|---|---|
| OS | JetPack 6.0 or 6.1 (Ubuntu 22.04 LTS arm64) | NOT 6.2 — JetPack 6.2 ships TensorRT 10.x which currently breaks INT8 quantization for YOLOv8 per Ultralytics docs. 6.0/6.1 ships TRT 8.6 (ADR-003) |
| Hostname | lbzf-jetson-01 (LBZF) / itba-jetson-01 (ITBA twin) | Tailscale auto-uses hostname |
| Default user | lbzf (LBZF) / itba (ITBA twin); delete nvidia / ubuntu after first boot | Factory-default nvidia is a brute-force magnet |
| Locale | en_US.UTF-8 system locale; dashboard strings localized to Spanish in the frontend bucket | Logs in English, UI in ES |
| Time zone | America/Bogota (LBZF) / America/Argentina/Buenos_Aires (ITBA) | Cycle-event timestamps must match local whiteboard hora |
Storage layout
Section titled “Storage layout”Root partition lives on the NVMe (not microSD); the microSD is recovery-only after the post-first-boot migration.
| Mount | Device | FS | Options | Size | Purpose |
|---|---|---|---|---|---|
/ | /dev/nvme0n1p1 | ext4 | defaults | ~50 GB | OS, packages, app code |
/var/log | /dev/nvme0n1p2 | ext4 | noatime | ~20 GB | Journal + app logs, rotated |
/data | /dev/nvme0n1p3 | ext4 | noatime,lazytime | ~3.9 TB | Rolling video buffer + SQLite + Excel exports |
| swap | /swapfile | swap | file-backed | 8 GB | Predictable I/O — avoid zram for Phase I |
Filesystem choice: ext4 with noatime on /data. F2FS has weaker unclean-shutdown recovery — bad fit for factory power. The rolling video buffer is pre-allocated as a ring of fixed-size files rather than growing organically; this keeps free-space behavior predictable and lets the eviction script (below) operate by oldest-file-first without surprising the underlying FS.
Directory plan under /data
Section titled “Directory plan under /data”/data/ video/ cam1/YYYY/MM/DD/cam1_HHMMSS.mp4 cam2/... db/ lbzf.sqlite # canonical store lbzf.sqlite-wal # SQLite WAL mode (concurrent dashboard reads safe) exports/ INDICADORES_YYYY-MM.xlsx models/ yolov8n.pt # source weights yolov8n.engine # TensorRT engine (Jetson-specific; rebuilt per device) logs/ pipeline.logPackages
Section titled “Packages”System packages (apt):
python3.11,python3.11-venv,python3-pip- GStreamer:
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav - NVIDIA GStreamer plugins ship with JetPack (
libgstnvvideo4linux2.soprovidesnvv4l2decoder) tailscalefrom the official Tailscale apt repo, pinned to a known versionsqlite3,tmux,htop,iotop,nvtop,ufwunattended-upgrades— security patches only, never unattended kernel jumps
OpenCV with GStreamer is non-negotiable. Default JetPack OpenCV often ships without GStreamer support, which silently breaks the entire RTSP pipeline. Either:
- Rebuild OpenCV from source with
-DWITH_GSTREAMER=ON, or - Use a Q-engineering prebuilt wheel.
Verify with:
import cv2; print(cv2.getBuildInformation())and grep for GStreamer: YES. The G1 gate (10-gates/G1-argentina-demo.md) has this as a check.
Python packages (pip into /opt/lbzf/venv):
ultralytics(pinned to a version bench-tested against TensorRT 8.6 on JetPack 6.0/6.1)flaskorfastapi+uvicorn(Phase I default = Flask)pandas,openpyxltensorrt(pre-installed via JetPack; verifyimport tensorrtworks with--system-site-packagesin the venv)
YOLOv8 + TensorRT deployment
Section titled “YOLOv8 + TensorRT deployment”The TensorRT engine is hardware-bound — an engine compiled on a MacBook will not run on a Jetson, and an engine compiled on the LBZF Jetson is not guaranteed to run on the ITBA Jetson even though they’re the same SKU. Compile on each device individually (ADR-003, ADR-006).
-
Copy
yolov8n.ptto/data/models/on the target Jetson. -
Export to TensorRT on the Jetson itself:
Terminal window yolo export model=/data/models/yolov8n.pt format=engine device=0 half=True -
Verify FP16 inference on a sample frame; record per-frame latency. Phase I targets per-frame inference < 50ms under concurrent 2-stream load.
Capture and inference pipeline
Section titled “Capture and inference pipeline”Per-camera pipeline (one process or thread per stream):
rtspsrc location=rtsp://<user>:<pwd>@<cam-ip>:554/cam/realmonitor?channel=1&subtype=1 → rtph264depay → h264parse → nvv4l2decoder → appsinkAmcrest exposes RTSP at /cam/realmonitor?channel=1&subtype=1 (see network-and-tailscale.md for the full URL pattern and credentials). Sub-stream presets are 704×480 / 352×240 / CIF / QCIF; 640×480 is NOT a preset. Ingest the closest preset and downscale on the Jetson to whatever the model expects (ADR-004).
Inference runs at 3–5 fps, downscaled on the Jetson from the ingested sub-stream (revised down from the v1.9 plan per ADR-004). Sewing-workstation cycles are 10–90 seconds long; 3–5 fps gives 5–25 frames per cycle, plenty for “person enters ROI → person leaves ROI” with hysteresis, and keeps the Jetson well below thermal throttling and NVMe TBW concerns.
The same RTSP stream is tee’d to a recording branch using splitmuxsink (10-minute segments). Phase I records the sub-stream only; main-stream recording is deferred to Phase II forensic playback.
systemd service
Section titled “systemd service”File: /etc/systemd/system/lbzf-monitor.service
[Unit]Description=LBZF CV monitor (capture + inference + recording + dashboard)After=network-online.target tailscaled.serviceWants=network-online.target
[Service]Type=simpleUser=lbzfGroup=lbzfWorkingDirectory=/opt/lbzf/appExecStart=/opt/lbzf/venv/bin/python -m lbzf.mainRestart=alwaysRestartSec=10StandardOutput=append:/data/logs/pipeline.logStandardError=append:/data/logs/pipeline.log
NoNewPrivileges=trueProtectSystem=strictReadWritePaths=/data /var/logProtectHome=yesPrivateTmp=yes
MemoryMax=6GTasksMax=512
[Install]WantedBy=multi-user.targetA separate lbzf-dashboard.service runs the Flask app so a dashboard crash doesn’t take down recording. If the backend bucket consolidates them, document the rationale there.
Auto-recovery
Section titled “Auto-recovery”Three layers:
- systemd
Restart=always— process-level crash recovery, 10s backoff. - Hardware watchdog — enable Jetson’s
/dev/watchdogwith a 60s timeout; systemd’sRuntimeWatchdogSec=30sin/etc/systemd/system.conftriggers a reset if PID 1 hangs. /healthzendpoint —lbzf-monitorreturns 200 if all cameras are streaming and the last cycle event is within2× max_expected_cycle_timeper camera (not globally — SAM varies per station). A 60s cron polls; 3 consecutive non-200s callssystemctl restart lbzf-monitor.
Disk-full / rolling-buffer eviction
Section titled “Disk-full / rolling-buffer eviction”A lbzf-eviction.timer fires every 10 minutes and runs /opt/lbzf/bin/evict.py:
THRESHOLD = 0.92TARGET = 0.80while disk_usage('/data') > THRESHOLD: oldest = sorted(glob('/data/video/cam*/**/*.mp4'), key=os.path.getmtime)[0] os.remove(oldest) if disk_usage('/data') < TARGET: breakTunables in /etc/lbzf/eviction.conf. SQLite + exports are never evicted; only video segments under /data/video/ are eligible.
lbzf-monitor issues PRAGMA wal_checkpoint(TRUNCATE) once per hour so the -wal sidecar doesn’t grow unbounded.
Time sync
Section titled “Time sync”systemd-timesyncd enabled, pointing to:
- LBZF:
0.co.pool.ntp.org,1.co.pool.ntp.org,time.cloudflare.com(fallback) - ITBA:
0.ar.pool.ntp.org,1.ar.pool.ntp.org,time.cloudflare.com
Verify with timedatectl status after first boot.
SSH + firewall hardening
Section titled “SSH + firewall hardening”- Delete factory
nvidia/ubuntuaccounts afterlbzf/itbais created. - Strong passphrase in 1Password and key-based SSH only. Sophia + Andrew public keys baked into
~lbzf/.ssh/authorized_keys; ITBA team keys baked into~itba/.ssh/authorized_keysper argentina-twin-set.md. /etc/ssh/sshd_config:PasswordAuthentication noPermitRootLogin noAllowUsers lbzf(oritba)
ufwdefault deny inbound; allow:tailscale0interface (everything from Tailscale, per ADR-005)eth0from192.168.1.0/24on TCP5000(dashboard, local LAN)- Outbound RTSP to the Amcrest camera VLAN
unattended-upgradeswithUnattended-Upgrade::Automatic-Reboot "false"— no 4am reboot during a shift.
Tailscale install
Section titled “Tailscale install”Per ADR-005, Tailscale is the only remote-access surface for Phase I.
LBZF:
curl -fsSL https://tailscale.com/install.sh | shsudo tailscale up --ssh --hostname=lbzf-jetson-01 --advertise-tags=tag:lbzf-jetsonITBA twin (installed, NOT yet authenticated — ITBA brings it up on first boot in BA):
sudo tailscale up --ssh --hostname=itba-jetson-01 --advertise-tags=tag:itba-devMark both devices “Key expiry: never” in the Tailscale admin console — the default 180-day expiry would silently disconnect the Jetson and recovery would be a flight.
Cross-bucket dependencies
Section titled “Cross-bucket dependencies”- Network (network-and-tailscale.md): IP plan, RTSP URL pattern, Tailscale ACLs.
- Backend bucket: the actual
lbzf.mainmodule, the/healthzcontract, the dashboard binding. - Frontend bucket: dashboard rendering on
:5000, Spanish localization. - Business bucket: confidentiality — video files contain identifiable operators; 120-day retention and
Confidencial — Uso Internoclassification are enforced via the Tailscale ACL.
Rollout
Section titled “Rollout”Tracked at the G1 gate. Summary:
- Pre-flight (CA, before 2026-05-15) — flash both Jetsons with JetPack 6.0/6.1; migrate root to NVMe; build OpenCV with GStreamer; compile TensorRT engine on each Jetson; smoke-test against one Amcrest camera.
- 2026-05-15 — Sophia + Armando fly CA → BA with both Jetsons in carry-on.
- Pereira install (July 2026) — LBZF Jetson goes live on site (see install-runbook.md).