- Add "Bootstrap Role (v2.1.0)" section to Eye-Remote-Implementation.md with capabilities, use cases, architecture, configuration, workflow, and security considerations - Update Table of Contents in Eye-Remote-Implementation.md to include bootstrap section - Add bootstrap reference to Eye Remote section in Home.md - Add bootstrap functionality note to Architecture-Boot.md with cross-reference to Eye-Remote-Bootstrap.md This cross-references the new Eye-Remote-Bootstrap feature across related documentation pages. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
17 KiB
Eye Remote v2.0.0 Implementation Guide
Table of Contents
- Project Overview
- Implementation Timeline
- Architecture Design
- Component Details
- Build System
- Display Configuration
- API Reference
- Development Tools
- Bootstrap Role
- Deployment
- Lessons Learned
Project Overview
Purpose
SecuBox Eye Remote provides a dedicated physical display for SecuBox security appliances. The circular 480x480 LCD shows real-time system metrics, alerts, and status information.
Goals Achieved
- Offline-capable SD card image (no internet at boot)
- USB OTG connectivity with fallback to WiFi
- Secure device pairing via QR codes
- Real-time metrics dashboard
- Chromium kiosk mode auto-start
- HyperPixel 2.1 Round display support
- Gateway emulator for development
Tech Stack
| Layer | Technology |
|---|---|
| OS | Raspberry Pi OS Bookworm (armhf) |
| Display | X11 + fbdev + DPI framebuffer |
| Window Manager | Openbox |
| Browser | Chromium (kiosk mode) |
| Backend | FastAPI + Uvicorn |
| Auth | JWT + Device Tokens |
| Build | QEMU ARM emulation |
Implementation Timeline
Task Breakdown (14 Tasks)
| Task | Component | Status |
|---|---|---|
| 1 | Metrics Models (models/metrics.py) |
✅ |
| 2 | Device Models (models/device.py) |
✅ |
| 3 | Pairing Models (models/pairing.py) |
✅ |
| 4 | Metrics Collection (core/metrics.py) |
✅ |
| 5 | Device Registry (core/device_registry.py) |
✅ |
| 6 | Pairing Manager (core/pairing.py) |
✅ |
| 7 | Devices Router (api/routers/devices.py) |
✅ |
| 8 | Pairing Router (api/routers/pairing.py) |
✅ |
| 9 | Metrics Router (api/routers/metrics.py) |
✅ |
| 10 | Gateway Emulator (tools/secubox-eye-gateway/) |
✅ |
| 11 | Debian Packaging (debian/) |
✅ |
| 12 | Build Script Update | ✅ |
| 13 | Integration Tests | ✅ |
| 14 | HyperPixel Fix (Legacy DPI) | ✅ |
Development Approach
Used Subagent-Driven Development:
- Fresh subagent dispatched per task
- Spec compliance review after implementation
- Code quality review before completion
- Two-stage review ensures correctness
Architecture Design
System Architecture
SecuBox Appliance
┌─────────────────────────┐
│ │
│ ┌─────────────────┐ │
│ │ secubox-eye- │ │
│ │ remote.service │ │
│ │ │ │
│ │ FastAPI:8000 │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ USB Host │ │
│ │ 10.55.0.1 │ │
│ └────────┬────────┘ │
│ │ │
└───────────┼────────────┘
│ USB OTG
│ (ECM + ACM)
┌───────────┼────────────┐
│ │ │
│ ┌────────▼────────┐ │
│ │ USB Gadget │ │
│ │ 10.55.0.2 │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ nginx :8080 │ │
│ │ proxy → :8000 │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ Chromium Kiosk │ │
│ │ localhost:8080 │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ HyperPixel │ │
│ │ 480x480 LCD │ │
│ └─────────────────┘ │
│ │
│ RPi Zero W │
└─────────────────────────┘
Data Flow
┌──────────┐ GET /metrics ┌──────────┐
│ Dashboard│◄──────────────────►│ FastAPI │
│ (JS) │ JSON response │ Backend │
└────┬─────┘ └────┬─────┘
│ │
│ fetch() every 2s │ reads /proc/*
│ │ os.statvfs()
▼ ▼
┌──────────┐ ┌──────────┐
│ Canvas │ │ Linux │
│ Rings │ │ Kernel │
└──────────┘ └──────────┘
Component Details
Models Layer
models/metrics.py
class SystemMetrics(BaseModel):
cpu_percent: float # 0-100
memory_percent: float # 0-100
disk_percent: float # 0-100
cpu_temp: float # Celsius
load_avg_1: float # 1-minute load
uptime_seconds: int
hostname: str
timestamp: datetime
models/device.py
class Device(BaseModel):
id: str # UUID
name: str
device_type: str # "eye-remote"
paired_at: datetime
last_seen: datetime
is_active: bool
class DeviceToken(BaseModel):
device_id: str
token_hash: str # SHA256
created_at: datetime
expires_at: Optional[datetime]
models/pairing.py
class PairingSession(BaseModel):
session_id: str
gateway_url: str
expires_at: datetime
qr_data: str # JSON for QR code
class PairingRequest(BaseModel):
session_id: str
device_name: str
device_type: str = "eye-remote"
Core Layer
core/metrics.py
- Reads CPU from
/proc/stat(delta calculation) - Reads memory from
/proc/meminfo - Reads disk via
os.statvfs('/') - Reads temperature from
/sys/class/thermal/thermal_zone0/temp - Caches results for 1 second (async)
core/device_registry.py
- Stores devices in
/var/lib/secubox/eye-remote/devices.json - Token validation with
secrets.compare_digest() - SHA256 token hashing
- Thread-safe file operations
core/pairing.py
- 5-minute session TTL
- QR code contains: gateway URL, session ID, timestamp
- Single-use sessions (consumed on completion)
API Layer
Authentication
# JWT for management endpoints
async def require_jwt(token: str = Depends(oauth2_scheme)):
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
return payload
# Device token for metrics endpoint
async def require_device_token(x_device_token: str = Header(...)):
device = registry.validate_token(x_device_token)
return device
Endpoints
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /devices/ |
JWT | List paired devices |
| POST | /devices/pair |
JWT | Pair new device |
| DELETE | /devices/{id} |
JWT | Unpair device |
| POST | /pairing/start |
JWT | Start pairing session |
| GET | /pairing/qr/{session_id} |
JWT | Get QR code PNG |
| POST | /pairing/complete |
None | Complete pairing (from device) |
| GET | /metrics/ |
Device Token | Get system metrics |
Build System
QEMU-Based Offline Build
The build script creates a complete SD card image with all packages pre-installed using QEMU ARM emulation:
┌─────────────────────────────────────────────────────┐
│ Build Process │
├─────────────────────────────────────────────────────┤
│ 1. Decompress RPi OS Lite image │
│ 2. Expand image by 1GB for packages │
│ 3. Mount via loopback device │
│ 4. Copy qemu-arm-static for ARM emulation │
│ 5. chroot and apt-get install packages │
│ 6. Configure HyperPixel (legacy DPI mode) │
│ 7. Install Eye Remote services │
│ 8. Configure kiosk (LightDM + Openbox + Chromium) │
│ 9. Cleanup and create final image │
└─────────────────────────────────────────────────────┘
Packages Pre-installed
chromium-browser xserver-xorg xinit
lightdm openbox unclutter
nginx python3-pigpio pigpio
i2c-tools fonts-dejavu-core
Display Configuration
The HyperPixel Problem
Issue: Pi Zero W does not support KMS (Kernel Mode Setting) properly.
Symptom: Black screen with KMS overlays (vc4-kms-v3d, vc4-kms-dpi-hyperpixel2r).
Solution: Use legacy DPI mode with manual pixel timings.
Working Configuration
# /boot/config.txt
# Legacy DPI overlay (NOT KMS)
dtoverlay=hyperpixel2r
# Manual DPI timings for ST7701S LCD
enable_dpi_lcd=1
display_default_lcd=1
dpi_group=2
dpi_mode=87
dpi_output_format=0x7f216
dpi_timings=480 0 10 16 55 480 0 15 60 15 0 0 0 60 0 19200000 6
framebuffer_width=480
framebuffer_height=480
ST7701S LCD Initialization
The HyperPixel 2.1 Round uses an ST7701S LCD controller that requires SPI initialization at boot:
# /usr/bin/hyperpixel2r-init (simplified)
import pigpio
pi = pigpio.pi()
# Bit-bang SPI commands to ST7701S
# Initialize display registers
# Enable backlight
pi.stop()
Service Dependencies
pigpiod.service
└── hyperpixel2r-init.service
└── lightdm.service
└── chromium (kiosk)
API Reference
GET /api/v1/eye-remote/metrics/
Auth: X-Device-Token header
Response:
{
"cpu_percent": 23.5,
"memory_percent": 45.2,
"disk_percent": 28.0,
"cpu_temp": 48.3,
"load_avg_1": 0.42,
"uptime_seconds": 86400,
"hostname": "secubox-pro",
"timestamp": "2026-04-21T20:30:00Z"
}
POST /api/v1/eye-remote/pairing/start
Auth: JWT Bearer token
Response:
{
"session_id": "abc123",
"expires_at": "2026-04-21T20:35:00Z",
"qr_url": "/api/v1/eye-remote/pairing/qr/abc123"
}
POST /api/v1/eye-remote/pairing/complete
Auth: None (uses session_id)
Request:
{
"session_id": "abc123",
"device_name": "Living Room Eye",
"device_type": "eye-remote"
}
Response:
{
"device_id": "dev_xyz789",
"token": "raw_token_for_device_storage",
"gateway_url": "http://10.55.0.1:8000"
}
Development Tools
Gateway Emulator
Simulates SecuBox metrics without physical hardware:
# Install
pip install -e tools/secubox-eye-gateway/
# Run with profile
secubox-eye-gateway --profile stressed --port 8765
# Profiles:
# idle - Low activity (CPU ~5%, temp ~40°C)
# normal - Typical load (CPU ~25%, temp ~50°C)
# busy - Heavy load (CPU ~65%, temp ~62°C)
# stressed - Near limits (CPU ~85%, temp ~72°C)
Emulator Endpoints
| Endpoint | Description |
|---|---|
GET /api/v1/health |
Health check |
GET /api/v1/system/metrics |
Simulated metrics |
GET /api/v1/eye-remote/discover |
Discovery info |
Bootstrap Role (v2.1.0)
The Eye Remote can serve as a boot device for ESPRESSObin and MOCHAbin appliances, providing critical functionality during system initialization and recovery:
Capabilities
- Mass Storage LUN — U-Boot loads kernel/DTB/initrd from active slot via USB Mass Storage
- TFTP Shadow — Test new images before promoting to active slot
- Atomic Swap — Safe active/shadow slot exchange with rollback protection
- Boot Menu — Interactive selection of kernel versions and boot parameters
Use Cases
| Use Case | Benefit |
|---|---|
| Factory Reset | Boot fresh image without physical SD cards |
| Recovery Boot | Fallback when internal storage is corrupted |
| Image Testing | Try new Debian images before committing |
| Firmware Updates | Safe A/B slot switching with 4R rollback |
Architecture
The Eye Remote exposes two USB LUNs:
-
LUN 0 — Mass Storage (active boot image)
- Kernel + DTB + initrd partition
- Auto-synced from active slot
-
LUN 1 — Shadow Test Storage
- Staging area for new images
- Isolated from live system
Configuration
Bootstrap is configured in /etc/secubox/eye-remote.toml:
[bootstrap]
enabled = true
mass_storage_lun = 0
shadow_lun = 1
auto_sync_interval = 300 # 5 minutes
max_rollback_slots = 4
Workflow
1. User initiates image test via dashboard
↓
2. Eye Remote downloads image to shadow LUN
↓
3. User selects "Boot from Shadow" in U-Boot menu
↓
4. System tests image on next reboot
↓
5. User confirms success → Image promoted to active
↓
6. Or rejects → Automatic rollback to previous active
Security Considerations
- Bootstrap operations are JWT-authenticated only
- All slot operations are logged to audit trail
- Image integrity verified via SHA256 checksums
- Rollback protected against malicious substitution via timestamp validation
Full documentation: See Eye-Remote-Bootstrap for detailed implementation, testing procedures, and troubleshooting.
Deployment
SD Card Flashing
# Build image
sudo ./remote-ui/round/build-eye-remote-image.sh \
-i raspios-lite-armhf.img.xz \
-s "WiFiSSID" -p "password"
# Flash
sudo dd if=/tmp/secubox-eye-remote-2.0.0.img \
of=/dev/sdX bs=4M status=progress
# Or compressed
xz -9 /tmp/secubox-eye-remote-2.0.0.img
xzcat *.img.xz | sudo dd of=/dev/sdX bs=4M status=progress
First Boot Checklist
- Insert SD card
- Connect USB DATA port (middle) to SecuBox
- Wait 60 seconds
- Display shows dashboard
- SSH accessible:
ssh pi@10.55.0.2
OTA Updates
# Update dashboard
scp index.html pi@10.55.0.2:/var/www/secubox-round/
# Update backend (on SecuBox host)
apt update && apt install secubox-eye-remote
# Restart services
systemctl restart secubox-eye-remote
Lessons Learned
1. KMS vs Legacy DPI
Problem: Assumed KMS would work everywhere since it's the "modern" approach.
Reality: Pi Zero W (BCM2835) has limited GPU support. KMS overlays cause black screen.
Solution: Always test on actual hardware. Use legacy DPI mode for Pi Zero W.
2. ST7701S Initialization
Problem: Display stayed black even with correct overlay.
Reality: ST7701S LCD controller requires SPI commands at boot.
Solution: hyperpixel2r-init script using pigpio for GPIO/SPI access.
3. QEMU Package Installation
Problem: Pi Zero W is slow. Installing packages at first boot takes forever.
Reality: ARM emulation via QEMU allows pre-installing packages during image build.
Solution: QEMU chroot in build script. Offline-capable images.
4. Token Security
Problem: Device tokens stored in plain text.
Reality: Compromised token = unauthorized access.
Solution: SHA256 hash stored server-side. secrets.compare_digest() for timing-safe comparison.
References
CyberMind · SecuBox Eye Remote v2.0.0 · April 2026