Initial RK1 media-center image project

This commit is contained in:
2026-08-17 18:42:29 +00:00
commit 5fe41e79e9
81 changed files with 5725 additions and 0 deletions
+19
View File
@@ -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.
+17
View File
@@ -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'
+21
View File
@@ -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'
+32
View File
@@ -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)"
+63
View File
@@ -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)'
+23
View File
@@ -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'
+150
View File
@@ -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)"