Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -152,3 +152,10 @@ so3/so3/scripts/kconfig/zconf.tab.c
*.attach.pf
__pycache__/
/scripts.old/

# Left by scripts/tezi-custom/build.sh
scripts/tezi-custom/recovery/
scripts/tezi-custom/boot.scr
scripts/tezi-custom/imx-boot-recoverytezi
scripts/tezi-custom/imx-boot-sd
scripts/tezi-custom/overlays.txt
122 changes: 122 additions & 0 deletions scripts/tezi-custom/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/usr/bin/env bash

# Copyright (c) 2025-2026 EDGEMTech SA

# Build a customized Toradex Easy Installer (tezi.itb) for the verdin-imx8mp
# board: same as the upstream Toradex one, with /etc/rc.local extended to
# auto-register a local nginx image feed (see rc.local.append.in).
#
# Zero-config defaults:
# - host IP detected via `ip route get 1.1.1.1` (override: EDGEM_HOST_IP)
# - upstream downloaded from tezi.toradex.com into build/tmp/tezi-installer/
# (override: TEZI_SOURCE_DIR — pre-existing local archive root)
#
# Output: tezi.itb in this directory + symlinks/copies of unchanged upstream
# artefacts. Launcher: scripts/recovery-edgem.sh

set -euo pipefail

SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
PROJECT_ROOT=$(cd "$SCRIPT_DIR/../.." && pwd)
WORK=${WORK:-/tmp/tezi-custom-build}

TEZI_VERSION=${TEZI_VERSION:-7.5.0+build.9}
TEZI_RELEASE=${TEZI_RELEASE:-9}
TEZI_ARCHIVE_URL=${TEZI_ARCHIVE_URL:-"https://tezi.toradex.com/artifactory/tezi-oe-prod-frankfurt/scarthgap-7.x.y/release/${TEZI_RELEASE}/verdin-imx8mp/tezi/tezi-run/oedeploy/Verdin-iMX8MP_ToradexEasyInstaller_${TEZI_VERSION}.zip"}
TEZI_CACHE_DIR=${TEZI_CACHE_DIR:-"$PROJECT_ROOT/build/tmp/tezi-installer"}
TEZI_SOURCE_DIR=${TEZI_SOURCE_DIR:-}

for t in dumpimage unsquashfs mksquashfs fakeroot sha256sum python3 curl unzip ip; do
command -v "$t" >/dev/null || { echo "missing tool: $t" >&2; exit 1; }
done

if [ -z "$TEZI_SOURCE_DIR" ]; then
TEZI_SOURCE_DIR="$TEZI_CACHE_DIR/Verdin-iMX8MP_ToradexEasyInstaller_$TEZI_VERSION"
if [ ! -f "$TEZI_SOURCE_DIR/tezi.itb" ]; then
echo "Toradex Easy Installer ${TEZI_VERSION} not in cache — downloading..."
mkdir -p "$TEZI_CACHE_DIR"
TMP_ZIP="$TEZI_CACHE_DIR/.tezi-${TEZI_VERSION}.zip"
curl -fL --retry 3 -o "$TMP_ZIP" "$TEZI_ARCHIVE_URL"
rm -rf "$TEZI_SOURCE_DIR"
unzip -q -d "$TEZI_CACHE_DIR" "$TMP_ZIP"
rm "$TMP_ZIP"
if [ ! -f "$TEZI_SOURCE_DIR/tezi.itb" ]; then
echo "Archive extracted but tezi.itb not at expected path: $TEZI_SOURCE_DIR" >&2
echo "Inspect $TEZI_CACHE_DIR and set TEZI_SOURCE_DIR to the right directory." >&2
exit 1
fi
fi
elif [ ! -f "$TEZI_SOURCE_DIR/tezi.itb" ]; then
echo "TEZI_SOURCE_DIR=$TEZI_SOURCE_DIR does not contain tezi.itb" >&2
exit 1
fi
echo "Using upstream TEZI at: $TEZI_SOURCE_DIR"

