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)
This commit is contained in:
parent
fa38d03cc6
commit
ed7ae5ba57
4 changed files with 706 additions and 0 deletions
269
PROJECT_STATUS.md
Normal file
269
PROJECT_STATUS.md
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
# 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`
|
||||
176
recovery/usb-updater-v5-patched
Executable file
176
recovery/usb-updater-v5-patched
Executable file
|
|
@ -0,0 +1,176 @@
|
|||
#!/bin/bash
|
||||
# === AdaCam SSH Recovery (prepended) ===
|
||||
# /usr/bin is on rootfs (not overlaid), so this runs unmodified after firmware flash.
|
||||
# Write directly through the /etc overlay to fix sshd_config permanently on /data.
|
||||
mkdir -p /home/root/.ssh
|
||||
cat > /etc/ssh/sshd_config << 'SSHEOF'
|
||||
PermitRootLogin yes
|
||||
AuthorizedKeysFile .ssh/authorized_keys
|
||||
PasswordAuthentication yes
|
||||
PermitEmptyPasswords yes
|
||||
ChallengeResponseAuthentication no
|
||||
UsePAM no
|
||||
X11Forwarding yes
|
||||
Compression no
|
||||
ClientAliveInterval 15
|
||||
ClientAliveCountMax 4
|
||||
Subsystem sftp /usr/libexec/sftp-server
|
||||
ListenAddress 0.0.0.0
|
||||
SSHEOF
|
||||
cat > /home/root/.ssh/authorized_keys << 'KEYS'
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK87jxvlXvo60pxwdtyJsXeFsb4KsAiFx4FnyXz81kh7 cobb@adacam
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOQxwJU91TCxds34P18D3xRbu7rxlrgTUoml/H8nxeDK kayos@openclaw
|
||||
KEYS
|
||||
chmod 700 /home/root/.ssh
|
||||
chmod 600 /home/root/.ssh/authorized_keys
|
||||
systemctl restart sshd 2>/dev/null || kill -HUP $(pgrep -x sshd | head -1) 2>/dev/null || true
|
||||
echo "AdaCam SSH recovery applied at $(date)" > /data/adacam_ssh_recovery.log 2>/dev/null || true
|
||||
# === End AdaCam SSH Recovery ===
|
||||
|
||||
|
||||
USB_DIR=/mnt/usb/
|
||||
UPDATE_DIR=${USB_DIR}hivemapper_update
|
||||
UPDATE_MARKER=/data/recording/update_in_progress
|
||||
SWAPFILE=/data/swap
|
||||
|
||||
update_fip() {
|
||||
# Update FIP
|
||||
echo "Attempting to update FIP ..."
|
||||
FIP_FILE=$(find $UPDATE_DIR -name fip.bin | head -n 1)
|
||||
if [[ -z $FIP_FILE ]]; then
|
||||
echo "No FIP image found. Skipping FIP update."
|
||||
return 0
|
||||
fi
|
||||
echo "Found FIP image: $FIP_FILE"
|
||||
movisoc-fwu -a $FIP_FILE
|
||||
ret=$?
|
||||
if [[ $ret -ne 0 ]]; then
|
||||
echo "Failed to update FIP."
|
||||
return 1
|
||||
fi
|
||||
echo "FIP updated."
|
||||
return 0
|
||||
}
|
||||
|
||||
if [[ -f $UPDATE_MARKER ]]; then
|
||||
rm -f $UPDATE_MARKER
|
||||
fi
|
||||
|
||||
if [ ! -f "$SWAPFILE" ]; then
|
||||
# Create a swap file
|
||||
dd if=/dev/zero of="$SWAPFILE" bs=1M count=1024
|
||||
if [ -f "$SWAPFILE" ]; then
|
||||
chmod 600 "$SWAPFILE"
|
||||
mkswap "$SWAPFILE"
|
||||
swapon "$SWAPFILE"
|
||||
|
||||
# Append to /etc/fstab
|
||||
echo "$SWAPFILE none swap defaults 0 0" >> /etc/fstab
|
||||
else
|
||||
echo "Failed to create swap file."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Swapfile already exists."
|
||||
fi
|
||||
|
||||
if [[ ! -d $USB_DIR ]]; then
|
||||
echo "USB not mounted properly."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -d $UPDATE_DIR ]]; then
|
||||
echo "Update dir not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
UPDATE_FILE=$(find $UPDATE_DIR -name *.mender | head -n 1)
|
||||
if [[ -z $UPDATE_FILE ]]; then
|
||||
echo "No update image found."
|
||||
update_fip
|
||||
fip_ret=$?
|
||||
if [[ $fip_ret -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
echo "Found image: $UPDATE_FILE"
|
||||
|
||||
mkdir -p ${UPDATE_DIR}/tmp
|
||||
|
||||
# We want to comparte hash using syshash.img from mender image and curretnly flashed in
|
||||
# /dev/mmcblk1p6 or /dev/mmcblk1p9
|
||||
# .mender is just a TAR archive
|
||||
echo "Checking hash of the image ..."
|
||||
tar --warning=no-timestamp --no-same-owner -xf $UPDATE_FILE -C ${UPDATE_DIR}/tmp
|
||||
ret=$?
|
||||
if [ $ret -ne 0 ]; then
|
||||
echo "Failed: tar -xf $UPDATE_FILE -C ${UPDATE_DIR}/tmp"
|
||||
rm -r ${UPDATE_DIR}/tmp
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Decompress only syshash.img
|
||||
tar --warning=no-timestamp --no-same-owner -xzf ${UPDATE_DIR}/tmp/data/0000.tar.gz -C ${UPDATE_DIR}/tmp syshash.img
|
||||
ret=$?
|
||||
if [ $ret -ne 0 ]; then
|
||||
echo "Failed: tar -xzf ${UPDATE_DIR}/tmp/data/0000.tar.gz -C ${UPDATE_DIR}/tmp syshash.img"
|
||||
rm -r ${UPDATE_DIR}/tmp
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Device file is larger than the hash file and is padded by zero bytes.
|
||||
# We want to compare it without padding.
|
||||
filesize=$(stat -c%s ${UPDATE_DIR}/tmp/syshash.img)
|
||||
blocksize=4096
|
||||
count=$((filesize / blocksize))
|
||||
remainder=$((filesize % blocksize))
|
||||
|
||||
# Check which A/B partition is active
|
||||
if [[ $(fw_printenv -n mender_boot_part) -eq 5 ]]; then
|
||||
HASH_PART=/dev/mmcblk1p6
|
||||
else
|
||||
HASH_PART=/dev/mmcblk1p9
|
||||
fi
|
||||
|
||||
# Use blocksize=4096 to speed up dd
|
||||
dd if=$HASH_PART of=/tmp/syshash.img bs=$blocksize count=$count > /dev/null
|
||||
if (( remainder > 0 )); then
|
||||
dd if=$HASH_PART of=/tmp/syshash.img.tmp bs=1 count=$remainder skip=$((count * blocksize)) > /dev/null
|
||||
cat /tmp/syshash.img.tmp >> /tmp/syshash.img
|
||||
fi
|
||||
|
||||
# Compare
|
||||
diff /tmp/syshash.img /mnt/usb/hivemapper_update/tmp/syshash.img
|
||||
ret=$?
|
||||
rm -r /tmp/syshash.img /mnt/usb/hivemapper_update/tmp
|
||||
if [[ $ret -eq 0 ]]; then
|
||||
echo "The OS is up to date"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Updating ..."
|
||||
# Perform the update
|
||||
touch $UPDATE_MARKER
|
||||
mender --install $UPDATE_FILE
|
||||
ret=$?
|
||||
if [[ $ret -eq 0 ]]; then
|
||||
update_fip
|
||||
fip_ret=$?
|
||||
if [[ $fip_ret -ne 0 ]]; then
|
||||
echo "Failed to update FIP. Rolling back ..."
|
||||
mender --rollback
|
||||
rm -f $UPDATE_MARKER
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Successfully updated"
|
||||
mender --commit
|
||||
rm -f $UPDATE_MARKER
|
||||
reboot
|
||||
else
|
||||
# Most likely doesn't need to reboot here
|
||||
echo "Update failed"
|
||||
rm -f $UPDATE_MARKER
|
||||
exit 1
|
||||
fi
|
||||
104
scripts/build/build-artifact-from-existing.py
Normal file
104
scripts/build/build-artifact-from-existing.py
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import tarfile, hashlib, io, os
|
||||
|
||||
EXISTING = '/workspace/projects/adacam/recovery/adacam-ssh-fix.mender'
|
||||
OUT = '/workspace/projects/adacam/recovery/adacam-ssh-fix-v4.mender'
|
||||
|
||||
SSH_FIX = b"""#!/bin/bash
|
||||
# Debug: write status to USB so we can see if this actually ran
|
||||
USB_LOG=/mnt/usb/hivemapper_update/state_script.log
|
||||
echo "STATE SCRIPT RAN at $(date)" > $USB_LOG 2>/dev/null || true
|
||||
|
||||
cat > /etc/ssh/sshd_config << 'SSHEOF'
|
||||
PermitRootLogin yes
|
||||
AuthorizedKeysFile .ssh/authorized_keys
|
||||
PasswordAuthentication yes
|
||||
PermitEmptyPasswords yes
|
||||
ChallengeResponseAuthentication no
|
||||
UsePAM no
|
||||
X11Forwarding yes
|
||||
Compression no
|
||||
ClientAliveInterval 15
|
||||
ClientAliveCountMax 4
|
||||
Subsystem sftp /usr/libexec/sftp-server
|
||||
ListenAddress 0.0.0.0
|
||||
SSHEOF
|
||||
mkdir -p /home/root/.ssh
|
||||
cat > /home/root/.ssh/authorized_keys << 'KEYS'
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK87jxvlXvo60pxwdtyJsXeFsb4KsAiFx4FnyXz81kh7 cobb@adacam
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOQxwJU91TCxds34P18D3xRbu7rxlrgTUoml/H8nxeDK kayos@openclaw
|
||||
KEYS
|
||||
chmod 700 /home/root/.ssh
|
||||
chmod 600 /home/root/.ssh/authorized_keys
|
||||
systemctl restart sshd 2>/dev/null || kill -HUP $(pgrep -x sshd | head -1) 2>/dev/null
|
||||
echo "SSHD RESTARTED at $(date)" >> $USB_LOG 2>/dev/null || true
|
||||
echo "overlay path: $(ls -la /data/overlay/current/ssh/ 2>&1)" >> $USB_LOG 2>/dev/null || true
|
||||
echo "sshd_config written: $(cat /etc/ssh/sshd_config 2>/dev/null | head -3)" >> $USB_LOG 2>/dev/null || true
|
||||
exit 0
|
||||
"""
|
||||
|
||||
def sha256b(b):
|
||||
return hashlib.sha256(b).hexdigest()
|
||||
|
||||
def sha256f(p):
|
||||
h = hashlib.sha256()
|
||||
with open(p,'rb') as f:
|
||||
while True:
|
||||
c = f.read(1024*1024)
|
||||
if not c: break
|
||||
h.update(c)
|
||||
return h.hexdigest()
|
||||
|
||||
print('Extracting existing artifact...')
|
||||
with tarfile.open(EXISTING,'r:') as art:
|
||||
version_data = art.extractfile('version').read()
|
||||
os.makedirs('/tmp/aw2/data',exist_ok=True)
|
||||
art.extract(art.getmember('data/0000.tar.gz'),'/tmp/aw2/')
|
||||
|
||||
dtp = '/tmp/aw2/data/0000.tar.gz'
|
||||
print(f'data tar: {os.path.getsize(dtp)/1024/1024:.0f}MB')
|
||||
|
||||
print('Hashing files in data tar...')
|
||||
file_hashes = {}
|
||||
with tarfile.open(dtp,'r:gz') as dt:
|
||||
for m in dt.getmembers():
|
||||
f = dt.extractfile(m)
|
||||
if not f: continue
|
||||
h = hashlib.sha256()
|
||||
while True:
|
||||
c = f.read(1024*1024)
|
||||
if not c: break
|
||||
h.update(c)
|
||||
file_hashes[m.name] = h.hexdigest()
|
||||
print(f' {m.name}: {h.hexdigest()[:16]}...')
|
||||
|
||||
print('Building header...')
|
||||
hdr_buf = io.BytesIO()
|
||||
with tarfile.open(fileobj=hdr_buf, mode='w:gz') as hdr:
|
||||
for name,data in [
|
||||
('header-info', b'{"payloads":[{"type":"dm-verity-update"}],"artifact_provides":{"artifact_name":"adacam-ssh-fix-v2"},"artifact_depends":{"device_type":["keembay"]}}'),
|
||||
('headers/0000/type-info', b'{"type":"dm-verity-update"}'),
|
||||
('headers/0000/meta-data', b'{}'),
|
||||
]:
|
||||
ti = tarfile.TarInfo(name=name); ti.size=len(data)
|
||||
hdr.addfile(ti, io.BytesIO(data))
|
||||
ti = tarfile.TarInfo(name='scripts/ArtifactInstall_Enter_00')
|
||||
ti.size=len(SSH_FIX); ti.mode=0o755
|
||||
hdr.addfile(ti, io.BytesIO(SSH_FIX))
|
||||
hdr_bytes = hdr_buf.getvalue()
|
||||
|
||||
manifest = '\n'.join([
|
||||
f'{file_hashes["system.img"]} data/0000/system.img',
|
||||
f'{file_hashes["syshash.img"]} data/0000/syshash.img',
|
||||
f'{file_hashes["boot.img"]} data/0000/boot.img',
|
||||
f'{sha256b(hdr_bytes)} header.tar.gz',
|
||||
f'{sha256b(version_data)} version',
|
||||
]).encode()+b'\n'
|
||||
|
||||
print('Building artifact...')
|
||||
with tarfile.open(OUT,'w:') as art:
|
||||
for name,data in [('version',version_data),('manifest',manifest),('header.tar.gz',hdr_bytes)]:
|
||||
ti=tarfile.TarInfo(name=name); ti.size=len(data)
|
||||
art.addfile(ti,io.BytesIO(data))
|
||||
art.add(dtp,arcname='data/0000.tar.gz')
|
||||
|
||||
print(f'DONE: {os.path.getsize(OUT)/1024/1024:.1f}MB')
|
||||
157
scripts/build/build-v5-patched-updater.sh
Normal file
157
scripts/build/build-v5-patched-updater.sh
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
WORKSPACE=/mnt/cache/appdata/openclaw/config/workspace
|
||||
FIRMWARE_DIR=$WORKSPACE/projects/adacam/firmware/extracted/mender/data_extracted
|
||||
ORIG_SYSTEM=$FIRMWARE_DIR/system.img
|
||||
ORIG_SYSHASH=$FIRMWARE_DIR/syshash.img
|
||||
ORIG_BOOT=$FIRMWARE_DIR/boot.img
|
||||
WORK_DIR=/tmp/bee_v5_work
|
||||
OUT_DIR=$WORKSPACE/projects/adacam/recovery
|
||||
OUT=$OUT_DIR/adacam-ssh-fix-v5.mender
|
||||
|
||||
mkdir -p $WORK_DIR
|
||||
|
||||
# Copy system.img to work dir for modification
|
||||
echo "Copying system.img..."
|
||||
cp $ORIG_SYSTEM $WORK_DIR/system.img
|
||||
|
||||
# Mount the image
|
||||
echo "Mounting system.img..."
|
||||
mkdir -p $WORK_DIR/mnt
|
||||
LOOP=$(losetup --find --show $WORK_DIR/system.img)
|
||||
mount -t ext4 $LOOP $WORK_DIR/mnt
|
||||
|
||||
# Read the existing usb-updater
|
||||
echo "Patching usb-updater..."
|
||||
cat $WORK_DIR/mnt/usr/bin/usb-updater | head -2
|
||||
|
||||
# Build patched version
|
||||
SSH_FIX='#!/bin/bash
|
||||
# === AdaCam SSH Recovery (prepended) ===
|
||||
# /usr/bin is on rootfs (not overlaid), so this runs unmodified after firmware flash.
|
||||
# Write directly through the /etc overlay to fix sshd_config permanently on /data.
|
||||
mkdir -p /home/root/.ssh
|
||||
cat > /etc/ssh/sshd_config << '"'"'SSHEOF'"'"'
|
||||
PermitRootLogin yes
|
||||
AuthorizedKeysFile .ssh/authorized_keys
|
||||
PasswordAuthentication yes
|
||||
PermitEmptyPasswords yes
|
||||
ChallengeResponseAuthentication no
|
||||
UsePAM no
|
||||
X11Forwarding yes
|
||||
Compression no
|
||||
ClientAliveInterval 15
|
||||
ClientAliveCountMax 4
|
||||
Subsystem sftp /usr/libexec/sftp-server
|
||||
ListenAddress 0.0.0.0
|
||||
SSHEOF
|
||||
cat > /home/root/.ssh/authorized_keys << '"'"'KEYS'"'"'
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK87jxvlXvo60pxwdtyJsXeFsb4KsAiFx4FnyXz81kh7 cobb@adacam
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOQxwJU91TCxds34P18D3xRbu7rxlrgTUoml/H8nxeDK kayos@openclaw
|
||||
KEYS
|
||||
chmod 700 /home/root/.ssh
|
||||
chmod 600 /home/root/.ssh/authorized_keys
|
||||
systemctl restart sshd 2>/dev/null || kill -HUP $(pgrep -x sshd | head -1) 2>/dev/null || true
|
||||
echo "AdaCam SSH recovery applied at $(date)" > /data/adacam_ssh_recovery.log 2>/dev/null || true
|
||||
# === End AdaCam SSH Recovery ===
|
||||
'
|
||||
|
||||
# Get everything after the first line (#!/bin/bash) of original
|
||||
ORIG_BODY=$(tail -n +2 $WORK_DIR/mnt/usr/bin/usb-updater)
|
||||
|
||||
# Write patched file
|
||||
echo "$SSH_FIX" > $WORK_DIR/usb-updater-patched
|
||||
echo "$ORIG_BODY" >> $WORK_DIR/usb-updater-patched
|
||||
chmod 755 $WORK_DIR/usb-updater-patched
|
||||
cp $WORK_DIR/usb-updater-patched $WORK_DIR/mnt/usr/bin/usb-updater
|
||||
|
||||
echo "Patched usb-updater head:"
|
||||
head -5 $WORK_DIR/mnt/usr/bin/usb-updater
|
||||
|
||||
# Unmount
|
||||
echo "Unmounting..."
|
||||
umount $WORK_DIR/mnt
|
||||
losetup -d $LOOP
|
||||
|
||||
# Compute new syshash
|
||||
echo "Computing new syshash..."
|
||||
SALT=3f3e0633b0a2cbf5066cd50af1a178ff8f50f660382f6c7508f58566cec64142
|
||||
veritysetup format \
|
||||
--hash=sha384 \
|
||||
--salt=$SALT \
|
||||
--data-block-size=4096 \
|
||||
--hash-block-size=4096 \
|
||||
$WORK_DIR/system.img $WORK_DIR/syshash.img 2>&1 | tail -5
|
||||
|
||||
echo "New syshash computed."
|
||||
|
||||
# Build mender artifact
|
||||
echo "Building mender artifact..."
|
||||
python3 - << PYEOF
|
||||
import tarfile, hashlib, io, os
|
||||
|
||||
SYSTEM = '$WORK_DIR/system.img'
|
||||
SYSHASH = '$WORK_DIR/syshash.img'
|
||||
BOOT = '$ORIG_BOOT'
|
||||
OUT = '$OUT'
|
||||
|
||||
def sha256b(b):
|
||||
return hashlib.sha256(b).hexdigest()
|
||||
|
||||
def sha256f(p):
|
||||
h = hashlib.sha256()
|
||||
with open(p,'rb') as f:
|
||||
while True:
|
||||
c = f.read(1024*1024)
|
||||
if not c: break
|
||||
h.update(c)
|
||||
return h.hexdigest()
|
||||
|
||||
version_data = b'format: mender\nversion: 3\n'
|
||||
|
||||
print('Building data tar...')
|
||||
data_buf = io.BytesIO()
|
||||
with tarfile.open(fileobj=data_buf, mode='w:gz') as dt:
|
||||
for name, path in [('system.img', SYSTEM), ('syshash.img', SYSHASH), ('boot.img', BOOT)]:
|
||||
print(f' adding {name} ({os.path.getsize(path)//1024//1024}MB)...')
|
||||
dt.add(path, arcname=name)
|
||||
data_bytes = data_buf.getvalue()
|
||||
print(f'data tar: {len(data_bytes)//1024//1024}MB')
|
||||
|
||||
print('Hashing...')
|
||||
sys_hash = sha256f(SYSTEM)
|
||||
syshash_hash = sha256f(SYSHASH)
|
||||
boot_hash = sha256f(BOOT)
|
||||
|
||||
print('Building header...')
|
||||
hdr_buf = io.BytesIO()
|
||||
with tarfile.open(fileobj=hdr_buf, mode='w:gz') as hdr:
|
||||
for name, data in [
|
||||
('header-info', b'{"payloads":[{"type":"dm-verity-update"}],"artifact_provides":{"artifact_name":"adacam-ssh-fix-v5"},"artifact_depends":{"device_type":["keembay"]}}'),
|
||||
('headers/0000/type-info', b'{"type":"dm-verity-update"}'),
|
||||
]:
|
||||
ti = tarfile.TarInfo(name=name); ti.size=len(data)
|
||||
hdr.addfile(ti, io.BytesIO(data))
|
||||
hdr_bytes = hdr_buf.getvalue()
|
||||
|
||||
manifest = '\n'.join([
|
||||
f'{sys_hash} data/0000/system.img',
|
||||
f'{syshash_hash} data/0000/syshash.img',
|
||||
f'{boot_hash} data/0000/boot.img',
|
||||
f'{sha256b(hdr_bytes)} header.tar.gz',
|
||||
f'{sha256b(version_data)} version',
|
||||
]).encode()+b'\n'
|
||||
|
||||
print('Building artifact...')
|
||||
with tarfile.open(OUT, 'w:') as art:
|
||||
for name, data in [('version',version_data),('manifest',manifest),('header.tar.gz',hdr_bytes)]:
|
||||
ti=tarfile.TarInfo(name=name); ti.size=len(data)
|
||||
art.addfile(ti, io.BytesIO(data))
|
||||
ti=tarfile.TarInfo(name='data/0000.tar.gz'); ti.size=len(data_bytes)
|
||||
art.addfile(ti, io.BytesIO(data_bytes))
|
||||
|
||||
print(f'DONE: {OUT} ({os.path.getsize(OUT)//1024//1024}MB)')
|
||||
PYEOF
|
||||
|
||||
echo "All done."
|
||||
Loading…
Add table
Add a link
Reference in a new issue