Files
rk1/tests/validate-image.sh

151 lines
6.0 KiB
Bash
Executable File

#!/usr/bin/env bash
# Read-only, loopless validation of a composed Turing RK1 image.
set -Eeuo pipefail
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
PROJECT_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd -P)
IMAGE=${1:-}
BASE_RAW=${2:-"$PROJECT_DIR/downloads/Armbian_community_26.11.0-trunk.1_Turing-rk1_trixie_vendor_6.1.115_minimal.img"}
[[ -n "$IMAGE" && -f "$IMAGE" ]] || {
echo "Usage: $0 IMAGE.img[.xz] [BASE.img]" >&2
exit 2
}
for tool in cmp debugfs e2fsck jq sfdisk sgdisk sha256sum xz; do
command -v "$tool" >/dev/null || { echo "Missing command: $tool" >&2; exit 1; }
done
TEST_TMP=$(mktemp -d /tmp/rk1-image-validation.XXXXXXXX)
trap 'rm -rf -- "$TEST_TMP"' EXIT
RAW_IMAGE=$IMAGE
if [[ "$IMAGE" == *.xz ]]; then
xz --test "$IMAGE"
RAW_IMAGE="$TEST_TMP/image.img"
xz --decompress --stdout "$IMAGE" >"$RAW_IMAGE"
fi
sgdisk --verify "$RAW_IMAGE" >/dev/null
partition_json=$(sfdisk --json "$RAW_IMAGE")
[[ "$(jq '.partitiontable.partitions | length' <<<"$partition_json")" == 1 ]] || {
echo 'Image must contain exactly one root partition' >&2
exit 1
}
partition_start=$(jq -r '.partitiontable.partitions[0].start' <<<"$partition_json")
partition_size=$(jq -r '.partitiontable.partitions[0].size' <<<"$partition_json")
partition_type=$(jq -r '.partitiontable.partitions[0].type' <<<"$partition_json")
[[ "$partition_start" == 32768 ]] || { echo "Unexpected partition start: $partition_start" >&2; exit 1; }
[[ "$partition_type" == B921B045-1DF0-41C3-AF44-4C6F280D3FAE ]] || {
echo "Unexpected ARM64 root partition type: $partition_type" >&2
exit 1
}
if [[ -f "$BASE_RAW" ]]; then
cmp --silent --bytes=440 "$BASE_RAW" "$RAW_IMAGE" || {
echo 'MBR bootstrap differs from the locked base image' >&2
exit 1
}
loader_offset=$((64 * 512))
loader_length=$(((32768 - 64) * 512))
cmp --silent --ignore-initial="$loader_offset:$loader_offset" \
--bytes="$loader_length" "$BASE_RAW" "$RAW_IMAGE" || {
echo 'Rockchip loader region differs from the locked base image' >&2
exit 1
}
fi
ROOTFS="$TEST_TMP/rootfs.ext4"
dd if="$RAW_IMAGE" of="$ROOTFS" bs=512 skip="$partition_start" \
count="$partition_size" status=none
e2fsck -fn "$ROOTFS" >"$TEST_TMP/e2fsck.log" 2>&1 || {
cat "$TEST_TMP/e2fsck.log" >&2
exit 1
}
ext4_stat() { debugfs -R "stat $1" "$ROOTFS" 2>/dev/null; }
ext4_exists() { ext4_stat "$1" | grep -q '^Inode:'; }
assert_contains() {
local path=$1 pattern=$2
debugfs -R "cat $path" "$ROOTFS" 2>/dev/null | grep -Eq "$pattern" || {
echo "Missing $pattern in $path" >&2
exit 1
}
}
assert_mode() {
local path=$1 mode=$2
ext4_stat "$path" | grep -Eq "Mode:[[:space:]]+$mode" || {
echo "Unexpected mode for $path (wanted $mode)" >&2
exit 1
}
}
assert_contains /etc/os-release '^VERSION_ID="?13"?$'
assert_contains /etc/armbian-release '^BOARD=turing-rk1$'
assert_contains /boot/armbianEnv.txt '^fdtfile=rockchip/rk3588-turing-rk1\.dtb$'
assert_contains /boot/armbianEnv.txt '^overlays=.*panthor-gpu'
assert_contains /etc/shadow '^root:!:'
assert_contains /etc/default/armbian-firstrun '^OPENSSHD_REGENERATE_HOST_KEYS=false$'
assert_contains /etc/ssh/sshd_config.d/90-rk1-media.conf '^PermitRootLogin no$'
assert_contains /etc/ssh/sshd_config.d/90-rk1-media.conf '^PasswordAuthentication no$'
assert_contains /etc/ssh/sshd_config.d/90-rk1-media.conf '^AuthenticationMethods publickey$'
assert_contains /etc/systemd/system/rk1-media-provision.service '^Before=.*kodi-rk\.service$'
assert_contains /usr/local/sbin/rk1-media-provision \
'^seed_archives=/opt/rk1-seed/debs$'
assert_contains /usr/local/sbin/rk1-media-provision \
'Dir::Cache::archives=.*seed_archives'
assert_contains /usr/local/sbin/rk1-media-provision \
'^systemctl --no-block start .*kodi-rk\.service$'
assert_contains /usr/local/sbin/rk1-media-provision \
'^udevadm trigger --action=add --subsystem-match=misc'
assert_contains /etc/systemd/system/rk1-media-identity.service '^RuntimeDirectory=sshd$'
assert_contains /usr/local/sbin/rk1-media-identity \
'^install -d -o root -g root -m 0755 /run/sshd$'
assert_contains /usr/local/sbin/rk1-media-identity '^/usr/sbin/sshd -t$'
assert_contains /opt/rk1-seed/debs.sha256 \
'^dd0ca68696aa35b1998f9c9e131fdf8f0d1ef4ba6f58a36d0d99f0625d47f430 rk1-media-stack_'
for required_path in \
/boot/dtb/rockchip/rk3588-turing-rk1.dtb \
/boot/dtb/rockchip/overlay/rockchip-rk3588-panthor-gpu.dtbo \
/lib/modules/6.1.115-vendor-rk35xx/kernel/drivers/gpu/drm/panthor/panthor.ko \
/opt/rknn/2.3.2/lib/librknnrt.so \
/opt/rknn/2.3.2/share/models/rk3588/mobilenet_v1.rknn \
/usr/local/bin/rknn-inference-test \
/usr/local/bin/rk1-media-selftest \
/usr/local/sbin/rk1-media-migrate-root-to-nvme \
/usr/local/sbin/rk1-media-diagnostics; do
ext4_exists "$required_path" || { echo "Missing image payload: $required_path" >&2; exit 1; }
done
for critical_dir in / /etc /etc/ssh /etc/systemd /usr /opt; do
assert_mode "$critical_dir" 0755
done
assert_mode /etc/shadow 0640
ext4_stat /etc/shadow | grep -Eq 'Group:[[:space:]]+42' || {
echo '/etc/shadow is not owned by the shadow group' >&2
exit 1
}
assert_mode /usr/local/sbin/rk1-media-identity 0755
assert_mode /usr/local/sbin/rk1-media-provision 0755
assert_mode /usr/local/sbin/rk1-media-migrate-root-to-nvme 0755
ext4_stat /etc/systemd/system/sysinit.target.wants/rk1-media-identity.service \
| grep -Fq 'Fast link dest: "../rk1-media-identity.service"' || exit 1
ext4_stat /etc/systemd/system/multi-user.target.wants/rk1-media-provision.service \
| grep -Fq 'Fast link dest: "../rk1-media-provision.service"' || exit 1
for forbidden_path in /root/.not_logged_in_yet /root/.ssh/authorized_keys \
/etc/ssh/ssh_host_rsa_key /etc/ssh/ssh_host_ecdsa_key \
/etc/ssh/ssh_host_ed25519_key; do
if ext4_exists "$forbidden_path"; then
echo "Image contains forbidden credential: $forbidden_path" >&2
exit 1
fi
done
deb_count=$(debugfs -R 'ls -p /opt/rk1-seed/debs' "$ROOTFS" 2>/dev/null \
| grep -c '/.*\.deb/' || true)
[[ "$deb_count" == 141 ]] || { echo "Expected 141 offline packages, found $deb_count" >&2; exit 1; }
echo "PASS: loopless RK1 image validation ($IMAGE)"