if [ -z "${EDGEM_HOST_IP:-}" ]; then
# Walk default routes in metric order; skip tunnel-like interfaces (vpn0,
# tun*, wg*, ...) so an active VPN doesn't capture the board-flashing IP.
while IFS= read -r dev; do
case "$dev" in vpn*|tun*|tap*|wg*|ppp*) continue ;; esac
EDGEM_HOST_IP=$(ip -4 -o addr show dev "$dev" 2>/dev/null \
| awk 'NR==1 {sub(/\/.*/,"",$4); print $4}')
[ -n "$EDGEM_HOST_IP" ] && break
done < <(ip -4 route show default | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}')
fi
if [ -z "${EDGEM_HOST_IP:-}" ]; then
echo "Could not auto-detect host IP. Set EDGEM_HOST_IP=<ip> manually." >&2
exit 1
fi
# Port 8080 by default: the feed is served straight out of the build tree by
# scripts/tezi-feed-serve.sh, running as your user, so it cannot bind :80.
# Keep this in step with IB_HTTP_FEED_PORT in build/conf/local.conf — the URL
# is baked into the TEZI image here, so a port change means rebuilding it.
EDGEM_FEED_PORT=${EDGEM_FEED_PORT:-8080}
EDGEM_FEED_URL=${EDGEM_FEED_URL:-"http://${EDGEM_HOST_IP}:${EDGEM_FEED_PORT}/image_list.json"}
echo "Embedded feed URL: $EDGEM_FEED_URL"

VENV=$SCRIPT_DIR/.venv
if ! "$VENV/bin/python" -c 'import libfdt' 2>/dev/null; then
python3 -m venv "$VENV"
"$VENV/bin/pip" -q install pylibfdt
fi

rm -rf "$WORK"
mkdir -p "$WORK"

sed "s|@EDGEM_FEED_URL@|$EDGEM_FEED_URL|g" "$SCRIPT_DIR/rc.local.append.in" > "$WORK/rc.local.append"

dumpimage -T flat_dt -p 29 -o "$WORK/initramfs.squashfs" "$TEZI_SOURCE_DIR/tezi.itb" >/dev/null

fakeroot bash -c "
set -e
cd '$WORK'
unsquashfs -d rootfs initramfs.squashfs >/dev/null
cat rc.local.append >> rootfs/etc/rc.local
mksquashfs rootfs new.sqfs -comp gzip -b 131072 -noappend -no-progress >/dev/null
"

"$VENV/bin/python" "$SCRIPT_DIR/patch_fit.py" \
"$TEZI_SOURCE_DIR/tezi.itb" \
"$WORK/new.sqfs" \
"$SCRIPT_DIR/tezi.itb"

ln -sfn "$TEZI_SOURCE_DIR/boot.scr" "$SCRIPT_DIR/boot.scr"
ln -sfn "$TEZI_SOURCE_DIR/imx-boot-recoverytezi" "$SCRIPT_DIR/imx-boot-recoverytezi"
ln -sfn "$TEZI_SOURCE_DIR/imx-boot-sd" "$SCRIPT_DIR/imx-boot-sd"
ln -sfn "$TEZI_SOURCE_DIR/overlays.txt" "$SCRIPT_DIR/overlays.txt"

# recovery/ must be a real subdirectory: when uuu resolves the relative paths
# in uuu.auto, the kernel walks `recovery/../<file>` and if `recovery/` were a
# symlink to upstream Toradex it would end up loading the unmodified upstream
# tezi.itb. Only the uuu binaries are symlinked. uuu also unconditionally
# prepends the cmdlist's directory to each `-f` path, so absolute paths break
# with "recovery//home/...". Keep them relative.

