- 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)
157 lines
4.9 KiB
Bash
157 lines
4.9 KiB
Bash
#!/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."
|