adacam/PROJECT_STATUS.md
Kayos ed7ae5ba57 docs+scripts: project status, build scripts, patched usb-updater v5
- PROJECT_STATUS.md: full project log (hardware, partitions, artifacts, lessons learned, next steps)
- scripts/build/build-artifact-from-existing.py: rebuild artifact from existing data tar with new header
- scripts/build/build-v5-patched-updater.sh: patch system.img usb-updater + build artifact
- recovery/usb-updater-v5-patched: patched usb-updater with SSH recovery prepended

adacam-ssh-fix-v5.mender: 403MB, SHA256 acfbd16db9620f23785f8b103ffaeff6aed780f383273a61a23c8002f2bf0980
Status: PENDING TEST on replacement Bee (192.168.0.10)
2026-03-16 09:58:45 -07:00

269 lines
11 KiB
Markdown

# AdaCam / Hivemapper Bee — Project Status
_Last updated: 2026-03-16_
---
## Goal
Convert Hivemapper Bee dashcams (HDC-S, Intel Keem Bay platform) into "AdaCam" units:
- Block Hivemapper telemetry / OTA
- Install adacam services (GPS forwarding, Wigle logging, etc.)
- Regain persistent root SSH access
- Long-term: custom firmware image, signed USB recovery, own update infrastructure
---
## Hardware
| Item | Value |
|------|-------|
| Platform | Intel Keem Bay (keembay) |
| SoC | Movidius MV0212, Yocto Linux |
| Storage | eMMC, 11 partitions |
| Update mechanism | Hivemapper USB updater (usb-updater.service) + custom Mender fork |
| SSH | dropbear or openssh on port 22 |
| AP interface | wlp1s0f0 (192.168.0.10 factory) |
| WiFi client | wlp1s0f1 |
| LTE | LE910C4-NF (wwan0) |
### Partition Layout
| Partition | Size | Purpose |
|-----------|------|---------|
| mmcblk1p1 | 32MB | ? |
| mmcblk1p2 | 512K | ? |
| mmcblk1p3 | 512K | ? |
| mmcblk1p4 | 256MB | Kernel A (ext4, FIT image) |
| mmcblk1p5 | 3GB | Rootfs A (ext4) |
| mmcblk1p6 | 128MB | dm-verity hashtree A |
| mmcblk1p7 | 256MB | Kernel B |
| mmcblk1p8 | 3GB | Rootfs B (ext4) |
| mmcblk1p9 | 128MB | dm-verity hashtree B |
| mmcblk1p10 | 64MB | /factory |
| mmcblk1p11 | ~52GB | /data (rw, persistent) |
### Filesystem Mounts (critical)
| Mount | Source | Type | Notes |
|-------|--------|------|-------|
| / | mmcblk1p5 or p8 | ext4 | **READ-ONLY** |
| /etc | overlayfs | overlay | lower=rootfs/etc, upper=/data/overlay/current/, workdir=/data/overlay/workdir/ |
| /data | mmcblk1p11 | ext4 | rw, **persists across ALL firmware flashes** |
| /home | /data/home | bind | persistent |
| /var | /data/var | bind | persistent |
| /uboot | mmcblk1p4 | ext4 | U-Boot env partition |
**KEY INSIGHT:** Only `/etc` is overlaid. `/usr`, `/bin`, `/usr/bin` etc. are directly on the read-only rootfs. The overlay upper dir lives on `/data` which survives firmware flashes.
---
## Firmware / Update System
### Mender Fork
- Version: `f9a29241` (custom Hivemapper fork, not standard Mender)
- **Does NOT call state scripts** (confirmed via v4 test — no USB log file written)
- Artifact format (must match exactly):
```json
{"payloads":[{"type":"dm-verity-update"}],"artifact_provides":{"artifact_name":"NAME"},"artifact_depends":{"device_type":["keembay"]}}
```
- NO `rootfs-image.version` in provides (breaks install if included)
- NO state scripts
- NO meta-data file needed
### dm-verity-update Module
- Path: `/usr/share/mender/modules/v3/dm-verity-update`
- Streams (in order): system.img → passive_rootfs, syshash.img → passive_hash, boot.img → passive_kernel
- **Blindly cats streams** — does NOT recompute hash tree
- dm-verity is **NOT enforced at runtime** (`verity=0` in /proc/cmdline)
- syshash is only used by usb-updater for "already up to date" comparison check
### usb-updater Script
- Path: `/usr/bin/usb-updater`
- Triggered by `usb-updater.service` (enabled, not masked by liberate.sh)
- Flow:
1. Create swapfile at /data/swap if not exists (2GB)
2. Find .mender file in `/mnt/usb/hivemapper_update/`
3. Extract outer tar to USB tmp dir
4. Extract `syshash.img` from data/0000.tar.gz
5. Compare USB syshash with active hash partition (mmcblk1p6 or p9)
6. If same → "OS up to date", exit
7. If different → `mender --install artifact.mender`
8. If success → `mender --commit` → `reboot`
9. If fail → "Update failed", exit
- USB dir cleaned up after syshash compare (before mender runs)
- `mender-client.service` being masked does NOT affect CLI `mender --install`
### A/B Slot Logic
| U-Boot var | Meaning |
|-----------|---------|
| mender_boot_part=5 | Slot A active |
| mender_boot_part=8 | Slot B active |
| upgrade_available=1 | Update pending commit |
| upgrade_available=0 | Committed / stable |
**Post-install rollback trap:** usb-updater calls `mender --commit` before reboot. After reboot, `ArtifactVerifyReboot` in the mender state machine checks `upgrade_available=1` — but commit already set it to 0 → exits 1 → mender triggers rollback procedure. However ArtifactRollback condition also fails (upgrade_available != 1) so boot_part doesn't change. Device ends up on new firmware but mender thinks it rolled back. Harmless in practice.
### dm-verity Parameters (for veritysetup)
```
Hash algorithm: sha384
Salt: 3f3e0633b0a2cbf5066cd50af1a178ff8f50f660382f6c7508f58566cec64142
Data blocks: 524288
Block size: 4096
```
---
## Device Status
### Unit 1 — Original Bee (LIBERATED ✅)
- IP: 192.168.0.155 (from Lucy's perspective, via SSH tunnel)
- SSH: works, root access
- Tunnel: bee-tunnel.service → Lucy (when powered on)
- Status: Fully liberated, adacam services running
- mender_boot_part: 8 (Slot B active)
- Artifact: Release-20260309193836
### Unit 2 — Replacement Bee (LOCKED OUT ⚠️)
- IP: 192.168.0.10 (factory AP)
- SSH: key-only, no authorized keys → Permission denied (publickey)
- Cause: liberate.sh (old version, before v0.4) ran Phase 4 SSH hardening (PasswordAuthentication no) then failed at `mkdir /root/.ssh` (read-only rootfs). Hardened sshd_config was written to /data/overlay/current/ssh/sshd_config before the failure.
- Overlay file `/data/overlay/current/ssh/sshd_config` has hardened config and survives every flash
- usb-updater.service: enabled, NOT masked
- mender-client.service: masked (by Phase 1 of liberate.sh — harmless for USB updates)
---
## Artifacts Built
| File | Size | Description | Result |
|------|------|-------------|--------|
| ssh-recovery.mender | small | State script only, no firmware | State scripts not called → failed |
| hivemapper-forced.mender | 431MB | Factory firmware, random syshash | Infinite USB loop (hash mismatch) |
| adacam-ssh-fix.mender | 443MB | Modified sshd_config in system.img, correct syshash, no state scripts | Overlay overrides system.img changes → still locked |
| adacam-ssh-fix-v2.mender | 444MB | State script + wrong header (rootfs-image.version) | mender --install likely failed |
| adacam-ssh-fix-v3.mender | 444MB | State script + correct header format | State scripts not called by this mender fork |
| adacam-ssh-fix-v4.mender | 444MB | State script + USB log debug | Confirmed: NO log file = state scripts never run |
| **adacam-ssh-fix-v5.mender** | **403MB** | **Modified usb-updater in system.img + correct syshash** | **PENDING TEST** |
All artifacts at: `/mnt/cache/appdata/openclaw/config/workspace/projects/adacam/recovery/`
### v5 Approach (current)
- Modified `/usr/bin/usb-updater` in system.img to prepend SSH recovery code
- `/usr/bin` is on rootfs, NOT in /etc overlay → change survives flash
- On next USB insert after reboot into v5 firmware, usb-updater:
1. Writes open sshd_config to /etc/ssh/sshd_config (via overlay to /data/overlay/current/ssh/sshd_config)
2. Writes authorized_keys to /home/root/.ssh/authorized_keys
3. Restarts sshd
4. Continues normal usb-updater behavior
- SHA256: `acfbd16db9620f23785f8b103ffaeff6aed780f383273a61a23c8002f2bf0980`
- Served via nginx on Lucy: `http://192.168.0.5:9999/adacam-ssh-fix-v5.mender`
---
## Build Infrastructure
### Files on Lucy
```
/mnt/cache/appdata/openclaw/config/workspace/
projects/
adacam/
firmware/
extracted/
core.mender # original factory firmware
mender/data_extracted/
system.img # original 2GB rootfs
syshash.img # original 33MB hash tree
boot.img # original 256MB kernel (ext4 + FIT image)
recovery/
ssh-recovery.mender
hivemapper-forced.mender
adacam-ssh-fix.mender
adacam-ssh-fix-v2.mender
adacam-ssh-fix-v3.mender
adacam-ssh-fix-v4.mender
adacam-ssh-fix-v5.mender # CURRENT
build_v2.py # artifact builder (reuses existing data tar)
build_v5.sh # system.img patcher + artifact builder
patch-mender-artifact.py
build-mender-artifact.py
/tmp/bee_v5_work/
system.img # patched (usb-updater prepended)
syshash.img # recomputed for patched system.img
usb-updater-patched # the patched script (reference copy)
```
### Build Commands
```bash
# SSH to Lucy
ssh -i ~/.openclaw/id_ed25519_unraid -o StrictHostKeyChecking=no root@192.168.0.5
# Run Python via Docker (python3 not available on Lucy bare metal)
docker run --rm \
-v /mnt/cache/appdata/openclaw/config/workspace:/workspace \
-v /tmp/bee_v5_work:/v5work \
mingc/android-build-box python3 /v5work/build.py
# Compute syshash
veritysetup format \
--hash=sha384 \
--salt=3f3e0633b0a2cbf5066cd50af1a178ff8f50f660382f6c7508f58566cec64142 \
--data-block-size=4096 \
--hash-block-size=4096 \
system.img syshash.img
# Serve artifact
docker restart adacam-http # nginx on Lucy:9999
```
### Keys (authorized_keys)
```
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK87jxvlXvo60pxwdtyJsXeFsb4KsAiFx4FnyXz81kh7 cobb@adacam
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOQxwJU91TCxds34P18D3xRbu7rxlrgTUoml/H8nxeDK kayos@openclaw
```
---
## Lessons Learned
1. **Never harden SSH before confirming key auth works.** Write keys first, test, then restrict.
2. **The /etc overlay survives firmware flashes.** Any config change via /etc goes to /data and persists. Flashing new sshd_config in system.img does nothing if the overlay has a conflicting file.
3. **This Mender fork does NOT call state scripts.** Don't use state scripts with dm-verity-update type.
4. **rootfs-image.version in artifact_provides breaks mender --install.** Match the exact original header format.
5. **usb-updater cleans up its tmp dir AFTER the syshash comparison.** Seeing tmp on the USB mid-process is normal; it doesn't indicate failure.
6. **/usr/bin is NOT overlaid.** Changes to files in system.img outside of /etc are not overridden and take effect after flash.
7. **Build data tars to disk, not BytesIO.** Lucy has ~761MB free RAM — compressing 2GB in memory OOMs.
---
## Next Steps (when resuming)
### Immediate
1. Test `adacam-ssh-fix-v5.mender` on Unit 2
- Flash via USB
- After reboot: plug in any USB drive
- Check if sshd_config overlay is cleared + SSH opens
- If SSH opens: run liberate.sh v0.4
### If v5 fails
- UART access as last resort (physical header on PCB, unknown pinout — no public schematics found)
- Consider Hivemapper community forums / Discord for UART pinout info
### After SSH is restored (Unit 2)
- Run `liberate.sh v0.4` (safe — /data-only writes, usb-updater preserved, no SSH hardening)
- Verify adacam services install correctly
### Long-term (Path A — Custom Firmware Image)
- Mount clean system.img
- Bake in: adacam services, correct sshd_config, authorized_keys, modified usb-updater
- Generate Mender artifact signing keypair
- Bake `artifact_verify_key` into custom image
- Sign all future artifacts with our key (locks out Hivemapper OTA permanently)
- Retire liberate.sh
---
## Repository
- Gitea: `http://192.168.0.5:3001/Sulkta-Coop/adacam`
- liberate.sh current version: v0.4 (commit bddc1507)
- API token: `33a9eb57b58c262f4434c12028bc3a30b1ff7021`