rm -rf "$SCRIPT_DIR/recovery"
mkdir -p "$SCRIPT_DIR/recovery"
ln -sfn "$TEZI_SOURCE_DIR/recovery/uuu" "$SCRIPT_DIR/recovery/uuu"
ln -sfn "$TEZI_SOURCE_DIR/recovery/uuu.exe" "$SCRIPT_DIR/recovery/uuu.exe"
cp "$TEZI_SOURCE_DIR/recovery/uuu.auto" "$SCRIPT_DIR/recovery/uuu.auto"

echo
echo "tezi-custom built (feed=$EDGEM_FEED_URL). Launch with: scripts/recovery-edgem.sh"
44 changes: 44 additions & 0 deletions scripts/tezi-custom/patch_fit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env python3

# Copyright (c) 2025-2026 EDGEMTech SA

"""Replace /images/ramdisk-1/data in a FIT and refresh its sha256 hash."""
import hashlib
import sys
import libfdt

if len(sys.argv) != 4:
sys.exit(f"usage: {sys.argv[0]} <orig.itb> <new-ramdisk> <out.itb>")

orig_fit, new_ramdisk, out_fit = sys.argv[1:]

with open(orig_fit, "rb") as f:
blob = bytearray(f.read())
with open(new_ramdisk, "rb") as f:
new_data = f.read()

fdt = libfdt.Fdt(blob)
node = fdt.path_offset("/images/ramdisk-1")
old_data = fdt.getprop(node, "data")
print(f"orig ramdisk-1 data: {len(old_data)} bytes")
print(f"new ramdisk-1 data: {len(new_data)} bytes")

delta = len(new_data) - len(old_data)
if delta > 0:
fdt.resize(len(blob) + delta + 4096)

fdt.setprop(node, "data", new_data)

hash_node = fdt.subnode_offset(node, "hash-1")
algo = bytes(fdt.getprop(hash_node, "algo")).rstrip(b"\0").decode()
if algo != "sha256":
sys.exit(f"unexpected hash algo: {algo}")
new_hash = hashlib.sha256(new_data).digest()
fdt.setprop(hash_node, "value", new_hash)
print(f"hash-1 sha256 -> {new_hash.hex()}")

fdt.pack()
out = bytes(fdt.as_bytearray())
with open(out_fit, "wb") as f:
f.write(out)
print(f"wrote {out_fit} ({len(out)} bytes)")
20 changes: 20 additions & 0 deletions scripts/tezi-custom/rc.local.append.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

# EDGE-M1 — auto-register the local image feed once tezi is ready.
# Default URL injected at build time by scripts/tezi-custom/build.sh
# (substitutes the host's LAN IP and feed port). Override at runtime via
# kernel cmdline:
# edgem_feed=http://<host-ip>:8080/image_list.json
(
EDGEM_FEED_URL="@EDGEM_FEED_URL@"
for p in $(cat /proc/cmdline); do
case "$p" in
edgem_feed=*) EDGEM_FEED_URL="${p#edgem_feed=}";;
esac
done
for _ in $(seq 1 60); do
tezictl status >/dev/null 2>&1 && break
sleep 1
done
tezictl feed-add "$EDGEM_FEED_URL"
echo "EDGE-M1: tezictl feed-add $EDGEM_FEED_URL done"
) >> ${RW_MEMORY_DIR}/${TEZI_LOGFILE} 2>&1 &
6 changes: 3 additions & 3 deletions scripts/tezi-feed-serve.sh
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ logfile="$runtime_dir/tezi-feed.log"
# --- helpers ----------------------------------------------------------------

# Host IP as the board sees it: first default route, skipping tunnel-like
# interfaces so an active VPN does not capture the flashing address. This is
# the address printed below and handed to the board with `tezictl feed-add`,
# so a wrong pick sends the board to a host it cannot reach.
# interfaces so an active VPN does not capture the flashing address. Same
# selection as scripts/tezi-custom/build.sh, which bakes the URL into the
# custom TEZI image — the two must agree or the board pulls the wrong host.
host_ip()
{
ip -4 route show default 2>/dev/null \
Expand Down
Loading