Initial RK1 media-center image project
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# Validation
|
||||
|
||||
Run all host-side checks, the offline package-transaction simulation, and the
|
||||
real acquisition-only APT regression (no packages are installed):
|
||||
|
||||
```bash
|
||||
./tests/run-static.sh
|
||||
```
|
||||
|
||||
Validate a finished image without mounting it or using loop devices:
|
||||
|
||||
```bash
|
||||
./tests/validate-image.sh dist/rk1-media-20260817-r4-trixie-vendor-6.1.115-turing-rk1-emmc.img
|
||||
```
|
||||
|
||||
Passing these checks establishes image structure, input integrity, package
|
||||
closure, boot payload preservation, and security invariants. HDMI link,
|
||||
hardware codecs, RGA, GPU, audio/CEC, and RKNN execution still require the
|
||||
physical acceptance test after flashing.
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
PROJECT_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd -P)
|
||||
|
||||
while IFS= read -r -d '' script; do
|
||||
bash -n "$script"
|
||||
done < <(find "$PROJECT_DIR" -path '*/sources/*' -prune -o -type f -name '*.sh' -print0)
|
||||
"$PROJECT_DIR/armbian/tests/lint.sh"
|
||||
"$SCRIPT_DIR/test-firstboot-ssh.sh"
|
||||
RKNN_SOURCE_DIR="$PROJECT_DIR/downloads/rknn-toolkit2-v2.3.2-minimal" \
|
||||
"$PROJECT_DIR/runtime/tests/run-tests.sh"
|
||||
"$SCRIPT_DIR/test-media-package.sh"
|
||||
bash "$SCRIPT_DIR/test-provisioning-recipe.sh"
|
||||
"$SCRIPT_DIR/test-offline-apt.sh"
|
||||
echo 'PASS: all static/integration tests'
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
PROJECT_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd -P)
|
||||
IDENTITY="$PROJECT_DIR/image/overlay/usr/local/sbin/rk1-media-identity"
|
||||
UNIT="$PROJECT_DIR/image/overlay/etc/systemd/system/rk1-media-identity.service"
|
||||
DROPIN="$PROJECT_DIR/image/overlay/etc/systemd/system/ssh.service.d/10-rk1-media-identity.conf"
|
||||
|
||||
runtime_line=$(grep -nF 'install -d -o root -g root -m 0755 /run/sshd' "$IDENTITY" | cut -d: -f1)
|
||||
validation_line=$(grep -nF '/usr/sbin/sshd -t' "$IDENTITY" | cut -d: -f1)
|
||||
[[ -n "$runtime_line" && -n "$validation_line" && "$runtime_line" -lt "$validation_line" ]] || {
|
||||
echo 'SSH runtime directory is not created before configuration validation' >&2
|
||||
exit 1
|
||||
}
|
||||
grep -qx 'RuntimeDirectory=sshd' "$UNIT"
|
||||
grep -qx 'RuntimeDirectoryMode=0755' "$UNIT"
|
||||
grep -qx 'Requires=rk1-media-identity.service' "$DROPIN"
|
||||
grep -qx 'After=rk1-media-identity.service' "$DROPIN"
|
||||
|
||||
echo 'PASS: first-boot SSH runtime ordering regression'
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
PROJECT_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd -P)
|
||||
DEB=${1:-"$PROJECT_DIR/media/out/rk1-media-stack_20260721.1741+rk1.1_arm64.deb"}
|
||||
[[ -f "$DEB" ]] || { echo "Missing media package: $DEB" >&2; exit 1; }
|
||||
|
||||
TEST_TMP=$(mktemp -d /tmp/rk1-media-package-test.XXXXXXXX)
|
||||
trap 'rm -rf -- "$TEST_TMP"' EXIT
|
||||
dpkg-deb --info "$DEB" >/dev/null
|
||||
dpkg-deb --extract "$DEB" "$TEST_TMP/root"
|
||||
dpkg-deb --control "$DEB" "$TEST_TMP/control"
|
||||
|
||||
[[ "$(dpkg-deb --field "$DEB" Package)" == rk1-media-stack ]]
|
||||
[[ "$(dpkg-deb --field "$DEB" Architecture)" == arm64 ]]
|
||||
if find "$TEST_TMP/root" \( -type d -o -type f \) -perm /0022 -print -quit | grep -q .; then
|
||||
echo 'Package contains a group/world-writable path' >&2
|
||||
exit 1
|
||||
fi
|
||||
! find "$TEST_TMP/root" -type f -name '*.a' -print -quit | grep -q .
|
||||
! find "$TEST_TMP/root" -type d -name pkgconfig -print -quit | grep -q .
|
||||
[[ -x "$TEST_TMP/root/opt/rkmedia/bin/ffmpeg-rk" ]]
|
||||
[[ -x "$TEST_TMP/root/opt/rkmedia/bin/kodi-rk" ]]
|
||||
[[ -f "$TEST_TMP/root/usr/lib/systemd/system/kodi-rk.service" ]]
|
||||
[[ ! -e "$TEST_TMP/root/usr/lib/systemd/system/kodi.service" ]]
|
||||
grep -q 'KERNEL=="rknpu\*"' "$TEST_TMP/root/usr/lib/udev/rules.d/70-rkmedia.rules"
|
||||
grep -q 'udevadm trigger.*subsystem-match=drm' "$TEST_TMP/control/postinst"
|
||||
! grep -q '^ConditionPathExists=/dev/dri/card0' \
|
||||
"$TEST_TMP/root/usr/lib/systemd/system/kodi-rk.service"
|
||||
|
||||
echo "PASS: media package structure ($DEB)"
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
PROJECT_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd -P)
|
||||
DEPENDENCY_ARCHIVES="$PROJECT_DIR/work/apt/archives"
|
||||
MEDIA_DEB="$PROJECT_DIR/media/out/rk1-media-stack_20260721.1741+rk1.1_arm64.deb"
|
||||
[[ -d "$DEPENDENCY_ARCHIVES" ]] || { echo "Missing APT archives: $DEPENDENCY_ARCHIVES" >&2; exit 1; }
|
||||
[[ -f "$MEDIA_DEB" ]] || { echo "Missing media package: $MEDIA_DEB" >&2; exit 1; }
|
||||
|
||||
TEST_TMP=$(mktemp -d "$PROJECT_DIR/work/rk1-offline-apt.XXXXXXXX")
|
||||
trap 'rm -rf -- "$TEST_TMP"' EXIT
|
||||
SEED_ARCHIVES="$TEST_TMP/opt/rk1-seed/debs"
|
||||
mkdir -p "$SEED_ARCHIVES/partial" "$TEST_TMP/state"
|
||||
|
||||
# Hard links keep this acquisition regression test fast while presenting APT
|
||||
# with the exact single-directory cache layout used in the finished image.
|
||||
while IFS= read -r -d '' source_deb; do
|
||||
ln -- "$source_deb" "$SEED_ARCHIVES/$(basename -- "$source_deb")"
|
||||
done < <(find "$DEPENDENCY_ARCHIVES" -maxdepth 1 -type f -name '*.deb' -print0 | sort -z)
|
||||
ln -- "$MEDIA_DEB" "$SEED_ARCHIVES/$(basename -- "$MEDIA_DEB")"
|
||||
mapfile -d '' debs < <(find "$SEED_ARCHIVES" -maxdepth 1 -type f -name '*.deb' -print0 | sort -z)
|
||||
[[ "${#debs[@]}" == 141 ]]
|
||||
|
||||
apt_options=(
|
||||
-o APT::Architecture=arm64
|
||||
-o Dir::State::status="$PROJECT_DIR/work/base-meta/status"
|
||||
-o Dir::State::lists="$PROJECT_DIR/work/apt/lists"
|
||||
-o Dir::State::extended_states="$TEST_TMP/state/extended_states"
|
||||
-o Dir::Cache::archives="$SEED_ARCHIVES"
|
||||
-o Dir::Cache::pkgcache="$TEST_TMP/pkgcache.bin"
|
||||
-o Dir::Cache::srcpkgcache="$TEST_TMP/srcpkgcache.bin"
|
||||
-o Dir::Etc::sourcelist="$PROJECT_DIR/work/base-meta/debian-build.sources"
|
||||
-o Dir::Etc::sourceparts=-
|
||||
-o Debug::NoLocking=true
|
||||
-o APT::Sandbox::User=
|
||||
-o APT::Keep-Downloaded-Packages=true
|
||||
)
|
||||
|
||||
simulation_log="$TEST_TMP/simulation.log"
|
||||
apt-get "${apt_options[@]}" --simulate --no-download --no-install-recommends \
|
||||
install "${debs[@]}" >"$simulation_log"
|
||||
grep -q '^0 upgraded, 141 newly installed, 0 to remove' "$simulation_log"
|
||||
[[ "$(grep -c '^Inst ' "$simulation_log")" == 141 ]]
|
||||
[[ "$(grep -c '^Conf ' "$simulation_log")" == 141 ]]
|
||||
|
||||
(
|
||||
cd "$SEED_ARCHIVES"
|
||||
sha256sum -- *.deb >"$TEST_TMP/debs.before.sha256"
|
||||
)
|
||||
acquisition_log="$TEST_TMP/acquisition.log"
|
||||
apt-get "${apt_options[@]}" --download-only --no-download \
|
||||
--no-install-recommends --yes install "${debs[@]}" >"$acquisition_log"
|
||||
grep -q '^0 upgraded, 141 newly installed, 0 to remove' "$acquisition_log"
|
||||
grep -Eq '^Download complete( and in download only mode)?$' "$acquisition_log"
|
||||
! grep -q '^Need to get ' "$acquisition_log"
|
||||
(
|
||||
cd "$SEED_ARCHIVES"
|
||||
sha256sum -- *.deb >"$TEST_TMP/debs.after.sha256"
|
||||
)
|
||||
cmp --silent "$TEST_TMP/debs.before.sha256" "$TEST_TMP/debs.after.sha256"
|
||||
|
||||
echo 'PASS: offline APT closure and real acquisition (141 packages, zero downloads)'
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)
|
||||
PROJECT_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd -P)
|
||||
PROVISION="$PROJECT_DIR/image/overlay/usr/local/sbin/rk1-media-provision"
|
||||
UNIT="$PROJECT_DIR/image/overlay/etc/systemd/system/rk1-media-provision.service"
|
||||
|
||||
grep -Fq 'seed_archives=/opt/rk1-seed/debs' "$PROVISION"
|
||||
grep -Fq 'install -d -o _apt -g root -m 0700 "$seed_archives/partial"' "$PROVISION"
|
||||
grep -Fq -- '-o Dir::Cache::archives="$seed_archives"' "$PROVISION"
|
||||
grep -Fq -- '--no-download --no-install-recommends --yes install' "$PROVISION"
|
||||
|
||||
grep -Eq '^Before=.*kodi-rk\.service$' "$UNIT"
|
||||
grep -Eq '^systemctl --no-block start .*kodi-rk\.service$' "$PROVISION"
|
||||
! grep -Eq '^systemctl start .*kodi-rk\.service' "$PROVISION"
|
||||
|
||||
for subsystem in drm dma_heap misc; do
|
||||
grep -Fq "udevadm trigger --action=add --subsystem-match=$subsystem" "$PROVISION"
|
||||
done
|
||||
! grep -Eq '^udevadm trigger --action=change' "$PROVISION"
|
||||
|
||||
echo 'PASS: provisioning cache, unit ordering, and device-event recipe'
|
||||
Executable
+150
@@ -0,0 +1,150 @@
|
||||
#!/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)"
|
||||
Reference in New Issue
Block a user